diff --git a/static/third/marked/lib/marked.js b/static/third/marked/lib/marked.js index aed224eec9..218b6caa65 100644 --- a/static/third/marked/lib/marked.js +++ b/static/third/marked/lib/marked.js @@ -445,7 +445,7 @@ var inline = { nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, - code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, + code: /^(`+)(\s*[\s\S]*?[^`]\s*)\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, emoji: noop, diff --git a/zerver/fixtures/markdown_test_cases.json b/zerver/fixtures/markdown_test_cases.json index 09cf998ef4..157692de77 100644 --- a/zerver/fixtures/markdown_test_cases.json +++ b/zerver/fixtures/markdown_test_cases.json @@ -25,6 +25,12 @@ "expected_output": "

Hamlet once said

\n
def func():\n    x = 1\n\n    y = 2\n\n    z = 3\n
\n\n\n

And all was good.

", "text_content": "Hamlet once said\ndef func():\n x = 1\n\n y = 2\n\n z = 3\n\n\n\nAnd all was good." }, + { + "name": "inline_code_spaces", + "input": "` outer ` ``` space ```", + "expected_output": "

outer space

", + "text_content": " outer space " + }, { "name": "codeblock_backticks", "input": "\n```\nfenced code\n```\n\n```inline code```\n", @@ -93,8 +99,8 @@ { "name": "dangerous_block", "input": "``` one ```\n\n``` two ```\n\n~~~~\nx = 1", - "expected_output": "

one

\n

two

\n
x = 1\n
", - "text_content": "one\ntwo\nx = 1\n" + "expected_output": "

one

\n

two

\n
x = 1\n
", + "text_content": " one \n two \nx = 1\n" }, { "name": "four_space_code_block", diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py index 84c7399bbc..3d85251f41 100644 --- a/zerver/lib/bugdown/__init__.py +++ b/zerver/lib/bugdown/__init__.py @@ -355,6 +355,24 @@ class InlineHttpsProcessor(markdown.treeprocessors.Treeprocessor): continue img.set("src", get_camo_url(url)) +class BacktickPattern(markdown.inlinepatterns.Pattern): + """ Return a `` element containing the matching text. """ + def __init__(self, pattern): + # type: (Text) -> None + markdown.inlinepatterns.Pattern.__init__(self, pattern) + self.ESCAPED_BSLASH = '%s%s%s' % (markdown.util.STX, ord('\\'), markdown.util.ETX) + self.tag = 'code' + + def handleMatch(self, m): + # type: (Match[Text]) -> Union[Text, Element] + if m.group(4): + el = markdown.util.etree.Element(self.tag) + # Modified to not strip whitespace + el.text = markdown.util.AtomicString(m.group(4)) + return el + else: + return m.group(2).replace('\\\\', self.ESCAPED_BSLASH) + class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): TWITTER_MAX_IMAGE_HEIGHT = 400 TWITTER_MAX_TO_PREVIEW = 3 @@ -1346,7 +1364,7 @@ class Bugdown(markdown.Extension): for k in ('image_link', 'image_reference', 'automail', 'autolink', 'link', 'reference', 'short_reference', 'escape', 'strong_em', 'emphasis', 'emphasis2', - 'linebreak', 'strong'): + 'linebreak', 'strong', 'backtick'): del md.inlinePatterns[k] try: # linebreak2 was removed upstream in version 3.2.1, so @@ -1357,6 +1375,12 @@ class Bugdown(markdown.Extension): md.preprocessors.add("custom_text_notifications", AlertWordsNotificationProcessor(md), "_end") + # Inline code block without whitespace stripping + md.inlinePatterns.add( + "backtick", + BacktickPattern(r'(?:(?