markdown: Fix merging of separate quotations.

Initally, when writing two or more quotes, having
a blank line in between them, merges those quotes.
This created confusion especially in "quote and reply".

This commit fixes such issues. Now two or more quotes
having a blank line in between them, will not get merged.

This change is correct both for usability and for improving our
compatibility with CommonMark.

Fixes #14379.
This commit is contained in:
akshatdalton 2020-10-30 19:10:29 +00:00 committed by Tim Abbott
parent cba7425cdc
commit 620e9cbf72
6 changed files with 59 additions and 16 deletions

View File

@ -55,22 +55,17 @@ export function wrap_code(code, lang) {
}
function wrap_quote(text) {
const paragraphs = text.split("\n\n");
const paragraphs = text.split("\n");
const quoted_paragraphs = [];
// Prefix each quoted paragraph with > at the
// beginning of each line
for (const paragraph of paragraphs) {
const lines = paragraph.split("\n");
quoted_paragraphs.push(
lines
.filter((line) => line !== "")
.map((line) => "> " + line)
.join("\n"),
);
quoted_paragraphs.push(lines.map((line) => "> " + line).join("\n"));
}
return quoted_paragraphs.join("\n\n");
return quoted_paragraphs.join("\n");
}
function wrap_tex(tex) {

View File

@ -17,7 +17,7 @@ var block = {
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
nptable: noop,
blockquote: /^(?!( *>\s*($|\n))*($|\n))( *>[^\n]*(\n(?!def)[^\n]+)*\n*)+/,
blockquote: /^(?!( *>\s*($|\n))*($|\n))( *>[^\n]*(\n(?!def)[^\n]+)*)+/,
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,

View File

@ -1546,6 +1546,34 @@ class BlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor):
r'[ ]{0,3}>[ ]?(.*)')
mention_re = re.compile(mention.find_mentions)
# run() is very slightly forked from the base class; see notes below.
def run(self, parent: Element, blocks: List[Any]) -> None:
block = blocks.pop(0)
m = self.RE.search(block)
if m:
before = block[:m.start()] # Lines before blockquote
# Pass lines before blockquote in recursively for parsing forst.
self.parser.parseBlocks(parent, [before])
# Remove ``> `` from beginning of each line.
block = '\n'.join(
[self.clean(line) for line in block[m.start():].split('\n')]
)
# Zulip modification: The next line is patched to match
# CommonMark rather than original Markdown. In original
# Markdown, blockquotes with a blank line between them were
# merged, which makes it impossible to break a blockquote with
# a blank line intentionally.
#
# This is a new blockquote. Create a new parent element.
quote = etree.SubElement(parent, 'blockquote')
# Recursively parse block with blockquote as parent.
# change parser state so blockquotes embedded in lists use p tags
self.parser.state.set('blockquote')
self.parser.parseChunk(quote, block)
self.parser.state.reset()
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)

View File

@ -427,12 +427,12 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
return code
def format_quote(self, text: str) -> str:
paragraphs = text.split("\n\n")
paragraphs = text.split("\n")
quoted_paragraphs = []
for paragraph in paragraphs:
lines = paragraph.split("\n")
quoted_paragraphs.append("\n".join("> " + line for line in lines if line != ''))
return "\n\n".join(quoted_paragraphs)
quoted_paragraphs.append("\n".join("> " + line for line in lines))
return "\n".join(quoted_paragraphs)
def format_spoiler(self, header: str, text: str) -> str:
output = []

View File

@ -111,6 +111,22 @@
"input": ">\n>\ntext",
"expected_output": "<blockquote>\n<p>text</p>\n</blockquote>"
},
{
"name": "multiple_blockquote",
"input": "> Hello World!\n\n> Hello Universe!",
"expected_output": "<blockquote>\n<p>Hello World!</p>\n</blockquote>\n<blockquote>\n<p>Hello Universe!</p>\n</blockquote>"
},
{
"name": "multiple_blockquote",
"input": "```quote\nText in\nmultiple lines.\n```\n\n> Text in a single line.",
"expected_output": "<blockquote>\n<p>Text in<br>\nmultiple lines.</p>\n</blockquote>\n<blockquote>\n<p>Text in a single line.</p>\n</blockquote>"
},
{
"name": "multiple_blockquote_with_quote_and_reply",
"input": "Hamlet said:\n~~~quote\nCan we talk ?\n~~~\n> Yes !!\n\nWelcome to Open Source.",
"expected_output": "<p>Hamlet said:</p>\n<blockquote>\n<p>Can we talk ?</p>\n</blockquote>\n<blockquote>\n<p>Yes !!</p>\n</blockquote>\n<p>Welcome to Open Source.</p>",
"text_content": "Hamlet said:\n> Can we talk ?\n\n> Yes !!\n\nWelcome to Open Source."
},
{
"name": "fenced_quote_with_hashtag",
"input": "```quote\n# line 1\n#line 2\n```",

View File

@ -87,7 +87,8 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
'',
'> hi',
'> bye',
'',
'> ',
'> ',
'',
'',
]
@ -113,7 +114,8 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
'',
'',
'> bye',
'',
'> ',
'> ',
'',
'',
]
@ -177,9 +179,11 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
expected = [
'',
'> hi',
'',
'> ',
'> **py:hello()**',
'',
'> ',
'> ',
'> ',
'',
'',
]