markdown: Extend user mention syntax to support user_id for mentioning.

Extend our markdown system to support mentioning of users
by id also. Following these changes, it would be possible
to mention users with @**|user_id** and silently mention
using @_**|user_id**.

Main intention for extending the mention syntax is to make
it convenient for bots to mention a users using their ids. It
is to be noted that previous syntax are also supported.

Documentation tweaked by tabbott for better readability.

The changes were tested manually in development server, and also
by adding some new backend and frontend tests.

Fixes: #17487.
This commit is contained in:
m-e-l-u-h-a-n
2021-03-24 00:52:04 +05:30
committed by Tim Abbott
parent 9c6d8d9d81
commit 2699048208
7 changed files with 65 additions and 14 deletions

View File

@@ -447,6 +447,16 @@ test("marked", () => {
expected:
'<p><span class="user-mention" data-user-id="106">@Brother of Bobby|123</span></p>',
},
{
input: "@**|106** valid user id.",
expected:
'<p><span class="user-mention" data-user-id="106">@Brother of Bobby|123</span> valid user id.</p>',
},
{
input: "@**|123|106** comes under user|id case.",
expected: "<p>@**|123|106** comes under user|id case.</p>",
},
{input: "@**|1234** invalid id.", expected: "<p>@**|1234** invalid id.</p>"},
{input: "T\n@hamletcharacters", expected: "<p>T<br>\n@hamletcharacters</p>"},
{
input: "T\n@*hamletcharacters*",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -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;
}
}
}

View File

@@ -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,

View File

@@ -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).
![Markdown mentions](/static/images/help/markdown-mentions.png)
## Status Messages

View File

@@ -1884,7 +1884,7 @@ class UserMentionPattern(markdown.inlinepatterns.InlineProcessor):
wildcard = mention.user_mention_matches_wildcard(name)
id_syntax_match = re.match(r".+\|(?P<user_id>\d+)$", name)
id_syntax_match = re.match(r"(.+)?\|(?P<user_id>\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<full_name>.+)\|\d+$"
mention_ids: Set[int] = set()
name_re = r"(?P<full_name>.+)?\|(?P<mention_id>\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(

View File

@@ -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),
'<p><span class="user-mention" ' f'data-user-id="{user_id}">' "@King Hamlet</span></p>",
)
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),
"<p>@_<strong>|123456789</strong> and "
'<span class="user-mention silent" '
f'data-user-id="{user_id}">'
"King Hamlet</span></p>",
)
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,
)