Handle nested lists (#853)

* Handle nested lists

* Allow heading to not be followed by an empty line

* Don't parse as inline code if contains newlines

* Use escape rule in plain as well
This commit is contained in:
ginnyTheCat 2022-09-25 16:01:59 +02:00 committed by GitHub
parent ee144ccb2b
commit 4291005161
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
import SimpleMarkdown from '@khanacademy/simple-markdown'; import SimpleMarkdown from '@khanacademy/simple-markdown';
import { idRegex, parseIdUri } from './common'; import { idRegex, parseIdUri } from './common';
@ -88,6 +89,10 @@ const plainRules = {
plain: (node, output, state) => `${output(node.content, state)}\n\n`, plain: (node, output, state) => `${output(node.content, state)}\n\n`,
html: (node, output, state) => htmlTag('p', output(node.content, state)), html: (node, output, state) => htmlTag('p', output(node.content, state)),
}, },
escape: {
...defaultRules.escape,
plain: (node, output, state) => `\\${output(node.content, state)}`,
},
br: { br: {
...defaultRules.br, ...defaultRules.br,
match: anyScopeRegex(/^ *\n/), match: anyScopeRegex(/^ *\n/),
@ -107,7 +112,7 @@ const markdownRules = {
...plainRules, ...plainRules,
heading: { heading: {
...defaultRules.heading, ...defaultRules.heading,
match: blockRegex(/^ *(#{1,6})([^\n:]*?(?: [^\n]*?)?)#* *(?:\n *)+\n/), match: blockRegex(/^ *(#{1,6})([^\n:]*?(?: [^\n]*?)?)#* *(?:\n *)*\n/),
plain: (node, output, state) => { plain: (node, output, state) => {
const out = output(node.content, state); const out = output(node.content, state);
if (state.kind === 'edit' || state.kind === 'notification' || node.level > 2) { if (state.kind === 'edit' || state.kind === 'notification' || node.level > 2) {
@ -122,7 +127,7 @@ const markdownRules = {
}, },
codeBlock: { codeBlock: {
...defaultRules.codeBlock, ...defaultRules.codeBlock,
plain: (node) => `\`\`\`${node.lang || ''}\n${node.content}\n\`\`\``, plain: (node) => `\`\`\`${node.lang || ''}\n${node.content}\n\`\`\`\n`,
html: (node) => htmlTag('pre', htmlTag('code', sanitizeText(node.content), { html: (node) => htmlTag('pre', htmlTag('code', sanitizeText(node.content), {
class: node.lang ? `language-${node.lang}` : undefined, class: node.lang ? `language-${node.lang}` : undefined,
})), })),
@ -137,10 +142,22 @@ const markdownRules = {
}, },
list: { list: {
...defaultRules.list, ...defaultRules.list,
plain: (node, output, state) => `${node.items.map((item, i) => { plain: (node, output, state) => {
const prefix = node.ordered ? `${node.start + i}. ` : '* '; const oldList = state._list;
return prefix + output(item, state).replace(/\n/g, `\n${' '.repeat(prefix.length)}`); state._list = true;
}).join('\n')}\n`,
let items = node.items.map((item, i) => {
const prefix = node.ordered ? `${node.start + i}. ` : '* ';
return prefix + output(item, state).replace(/\n/g, `\n${' '.repeat(prefix.length)}`);
}).join('\n');
state._list = oldList;
if (!state._list) {
items += '\n\n';
}
return items;
},
}, },
def: undefined, def: undefined,
table: { table: {
@ -219,10 +236,6 @@ const markdownRules = {
match: inlineRegex(/^¯\\_\(ツ\)_\/¯/), match: inlineRegex(/^¯\\_\(ツ\)_\/¯/),
parse: (capture) => ({ type: 'text', content: capture[0] }), parse: (capture) => ({ type: 'text', content: capture[0] }),
}, },
escape: {
...defaultRules.escape,
plain: (node, output, state) => `\\${output(node.content, state)}`,
},
tableSeparator: { tableSeparator: {
...defaultRules.tableSeparator, ...defaultRules.tableSeparator,
plain: () => ' | ', plain: () => ' | ',
@ -278,6 +291,7 @@ const markdownRules = {
}, },
inlineCode: { inlineCode: {
...defaultRules.inlineCode, ...defaultRules.inlineCode,
match: inlineRegex(/^(`+)([^\n]*?[^`\n])\1(?!`)/),
plain: (node) => `\`${node.content}\``, plain: (node) => `\`${node.content}\``,
}, },
spoiler: { spoiler: {
@ -349,13 +363,13 @@ function mapElement(el) {
case 'BLOCKQUOTE': case 'BLOCKQUOTE':
return [{ type: 'blockQuote', content: mapChildren(el) }]; return [{ type: 'blockQuote', content: mapChildren(el) }];
case 'UL': case 'UL':
return [{ type: 'list', items: mapChildren(el) }]; return [{ type: 'list', items: Array.from(el.childNodes).map(mapNode) }];
case 'OL': case 'OL':
return [{ return [{
type: 'list', type: 'list',
ordered: true, ordered: true,
start: Number(el.getAttribute('start')), start: Number(el.getAttribute('start')),
items: mapChildren(el), items: Array.from(el.childNodes).map(mapNode),
}]; }];
case 'TABLE': { case 'TABLE': {
const headerEl = Array.from(el.querySelector('thead > tr').childNodes); const headerEl = Array.from(el.querySelector('thead > tr').childNodes);