diff --git a/frontend_tests/node_tests/markdown.js b/frontend_tests/node_tests/markdown.js
index 9fb27268a8..9cc06e1dfb 100644
--- a/frontend_tests/node_tests/markdown.js
+++ b/frontend_tests/node_tests/markdown.js
@@ -447,6 +447,16 @@ test("marked", () => {
expected:
'
@Brother of Bobby|123
',
},
+ {
+ input: "@**|106** valid user id.",
+ expected:
+ '@Brother of Bobby|123 valid user id.
',
+ },
+ {
+ input: "@**|123|106** comes under user|id case.",
+ expected: "@**|123|106** comes under user|id case.
",
+ },
+ {input: "@**|1234** invalid id.", expected: "@**|1234** invalid id.
"},
{input: "T\n@hamletcharacters", expected: "T
\n@hamletcharacters
"},
{
input: "T\n@*hamletcharacters*",
diff --git a/static/images/help/markdown-mentions.png b/static/images/help/markdown-mentions.png
index d3a70b6ca8..ba0a35ee79 100644
Binary files a/static/images/help/markdown-mentions.png and b/static/images/help/markdown-mentions.png differ
diff --git a/static/js/markdown.js b/static/js/markdown.js
index 0b1867323d..d833f2a1bd 100644
--- a/static/js/markdown.js
+++ b/static/js/markdown.js
@@ -109,7 +109,7 @@ export function apply_markdown(message) {
let full_name;
let user_id;
- const id_regex = /(.+)\|(\d+)$/g; // For @**user|id** syntax
+ const id_regex = /^(.+)?\|(\d+)$/; // For @**user|id** and @**|id** syntax
const match = id_regex.exec(mention);
if (match) {
@@ -131,9 +131,20 @@ export function apply_markdown(message) {
full_name = match[1];
user_id = Number.parseInt(match[2], 10);
- if (!helpers.is_valid_full_name_and_user_id(full_name, user_id)) {
- user_id = undefined;
- full_name = undefined;
+ if (full_name === undefined) {
+ // For @**|id** syntax
+ if (!helpers.is_valid_user_id(user_id)) {
+ // silently ignore invalid user id.
+ user_id = undefined;
+ } else {
+ full_name = helpers.get_actual_name_from_user_id(user_id);
+ }
+ } else {
+ // For @**user|id** syntax
+ if (!helpers.is_valid_full_name_and_user_id(full_name, user_id)) {
+ user_id = undefined;
+ full_name = undefined;
+ }
}
}
diff --git a/static/js/markdown_config.js b/static/js/markdown_config.js
index 371a4f39fc..505776b3d4 100644
--- a/static/js/markdown_config.js
+++ b/static/js/markdown_config.js
@@ -31,6 +31,7 @@ export const get_helpers = () => ({
get_user_id_from_name: people.get_user_id_from_name,
is_valid_full_name_and_user_id: people.is_valid_full_name_and_user_id,
my_user_id: people.my_current_user_id,
+ is_valid_user_id: people.is_known_user_id,
// user groups
get_user_group_from_name: user_groups.get_user_group_from_name,
diff --git a/templates/zerver/help/format-your-message-using-markdown.md b/templates/zerver/help/format-your-message-using-markdown.md
index cb3021d5b7..4db72fbd0b 100644
--- a/templates/zerver/help/format-your-message-using-markdown.md
+++ b/templates/zerver/help/format-your-message-using-markdown.md
@@ -189,14 +189,17 @@ You can also [add custom emoji](/help/add-custom-emoji).
## Mentions
Learn more about mentions [here](/help/mention-a-user-or-group).
-The numbers will be added automatically by the typeahead if needed for disambiguation.
```
-Users: @**Polonius** or @**Zoe|2132** (two asterisks)
+Users: @**Polonius** or @**aaron|26** or @**|26** (two asterisks)
User group: @*support team* (one asterisk)
-Silent mention: @_**Polonius** (@_ instead of @)
+Silent mention: @_**Polonius** or @_**|26** (@_ instead of @)
```
+The variants with numbers use user IDs, and are intended for
+disambiguation (if multiple users have the same name) and bots (for
+the variant that only contains the user ID).
+

## Status Messages
diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py
index 9ab0ca4d0a..0334f4c4bf 100644
--- a/zerver/lib/markdown/__init__.py
+++ b/zerver/lib/markdown/__init__.py
@@ -1884,7 +1884,7 @@ class UserMentionPattern(markdown.inlinepatterns.InlineProcessor):
wildcard = mention.user_mention_matches_wildcard(name)
- id_syntax_match = re.match(r".+\|(?P\d+)$", name)
+ id_syntax_match = re.match(r"(.+)?\|(?P\d+)$", name)
if id_syntax_match:
id = int(id_syntax_match.group("user_id"))
user = db_data["mention_data"].get_user_by_id(id)
@@ -2445,18 +2445,25 @@ def get_possible_mentions_info(realm_id: int, mention_texts: Set[str]) -> List[F
if not mention_texts:
return []
- # Remove the trailing part of the `name|id` mention syntax,
- # thus storing only full names in full_names.
full_names = set()
- name_re = r"(?P.+)\|\d+$"
+ mention_ids: Set[int] = set()
+
+ name_re = r"(?P.+)?\|(?P\d+)$"
for mention_text in mention_texts:
name_syntax_match = re.match(name_re, mention_text)
if name_syntax_match:
- full_names.add(name_syntax_match.group("full_name"))
+ full_name = name_syntax_match.group("full_name")
+ mention_id = name_syntax_match.group("mention_id")
+ if full_name:
+ full_names.add(name_syntax_match.group("full_name"))
+ else:
+ mention_ids.add(int(mention_id))
else:
full_names.add(mention_text)
q_list = {Q(full_name__iexact=full_name) for full_name in full_names}
+ id_q_list = {Q(id=id) for id in mention_ids}
+ q_list |= id_q_list
rows = (
UserProfile.objects.filter(
diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py
index 258883c017..1bd4f59528 100644
--- a/zerver/tests/test_markdown.py
+++ b/zerver/tests/test_markdown.py
@@ -1830,6 +1830,13 @@ class MarkdownTest(ZulipTestCase):
)
self.assertEqual(msg.mentions_user_ids, {user_profile.id})
+ content = f"@**|{user_id}**"
+ self.assertEqual(
+ render_markdown(msg, content),
+ '' "@King Hamlet
",
+ )
+ self.assertEqual(msg.mentions_user_ids, {user_profile.id})
+
def test_mention_silent(self) -> None:
sender_user_profile = self.example_user("othello")
user_profile = self.example_user("hamlet")
@@ -1875,18 +1882,30 @@ class MarkdownTest(ZulipTestCase):
)
self.assertEqual(msg.mentions_user_ids, set())
+ content = f"@_**|123456789** and @_**|{user_id}**"
+ self.assertEqual(
+ render_markdown(msg, content),
+ "@_|123456789 and "
+ ''
+ "King Hamlet
",
+ )
+ self.assertEqual(msg.mentions_user_ids, set())
+
def test_possible_mentions(self) -> None:
def assert_mentions(content: str, names: Set[str], has_wildcards: bool = False) -> None:
self.assertEqual(possible_mentions(content), (names, has_wildcards))
+ aaron = self.example_user("aaron")
+
assert_mentions("", set())
assert_mentions("boring", set())
assert_mentions("@**all**", set(), True)
assert_mentions("smush@**steve**smush", set())
assert_mentions(
- "Hello @**King Hamlet** and @**Cordelia Lear**\n@**Foo van Barson|1234** @**all**",
- {"King Hamlet", "Cordelia Lear", "Foo van Barson|1234"},
+ f"Hello @**King Hamlet**, @**|{aaron.id}** and @**Cordelia Lear**\n@**Foo van Barson|1234** @**all**",
+ {"King Hamlet", f"|{aaron.id}", "Cordelia Lear", "Foo van Barson|1234"},
True,
)