diff --git a/frontend_tests/node_tests/markdown.js b/frontend_tests/node_tests/markdown.js index 53ee4e7849..8484e2f5cd 100644 --- a/frontend_tests/node_tests/markdown.js +++ b/frontend_tests/node_tests/markdown.js @@ -304,6 +304,8 @@ run_test('marked', () => { expected: '
\u{1f6b2}
' }, {input: 'Silent mention: _@**Cordelia Lear**', expected: 'Silent mention: @Cordelia Lear
'}, + {input: '> Mention in quote: @**Cordelia Lear**\n\nMention outside quote: @**Cordelia Lear**', + expected: '\n\nMention in quote: @Cordelia Lear
\n
Mention outside quote: @Cordelia Lear
'}, // Test only those realm filters which don't return True for // `contains_backend_only_syntax()`. Those which return True // are tested separately. diff --git a/static/js/markdown.js b/static/js/markdown.js index 444dc810bf..4aa47da4dc 100644 --- a/static/js/markdown.js +++ b/static/js/markdown.js @@ -105,6 +105,20 @@ exports.apply_markdown = function (message) { } return; }, + silencedMentionHandler: function (quote) { + // Silence quoted mentions. + var user_mention_re = /\n' + quote + '\n'; }; diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py index 6c085db199..4045317491 100644 --- a/zerver/lib/bugdown/__init__.py +++ b/zerver/lib/bugdown/__init__.py @@ -1320,6 +1320,24 @@ class ListIndentProcessor(markdown.blockprocessors.ListIndentProcessor): super().__init__(parser) parser.markdown.tab_length = 4 +class BlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor): + """ Process BlockQuotes. + + Based on markdown.blockprocessors.BlockQuoteProcessor, but with 2-space indent + """ + + # Original regex for blockquote is RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)') + RE = re.compile(r'(^|\n)(?!(?:[ ]{0,3}>\s*(?:$|\n))*(?:$|\n))' + r'[ ]{0,3}>[ ]?(.*)') + mention_re = re.compile(mention.find_mentions) + + def clean(self, line: str) -> str: + # Silence all the mentions inside blockquotes + line = re.sub(self.mention_re, lambda m: "_@{}".format(m.group('match')), line) + + # And then run the upstream processor's code for removing the '>' + return super().clean(line) + class BugdownUListPreprocessor(markdown.preprocessors.Preprocessor): """ Allows unordered list blocks that come directly after a paragraph to be rendered as an unordered list @@ -1715,16 +1733,12 @@ class Bugdown(markdown.Extension): '>strong') def extend_block_formatting(self, md: markdown.Markdown) -> None: - for k in ('hashheader', 'setextheader', 'olist', 'ulist', 'indent'): + for k in ('hashheader', 'setextheader', 'olist', 'ulist', 'indent', 'quote'): del md.parser.blockprocessors[k] md.parser.blockprocessors.add('ulist', UListProcessor(md.parser), '>hr') md.parser.blockprocessors.add('indent', ListIndentProcessor(md.parser), '
\n\n' + '' + '@King Hamlet' + ' and ' + '@Othello, the Moor of Venice' + '
\n
' + '@King Hamlet' + ' and ' + '@Cordelia Lear' + '
' % (hamlet.id, othello.id, hamlet.id, cordelia.id)) + self.assertEqual(msg.mentions_user_ids, set([hamlet.id, cordelia.id])) + + # Both fenced quote and > quote should be identical + expected = ('\n' % (hamlet.id)) + content = "```quote\n@**King Hamlet**\n```" + self.assertEqual(render_markdown(msg, content), expected) + self.assertEqual(msg.mentions_user_ids, set()) + content = "> @**King Hamlet**" + self.assertEqual(render_markdown(msg, content), expected) + self.assertEqual(msg.mentions_user_ids, set()) + def test_mention_duplicate_full_name(self) -> None: realm = get_realm('zulip')' + '@King Hamlet' + '
\n