diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 3821d33669..5885c24161 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 9.0 +**Feature level 247** + +* [Markdown message formatting](/api/message-formatting#mentions): + Added `channel` to the supported options for [wildcard + mentions](/help/mention-a-user-or-group#mention-everyone-on-a-stream). + **Feature level 246** * [`POST /register`](/api/register-queue), [`POST diff --git a/api_docs/message-formatting.md b/api_docs/message-formatting.md index edd879516f..7be9da5099 100644 --- a/api_docs/message-formatting.md +++ b/api_docs/message-formatting.md @@ -23,6 +23,12 @@ for syntax highlighting. This field is used in the mentions][help-global-time] to supported Markdown message formatting features. +## Mentions + +**Changes**: In Zulip 9.0 (feature level 247), `channel` was added +to the supported [wildcard][help-mention-all] options used in the +[mentions][help-mentions] Markdown message formatting feature. + ## Spoilers **Changes**: In Zulip 3.0 (feature level 15), added @@ -45,3 +51,5 @@ inconsistent syntax, were removed. [help-playgrounds]: /help/code-blocks#code-playgrounds [help-spoilers]: /help/spoilers [help-global-time]: /help/global-times +[help-mentions]: /help/mention-a-user-or-group +[help-mention-all]: /help/mention-a-user-or-group#mention-everyone-on-a-stream diff --git a/version.py b/version.py index ac54b9b59d..af795ed792 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # Changes should be accompanied by documentation explaining what the # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 246 +API_FEATURE_LEVEL = 247 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/web/src/composebox_typeahead.js b/web/src/composebox_typeahead.js index a16886b975..5a37d325ed 100644 --- a/web/src/composebox_typeahead.js +++ b/web/src/composebox_typeahead.js @@ -411,7 +411,7 @@ export function broadcast_mentions() { if (compose_state.get_message_type() === "private") { wildcard_mention_array = ["all", "everyone"]; } else if (compose_validate.stream_wildcard_mention_allowed()) { - wildcard_mention_array = ["all", "everyone", "stream", "topic"]; + wildcard_mention_array = ["all", "everyone", "stream", "channel", "topic"]; } else if (compose_validate.topic_wildcard_mention_allowed()) { wildcard_mention_array = ["topic"]; } diff --git a/web/src/markdown.ts b/web/src/markdown.ts index 50d4d29550..3634034e11 100644 --- a/web/src/markdown.ts +++ b/web/src/markdown.ts @@ -191,7 +191,12 @@ function parse_with_options( const marked_options = { ...options, userMentionHandler(mention: string, silently: boolean): string | undefined { - if (mention === "all" || mention === "everyone" || mention === "stream") { + if ( + mention === "all" || + mention === "everyone" || + mention === "stream" || + mention === "channel" + ) { let classes; let display_text; if (silently) { diff --git a/web/src/people.ts b/web/src/people.ts index ff7b4006f8..1dd8b32fd3 100644 --- a/web/src/people.ts +++ b/web/src/people.ts @@ -1373,7 +1373,7 @@ export function get_mention_syntax(full_name: string, user_id?: number, silent = } function full_name_matches_wildcard_mention(full_name: string): boolean { - return ["all", "everyone", "stream", "topic"].includes(full_name); + return ["all", "everyone", "stream", "channel", "topic"].includes(full_name); } export function _add_user(person: User): void { diff --git a/web/src/util.ts b/web/src/util.ts index 8a3655b9b7..23d4f093b4 100644 --- a/web/src/util.ts +++ b/web/src/util.ts @@ -195,10 +195,12 @@ export class CachedValue { } export function find_stream_wildcard_mentions(message_content: string): string | null { - // We cannot use the exact same regex as the server side users (in zerver/lib/mention.py) + // We cannot use the exact same regex as the server side uses (in zerver/lib/mention.py) // because Safari < 16.4 does not support look-behind assertions. Reframe the lookbehind of a // negative character class as a start-of-string or positive character class. - const mention = message_content.match(/(?:^|[\s"'(/<[{])(@\*{2}(all|everyone|stream)\*{2})/); + const mention = message_content.match( + /(?:^|[\s"'(/<[{])(@\*{2}(all|everyone|stream|channel)\*{2})/, + ); if (mention === null) { return null; } diff --git a/web/tests/composebox_typeahead.test.js b/web/tests/composebox_typeahead.test.js index aa3413424a..29c47a30d2 100644 --- a/web/tests/composebox_typeahead.test.js +++ b/web/tests/composebox_typeahead.test.js @@ -65,19 +65,23 @@ run_test("verify wildcard mentions typeahead for stream message", () => { const mention_all = ct.broadcast_mentions()[0]; const mention_everyone = ct.broadcast_mentions()[1]; const mention_stream = ct.broadcast_mentions()[2]; - const mention_topic = ct.broadcast_mentions()[3]; + const mention_channel = ct.broadcast_mentions()[3]; + const mention_topic = ct.broadcast_mentions()[4]; assert.equal(mention_all.email, "all"); assert.equal(mention_all.full_name, "all"); assert.equal(mention_everyone.email, "everyone"); assert.equal(mention_everyone.full_name, "everyone"); assert.equal(mention_stream.email, "stream"); assert.equal(mention_stream.full_name, "stream"); + assert.equal(mention_channel.email, "channel"); + assert.equal(mention_channel.full_name, "channel"); assert.equal(mention_topic.email, "topic"); assert.equal(mention_topic.full_name, "topic"); assert.equal(mention_all.special_item_text, "all (translated: Notify stream)"); assert.equal(mention_everyone.special_item_text, "everyone (translated: Notify stream)"); assert.equal(mention_stream.special_item_text, "stream (translated: Notify stream)"); + assert.equal(mention_channel.special_item_text, "channel (translated: Notify stream)"); assert.equal(mention_topic.special_item_text, "topic (translated: Notify topic)"); compose_validate.stream_wildcard_mention_allowed = () => false; @@ -1788,7 +1792,7 @@ test("typeahead_results", () => { // Verify we suggest both 'the first matching stream wildcard' and // 'topic wildcard' mentions. Not only one matching wildcard mention. - const mention_topic = ct.broadcast_mentions()[3]; + const mention_topic = ct.broadcast_mentions()[4]; // Here, we suggest both "everyone" and "topic". assert_mentions_matches("o", [othello, mention_everyone, mention_topic, cordelia]); diff --git a/web/tests/markdown.test.js b/web/tests/markdown.test.js index c1a5c57de3..787bd51499 100644 --- a/web/tests/markdown.test.js +++ b/web/tests/markdown.test.js @@ -781,6 +781,17 @@ test("message_flags", () => { assert.equal(message.flags.includes("topic_wildcard_mentioned"), false); assert.equal(message.flags.includes("mentioned"), false); + input = "test @**channel**"; + message = {topic: "No links here", raw_content: input}; + message = { + ...message, + ...markdown.render(message.raw_content), + }; + assert.equal(message.is_me_message, false); + assert.equal(message.flags.includes("stream_wildcard_mentioned"), true); + assert.equal(message.flags.includes("topic_wildcard_mentioned"), false); + assert.equal(message.flags.includes("mentioned"), false); + input = "test @**topic**"; message = {topic: "No links here", raw_content: input}; message = { diff --git a/web/tests/typeahead_helper.test.js b/web/tests/typeahead_helper.test.js index 1c88bb3670..028d0dc5ee 100644 --- a/web/tests/typeahead_helper.test.js +++ b/web/tests/typeahead_helper.test.js @@ -602,7 +602,7 @@ test("sort broadcast mentions for stream message type", () => { assert.deepEqual( results.map((r) => r.email), - ["all", "everyone", "stream", "topic"], + ["all", "everyone", "stream", "channel", "topic"], ); // Reverse the list to test actual sorting @@ -616,7 +616,7 @@ test("sort broadcast mentions for stream message type", () => { assert.deepEqual( results2.map((r) => r.email), - ["all", "everyone", "stream", "topic", a_user.email, zman.email], + ["all", "everyone", "stream", "channel", "topic", a_user.email, zman.email], ); }); diff --git a/web/tests/util.test.js b/web/tests/util.test.js index b5251385b6..f0921a0cef 100644 --- a/web/tests/util.test.js +++ b/web/tests/util.test.js @@ -184,6 +184,13 @@ run_test("wildcard_mentions_regexp", () => { "some text before only @**stream**", ]; + const messages_with_channel_mentions = [ + "@**channel**", + "some text before @**channel** some text after", + "@**channel** some text after only", + "some text before only @**channel**", + ]; + const messages_with_topic_mentions = [ "@**topic**", "some text before @**topic** some text after", @@ -218,6 +225,15 @@ run_test("wildcard_mentions_regexp", () => { "some_email@**stream**.com", ]; + const messages_without_channel_mentions = [ + "some text before @channel some text after", + "@channel", + "`@channel`", + "some_email@channel.com", + "`@**channel**`", + "some_email@**channel**.com", + ]; + let i; for (i = 0; i < messages_with_all_mentions.length; i += 1) { assert.ok(util.find_stream_wildcard_mentions(messages_with_all_mentions[i])); @@ -231,6 +247,10 @@ run_test("wildcard_mentions_regexp", () => { assert.ok(util.find_stream_wildcard_mentions(messages_with_stream_mentions[i])); } + for (i = 0; i < messages_with_channel_mentions.length; i += 1) { + assert.ok(util.find_stream_wildcard_mentions(messages_with_channel_mentions[i])); + } + for (i = 0; i < messages_with_topic_mentions.length; i += 1) { assert.ok(!util.find_stream_wildcard_mentions(messages_with_topic_mentions[i])); } @@ -246,6 +266,10 @@ run_test("wildcard_mentions_regexp", () => { for (i = 0; i < messages_without_stream_mentions.length; i += 1) { assert.ok(!util.find_stream_wildcard_mentions(messages_without_stream_mentions[i])); } + + for (i = 0; i < messages_without_channel_mentions.length; i += 1) { + assert.ok(!util.find_stream_wildcard_mentions(messages_without_channel_mentions[i])); + } }); run_test("move_array_elements_to_front", () => { diff --git a/zerver/lib/mention.py b/zerver/lib/mention.py index 18d0ded238..f15cb52d8a 100644 --- a/zerver/lib/mention.py +++ b/zerver/lib/mention.py @@ -22,7 +22,7 @@ USER_GROUP_MENTIONS_RE = re.compile( ) topic_wildcards = frozenset(["topic"]) -stream_wildcards = frozenset(["all", "everyone", "stream"]) +stream_wildcards = frozenset(["all", "everyone", "stream", "channel"]) @dataclass