-
Notifications
You must be signed in to change notification settings - Fork 436
Topic link autocomplete #2137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Topic link autocomplete #2137
Changes from 7 commits
37bebaf
39606eb
d565fd2
9a645a0
1e0e93e
7a86896
c407d85
5ddd4d9
a611455
d9a3b48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -188,7 +188,7 @@ String userGroupMention(String userGroupName, {bool silent = false}) => | |||||||||||||||
| // Corresponds to `topic_link_util.escape_invalid_stream_topic_characters` | ||||||||||||||||
| // in Zulip web: | ||||||||||||||||
| // https://github.com/zulip/zulip/blob/b42d3e77e/web/src/topic_link_util.ts#L15-L34 | ||||||||||||||||
| const _channelAvoidedCharsReplacements = { | ||||||||||||||||
| const _channelTopicAvoidedCharsReplacements = { | ||||||||||||||||
| '`': '`', | ||||||||||||||||
| '>': '>', | ||||||||||||||||
| '*': '*', | ||||||||||||||||
|
|
@@ -198,41 +198,78 @@ const _channelAvoidedCharsReplacements = { | |||||||||||||||
| r'$$': '$$', | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| final _channelAvoidedCharsRegex = RegExp(r'[`>*&[\]]|\$\$'); | ||||||||||||||||
| final _channelTopicAvoidedCharsRegex = RegExp(r'[`>*&[\]]|\$\$'); | ||||||||||||||||
| final _channelTopicAvoidedCharsReplacementsRegex = | ||||||||||||||||
| RegExp(_channelTopicAvoidedCharsReplacements.values.join('|')); | ||||||||||||||||
|
|
||||||||||||||||
| /// Markdown link for channel when the channel name includes characters that | ||||||||||||||||
| String escapeChannelTopicAvoidedChars(String str) { | ||||||||||||||||
| return str.replaceAllMapped(_channelTopicAvoidedCharsRegex, | ||||||||||||||||
| (match) => _channelTopicAvoidedCharsReplacements[match[0]]!); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| String unescapeChannelTopicAvoidedChars(String str) { | ||||||||||||||||
| return str.replaceAllMapped(_channelTopicAvoidedCharsReplacementsRegex, | ||||||||||||||||
| (match) => _channelTopicAvoidedCharsReplacements.map((k, v) => MapEntry(v, k))[match[0]]!); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// Markdown link for channel or topic whose name includes characters that | ||||||||||||||||
| /// will break normal markdown rendering. | ||||||||||||||||
| /// | ||||||||||||||||
| /// Refer to [_channelAvoidedCharsReplacements] for a complete list of | ||||||||||||||||
| /// Refer to [_channelTopicAvoidedCharsReplacements] for a complete list of | ||||||||||||||||
| /// these characters. | ||||||||||||||||
| // Adopted from `topic_link_util.get_fallback_markdown_link` in Zulip web; | ||||||||||||||||
| // https://github.com/zulip/zulip/blob/b42d3e77e/web/src/topic_link_util.ts#L96-L108 | ||||||||||||||||
| String _channelFallbackMarkdownLink(ZulipStream channel, { | ||||||||||||||||
| required PerAccountStore store, | ||||||||||||||||
| String _channelTopicFallbackMarkdownLink(ZulipStream channel, PerAccountStore store, { | ||||||||||||||||
| TopicName? topic, | ||||||||||||||||
| }) { | ||||||||||||||||
| final text = StringBuffer('#${escapeChannelTopicAvoidedChars(channel.name)}'); | ||||||||||||||||
| if (topic != null) { | ||||||||||||||||
| text.write(' > ${escapeChannelTopicAvoidedChars(topic.displayName ?? store.realmEmptyTopicDisplayName)}'); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| final narrow = topic == null | ||||||||||||||||
| ? ChannelNarrow(channel.streamId) : TopicNarrow(channel.streamId, topic); | ||||||||||||||||
| // Like Zulip web, we use a relative URL here, unlike [quoteAndReply] which | ||||||||||||||||
| // uses an absolute URL. There'd be little benefit to an absolute URL here | ||||||||||||||||
| // because this isn't a likely flow when a user wants something to copy-paste | ||||||||||||||||
| // elsewhere: this flow normally produces `#**…**` syntax, which wouldn't work | ||||||||||||||||
| // for that at all. And conversely, it's nice to keep reasonably short the | ||||||||||||||||
| // markup that we put into the text box and which the user sees. Discussion: | ||||||||||||||||
| // elsewhere: this flow normally produces `#**…**` or `#**…>…**` syntax, | ||||||||||||||||
| // which wouldn't work for that at all. And conversely, it's nice to keep | ||||||||||||||||
| // reasonably short the markup that we put into the text box and which the | ||||||||||||||||
| // user sees. Discussion: | ||||||||||||||||
| // https://chat.zulip.org/#narrow/channel/101-design/topic/.22quote.20message.22.20uses.20absolute.20URL.20instead.20of.20realm-relative/near/2325588 | ||||||||||||||||
| final relativeLink = '#${narrowLinkFragment(store, ChannelNarrow(channel.streamId))}'; | ||||||||||||||||
| final relativeLink = '#${narrowLinkFragment(store, narrow)}'; | ||||||||||||||||
|
|
||||||||||||||||
| final text = '#${channel.name.replaceAllMapped(_channelAvoidedCharsRegex, | ||||||||||||||||
| (match) => _channelAvoidedCharsReplacements[match[0]]!)}'; | ||||||||||||||||
| return inlineLink(text.toString(), relativeLink); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// A #channel link syntax of a channel, like #**announce**. | ||||||||||||||||
| /// | ||||||||||||||||
| /// A plain Markdown link will be used if the channel name includes some | ||||||||||||||||
| /// characters that would break normal #**channel** rendering. | ||||||||||||||||
| String channelLink(ZulipStream channel, {required PerAccountStore store}) { | ||||||||||||||||
| if (_channelAvoidedCharsRegex.hasMatch(channel.name)) { | ||||||||||||||||
| return _channelFallbackMarkdownLink(channel, store: store); | ||||||||||||||||
| /// | ||||||||||||||||
| /// Set [isComplete] to `false` for an incomplete syntax such as "#**…>"" | ||||||||||||||||
| /// or "[#…](#…)>"; the one that will trigger a topic autocomplete | ||||||||||||||||
| /// interaction for the channel. | ||||||||||||||||
|
Comment on lines
+250
to
+252
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's an extra quotation mark in in the first example? Also I think we can name
Suggested change
(and reverse the sign when renaming |
||||||||||||||||
| String channelLink(ZulipStream channel, { | ||||||||||||||||
| bool isComplete = true, | ||||||||||||||||
| required PerAccountStore store, | ||||||||||||||||
| }) { | ||||||||||||||||
| if (_channelTopicAvoidedCharsRegex.hasMatch(channel.name)) { | ||||||||||||||||
| return '${_channelTopicFallbackMarkdownLink(channel, store)}${isComplete ? '' : '>'}'; | ||||||||||||||||
| } | ||||||||||||||||
| return '#**${channel.name}${isComplete ? '**' : '>'}'; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// A #channel>topic link syntax of a topic, like #**announce>GSoC**. | ||||||||||||||||
| /// | ||||||||||||||||
| /// A plain Markdown link will be used if the channel or topic name includes | ||||||||||||||||
| /// some characters that would break normal #**channel>topic** rendering. | ||||||||||||||||
| String topicLink(ZulipStream channel, TopicName topic, {required PerAccountStore store}) { | ||||||||||||||||
| if (_channelTopicAvoidedCharsRegex.hasMatch(channel.name) | ||||||||||||||||
| || _channelTopicAvoidedCharsRegex.hasMatch(topic.apiName)) { | ||||||||||||||||
| return _channelTopicFallbackMarkdownLink(channel, topic: topic, store); | ||||||||||||||||
| } | ||||||||||||||||
| return '#**${channel.name}**'; | ||||||||||||||||
| return '#**${channel.name}>${topic.apiName}**'; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// https://spec.commonmark.org/0.30/#inline-link | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this
unescapeChannelTopicAvoidedCharsin the later commit that makes use of it; it looks like it isn't used in this commit.