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:
parent
ee144ccb2b
commit
4291005161
1 changed files with 26 additions and 12 deletions
|
@ -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 oldList = state._list;
|
||||||
|
state._list = true;
|
||||||
|
|
||||||
|
let items = node.items.map((item, i) => {
|
||||||
const prefix = node.ordered ? `${node.start + i}. ` : '* ';
|
const prefix = node.ordered ? `${node.start + i}. ` : '* ';
|
||||||
return prefix + output(item, state).replace(/\n/g, `\n${' '.repeat(prefix.length)}`);
|
return prefix + output(item, state).replace(/\n/g, `\n${' '.repeat(prefix.length)}`);
|
||||||
}).join('\n')}\n`,
|
}).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);
|
||||||
|
|
Loading…
Reference in a new issue