diff --git a/src/Markdown.ts b/src/Markdown.ts index 709c34f31b8..0d33e58b6f0 100644 --- a/src/Markdown.ts +++ b/src/Markdown.ts @@ -112,6 +112,10 @@ const innerNodeLiteral = (node: commonmark.Node): string => { return literal; }; +const emptyItemWithNoSiblings = (node: commonmark.Node): boolean => { + return !node.prev && !node.next && !node.firstChild; +}; + /** * Class that wraps commonmark, adding the ability to see whether * a given message actually uses any markdown syntax or whether @@ -242,13 +246,30 @@ export default class Markdown { public isPlainText(): boolean { const walker = this.parsed.walker(); - let ev: commonmark.NodeWalkingStep | null; + while ((ev = walker.next())) { const node = ev.node; + if (TEXT_NODES.indexOf(node.type) > -1) { // definitely text continue; + } else if (node.type == "list" || node.type == "item") { + // Special handling for inputs like `+`, `*`, `-` and `2021.` which + // would otherwise be treated as a list of a single empty item. + // See https://github.com/vector-im/element-web/issues/7631 + if (node.type == "list" && node.firstChild && emptyItemWithNoSiblings(node.firstChild)) { + // A list with a single empty item is treated as plain text. + continue; + } + + if (node.type == "item" && emptyItemWithNoSiblings(node)) { + // An empty list item with no sibling items is treated as plain text. + continue; + } + + // Everything else is actual lists and therefore not plaintext. + return false; } else if (node.type == "html_inline" || node.type == "html_block") { // if it's an allowed html tag, we need to render it and therefore // we will need to use HTML. If it's not allowed, it's not HTML since diff --git a/test/editor/serialize-test.ts b/test/editor/serialize-test.ts index b78ae308c85..48644da1101 100644 --- a/test/editor/serialize-test.ts +++ b/test/editor/serialize-test.ts @@ -81,6 +81,29 @@ describe("editor/serialize", function () { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe("*hello* world < hey world!"); }); + + it("lists with a single empty item are not considered markdown", function () { + const pc = createPartCreator(); + + const model1 = new EditorModel([pc.plain("-")], pc); + const html1 = htmlSerializeIfNeeded(model1, {}); + expect(html1).toBe(undefined); + + const model2 = new EditorModel([pc.plain("* ")], pc); + const html2 = htmlSerializeIfNeeded(model2, {}); + expect(html2).toBe(undefined); + + const model3 = new EditorModel([pc.plain("2021.")], pc); + const html3 = htmlSerializeIfNeeded(model3, {}); + expect(html3).toBe(undefined); + }); + + it("lists with a single non-empty item are still markdown", function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("2021. foo")], pc); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe('