mentions: Add "channel" as a wildcard mention.

Adds "channel" to the `stream_wildcards` frozenset for stream
wildcard notifications on the backend/server.

Updates frontend/web-app to handle "channel" as the other stream
wildcards are handled in the typeahead and composebox modules.

Updates the API version and documentation for the addition of
"channel" as a wildcard mention. But does not change any of the
functionailty of (or deprecate) the "stream" wildcard at this
point.

Part of project to rename "stream" to "channel".
This commit is contained in:
Lauryn Menard 2024-03-13 19:19:24 +01:00 committed by Tim Abbott
parent 88fd9d947b
commit e700e818e5
12 changed files with 71 additions and 11 deletions

View File

@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 9.0 ## 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** **Feature level 246**
* [`POST /register`](/api/register-queue), [`POST * [`POST /register`](/api/register-queue), [`POST

View File

@ -23,6 +23,12 @@ for syntax highlighting. This field is used in the
mentions][help-global-time] to supported Markdown message formatting mentions][help-global-time] to supported Markdown message formatting
features. 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 ## Spoilers
**Changes**: In Zulip 3.0 (feature level 15), added **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-playgrounds]: /help/code-blocks#code-playgrounds
[help-spoilers]: /help/spoilers [help-spoilers]: /help/spoilers
[help-global-time]: /help/global-times [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

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # 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 # 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 # only when going from an old version of the code to a newer version. Bump

View File

@ -411,7 +411,7 @@ export function broadcast_mentions() {
if (compose_state.get_message_type() === "private") { if (compose_state.get_message_type() === "private") {
wildcard_mention_array = ["all", "everyone"]; wildcard_mention_array = ["all", "everyone"];
} else if (compose_validate.stream_wildcard_mention_allowed()) { } 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()) { } else if (compose_validate.topic_wildcard_mention_allowed()) {
wildcard_mention_array = ["topic"]; wildcard_mention_array = ["topic"];
} }

View File

@ -191,7 +191,12 @@ function parse_with_options(
const marked_options = { const marked_options = {
...options, ...options,
userMentionHandler(mention: string, silently: boolean): string | undefined { 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 classes;
let display_text; let display_text;
if (silently) { if (silently) {

View File

@ -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 { 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 { export function _add_user(person: User): void {

View File

@ -195,10 +195,12 @@ export class CachedValue<T> {
} }
export function find_stream_wildcard_mentions(message_content: string): string | null { 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 // 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. // 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) { if (mention === null) {
return null; return null;
} }

View File

@ -65,19 +65,23 @@ run_test("verify wildcard mentions typeahead for stream message", () => {
const mention_all = ct.broadcast_mentions()[0]; const mention_all = ct.broadcast_mentions()[0];
const mention_everyone = ct.broadcast_mentions()[1]; const mention_everyone = ct.broadcast_mentions()[1];
const mention_stream = ct.broadcast_mentions()[2]; 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.email, "all");
assert.equal(mention_all.full_name, "all"); assert.equal(mention_all.full_name, "all");
assert.equal(mention_everyone.email, "everyone"); assert.equal(mention_everyone.email, "everyone");
assert.equal(mention_everyone.full_name, "everyone"); assert.equal(mention_everyone.full_name, "everyone");
assert.equal(mention_stream.email, "stream"); assert.equal(mention_stream.email, "stream");
assert.equal(mention_stream.full_name, "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.email, "topic");
assert.equal(mention_topic.full_name, "topic"); assert.equal(mention_topic.full_name, "topic");
assert.equal(mention_all.special_item_text, "all (translated: Notify stream)"); 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_everyone.special_item_text, "everyone (translated: Notify stream)");
assert.equal(mention_stream.special_item_text, "stream (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)"); assert.equal(mention_topic.special_item_text, "topic (translated: Notify topic)");
compose_validate.stream_wildcard_mention_allowed = () => false; compose_validate.stream_wildcard_mention_allowed = () => false;
@ -1788,7 +1792,7 @@ test("typeahead_results", () => {
// Verify we suggest both 'the first matching stream wildcard' and // Verify we suggest both 'the first matching stream wildcard' and
// 'topic wildcard' mentions. Not only one matching wildcard mention. // '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". // Here, we suggest both "everyone" and "topic".
assert_mentions_matches("o", [othello, mention_everyone, mention_topic, cordelia]); assert_mentions_matches("o", [othello, mention_everyone, mention_topic, cordelia]);

View File

@ -781,6 +781,17 @@ test("message_flags", () => {
assert.equal(message.flags.includes("topic_wildcard_mentioned"), false); assert.equal(message.flags.includes("topic_wildcard_mentioned"), false);
assert.equal(message.flags.includes("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**"; input = "test @**topic**";
message = {topic: "No links here", raw_content: input}; message = {topic: "No links here", raw_content: input};
message = { message = {

View File

@ -602,7 +602,7 @@ test("sort broadcast mentions for stream message type", () => {
assert.deepEqual( assert.deepEqual(
results.map((r) => r.email), results.map((r) => r.email),
["all", "everyone", "stream", "topic"], ["all", "everyone", "stream", "channel", "topic"],
); );
// Reverse the list to test actual sorting // Reverse the list to test actual sorting
@ -616,7 +616,7 @@ test("sort broadcast mentions for stream message type", () => {
assert.deepEqual( assert.deepEqual(
results2.map((r) => r.email), results2.map((r) => r.email),
["all", "everyone", "stream", "topic", a_user.email, zman.email], ["all", "everyone", "stream", "channel", "topic", a_user.email, zman.email],
); );
}); });

View File

@ -184,6 +184,13 @@ run_test("wildcard_mentions_regexp", () => {
"some text before only @**stream**", "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 = [ const messages_with_topic_mentions = [
"@**topic**", "@**topic**",
"some text before @**topic** some text after", "some text before @**topic** some text after",
@ -218,6 +225,15 @@ run_test("wildcard_mentions_regexp", () => {
"some_email@**stream**.com", "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; let i;
for (i = 0; i < messages_with_all_mentions.length; i += 1) { for (i = 0; i < messages_with_all_mentions.length; i += 1) {
assert.ok(util.find_stream_wildcard_mentions(messages_with_all_mentions[i])); 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])); 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) { for (i = 0; i < messages_with_topic_mentions.length; i += 1) {
assert.ok(!util.find_stream_wildcard_mentions(messages_with_topic_mentions[i])); 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) { for (i = 0; i < messages_without_stream_mentions.length; i += 1) {
assert.ok(!util.find_stream_wildcard_mentions(messages_without_stream_mentions[i])); 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", () => { run_test("move_array_elements_to_front", () => {

View File

@ -22,7 +22,7 @@ USER_GROUP_MENTIONS_RE = re.compile(
) )
topic_wildcards = frozenset(["topic"]) topic_wildcards = frozenset(["topic"])
stream_wildcards = frozenset(["all", "everyone", "stream"]) stream_wildcards = frozenset(["all", "everyone", "stream", "channel"])
@dataclass @dataclass