diff --git a/src/node/markdown/plugins/restoreEntities.ts b/src/node/markdown/plugins/restoreEntities.ts index 5198bea3..d5bb80ed 100644 --- a/src/node/markdown/plugins/restoreEntities.ts +++ b/src/node/markdown/plugins/restoreEntities.ts @@ -1,11 +1,53 @@ import type MarkdownIt from 'markdown-it' +import type StateCore from 'markdown-it/lib/rules_core/state_core.mjs' +import type Token from 'markdown-it/lib/token.mjs' export function restoreEntities(md: MarkdownIt): void { - md.core.ruler.disable('text_join') - md.renderer.rules.text_special = (tokens, idx) => { - if (tokens[idx].info === 'entity') { - return tokens[idx].markup // leave as is so Vue can handle it - } - return md.utils.escapeHtml(tokens[idx].content) + md.core.ruler.at('text_join', text_join) + md.renderer.rules.text = (tokens, idx) => escapeHtml(tokens[idx].content) +} + +function text_join(state: StateCore): void { + let curr, last + const blockTokens = state.tokens + const l = blockTokens.length + + for (let j = 0; j < l; ++j) { + if (blockTokens[j].type !== 'inline') continue + + const tokens = blockTokens[j].children || [] + const max = tokens.length + + for (curr = 0; curr < max; ++curr) + if (tokens[curr].type === 'text_special') tokens[curr].type = 'text' + + for (curr = last = 0; curr < max; ++curr) + if ( + tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text' + ) { + tokens[curr + 1].content = + getContent(tokens[curr]) + getContent(tokens[curr + 1]) + tokens[curr + 1].info = '' + tokens[curr + 1].markup = '' + } else { + if (curr !== last) tokens[last] = tokens[curr] + ++last + } + + if (curr !== last) tokens.length = last } } + +function getContent(token: Token): string { + return token.info === 'entity' + ? token.markup + : token.info === 'escape' && token.content === '&' + ? '&' + : token.content +} + +function escapeHtml(str: string): string { + return str.replace(//g, '>').replace(/"/g, '"') +}