mirror of https://github.com/zulip/zulip.git
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:
parent
cba7425cdc
commit
620e9cbf72
|
@ -55,22 +55,17 @@ export function wrap_code(code, lang) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap_quote(text) {
|
function wrap_quote(text) {
|
||||||
const paragraphs = text.split("\n\n");
|
const paragraphs = text.split("\n");
|
||||||
const quoted_paragraphs = [];
|
const quoted_paragraphs = [];
|
||||||
|
|
||||||
// Prefix each quoted paragraph with > at the
|
// Prefix each quoted paragraph with > at the
|
||||||
// beginning of each line
|
// beginning of each line
|
||||||
for (const paragraph of paragraphs) {
|
for (const paragraph of paragraphs) {
|
||||||
const lines = paragraph.split("\n");
|
const lines = paragraph.split("\n");
|
||||||
quoted_paragraphs.push(
|
quoted_paragraphs.push(lines.map((line) => "> " + line).join("\n"));
|
||||||
lines
|
|
||||||
.filter((line) => line !== "")
|
|
||||||
.map((line) => "> " + line)
|
|
||||||
.join("\n"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return quoted_paragraphs.join("\n\n");
|
return quoted_paragraphs.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap_tex(tex) {
|
function wrap_tex(tex) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ var block = {
|
||||||
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
||||||
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
|
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
|
||||||
nptable: noop,
|
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*$)/,
|
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
|
||||||
html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
|
html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
|
||||||
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
||||||
|
|
|
@ -1546,6 +1546,34 @@ class BlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor):
|
||||||
r'[ ]{0,3}>[ ]?(.*)')
|
r'[ ]{0,3}>[ ]?(.*)')
|
||||||
mention_re = re.compile(mention.find_mentions)
|
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:
|
def clean(self, line: str) -> str:
|
||||||
# Silence all the mentions inside blockquotes
|
# Silence all the mentions inside blockquotes
|
||||||
line = re.sub(self.mention_re, lambda m: "@_{}".format(m.group('match')), line)
|
line = re.sub(self.mention_re, lambda m: "@_{}".format(m.group('match')), line)
|
||||||
|
|
|
@ -427,12 +427,12 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
return code
|
return code
|
||||||
|
|
||||||
def format_quote(self, text: str) -> str:
|
def format_quote(self, text: str) -> str:
|
||||||
paragraphs = text.split("\n\n")
|
paragraphs = text.split("\n")
|
||||||
quoted_paragraphs = []
|
quoted_paragraphs = []
|
||||||
for paragraph in paragraphs:
|
for paragraph in paragraphs:
|
||||||
lines = paragraph.split("\n")
|
lines = paragraph.split("\n")
|
||||||
quoted_paragraphs.append("\n".join("> " + line for line in lines if line != ''))
|
quoted_paragraphs.append("\n".join("> " + line for line in lines))
|
||||||
return "\n\n".join(quoted_paragraphs)
|
return "\n".join(quoted_paragraphs)
|
||||||
|
|
||||||
def format_spoiler(self, header: str, text: str) -> str:
|
def format_spoiler(self, header: str, text: str) -> str:
|
||||||
output = []
|
output = []
|
||||||
|
|
|
@ -111,6 +111,22 @@
|
||||||
"input": ">\n>\ntext",
|
"input": ">\n>\ntext",
|
||||||
"expected_output": "<blockquote>\n<p>text</p>\n</blockquote>"
|
"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",
|
"name": "fenced_quote_with_hashtag",
|
||||||
"input": "```quote\n# line 1\n#line 2\n```",
|
"input": "```quote\n# line 1\n#line 2\n```",
|
||||||
|
|
|
@ -87,7 +87,8 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
|
||||||
'',
|
'',
|
||||||
'> hi',
|
'> hi',
|
||||||
'> bye',
|
'> bye',
|
||||||
'',
|
'> ',
|
||||||
|
'> ',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
]
|
]
|
||||||
|
@ -113,7 +114,8 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'> bye',
|
'> bye',
|
||||||
'',
|
'> ',
|
||||||
|
'> ',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
]
|
]
|
||||||
|
@ -177,9 +179,11 @@ class FencedBlockPreprocessorTest(ZulipTestCase):
|
||||||
expected = [
|
expected = [
|
||||||
'',
|
'',
|
||||||
'> hi',
|
'> hi',
|
||||||
'',
|
'> ',
|
||||||
'> **py:hello()**',
|
'> **py:hello()**',
|
||||||
'',
|
'> ',
|
||||||
|
'> ',
|
||||||
|
'> ',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue