diff --git a/src/renderer/plugins/markdown-html.ts b/src/renderer/plugins/markdown-html.ts index 74e0c5c5..d5456c09 100644 --- a/src/renderer/plugins/markdown-html.ts +++ b/src/renderer/plugins/markdown-html.ts @@ -1,7 +1,9 @@ import { Plugin } from '@fe/useful/plugin' -import MarkdownIt from 'markdown-it' +import StateBlock from 'markdown-it/lib/rules_block/state_block' import StateInline from 'markdown-it/lib/rules_inline/state_inline' import Token from 'markdown-it/lib/token' +import ParserInline from 'markdown-it/lib/parser_inline' +import MarkdownIt from 'markdown-it' const unquoted = '[^"\'=<>`\\x00-\\x20]+' const singleQuoted = "'[^']*'" @@ -25,7 +27,6 @@ function isLetter (ch: number) { } function htmlInline (state: StateInline): boolean { - console.log('www', state) const pos = state.pos if (!state.md.options.html) { return false } @@ -45,8 +46,6 @@ function htmlInline (state: StateInline): boolean { const match = state.src.slice(pos).match(HTML_TAG_RE) if (!match) { return false } - console.log('mmm', match) - const content = state.src.slice(pos, pos + match[0].length) // ignore comment @@ -59,8 +58,6 @@ function htmlInline (state: StateInline): boolean { const div = document.createElement('div') div.innerHTML = content - console.log(div.children, 'www') - const element = div.children[0] if (!element) { return @@ -74,21 +71,21 @@ function htmlInline (state: StateInline): boolean { } } - const prevHtmlTags = (state.md as any)._prev_html_tags + const prevHtmlTags = (state.md as any)._prev_inline_html_tags let token: Token if (content.startsWith('') || HTML_SELF_CLOSE_TAG_RE.test(content)) { - token = state.push('html_inline_self', tag, 0) + token = state.push('html_self', tag, 0) } else { - token = state.push('html_inline_open', tag, 1) + token = state.push('html_open', tag, 1) prevHtmlTags.push(tag) } @@ -99,15 +96,88 @@ function htmlInline (state: StateInline): boolean { return true } +function htmlBlock (state: StateBlock, startLine: number, endLine: number) { + let nextLine; let lineText + let pos = state.bMarks[startLine] + state.tShift[startLine] + let max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false } + if (!state.md.options.html) { return false } + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false } + + const inlineParse: any = state.md.inline.parse.bind(state.md.inline) + const pushState = state.push + const prevHtmlTags = (state.md as any)._prev_block_html_tags + + lineText = state.src.slice(pos, max) + // 调用行内解析规则 + inlineParse(lineText, state.md, state.env, state.tokens, prevHtmlTags, pushState) + + // If we are here - we detected HTML block. + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break } + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + lineText = state.src.slice(pos, max) + + if (lineText.length === 0) { + break + } + + inlineParse(lineText, state.md, state.env, state.tokens, prevHtmlTags, pushState) + } + + const token = state.push('html_end', '', 0) + token.hidden = true + token.map = [startLine, nextLine] + token.content = state.getLines(startLine, nextLine, state.blkIndent, true) + + state.line = nextLine + + return true +} + +function inlineParse (this: ParserInline, src: string, md: any, env: any, outTokens: Token[], prevHtmlTags: string[], htmlBlock: boolean) { + // 每次解析 inline 时候,重置或者使用外部的 html 标签堆栈 + md._prev_inline_html_tags = prevHtmlTags || [] + + const state = new this.State(src, md, env, outTokens) + + // 如果是 html block 规则调用的,这里 hack 一下,避免 delimiters 为 undefined + if (htmlBlock) { + Object.defineProperty(state, 'delimiters', { + get () { + return this._delimiters || [] + }, + set (val) { + this._delimiters = val + }, + }) + } + + this.tokenize(state) + + this.ruler2.getRules('').forEach(rule => { + rule(state) + }) +} + +function parse (this: any, src: string, env: any) { + // 每次运行解析程序,设定一个 block html 标签栈,便于后续处理程序匹配标签 + this._prev_block_html_tags = [] + return MarkdownIt.prototype.parse.call(this, src, env) +} + export default { name: 'markdown-html', register: ctx => { ctx.markdown.registerPlugin(md => { - md.parse = function (src: string, env: any) { - (this as any)._prev_html_tags = [] - return MarkdownIt.prototype.parse.call(this, src, env) - } + md.parse = parse + md.inline.parse = inlineParse as any md.inline.ruler.at('html_inline', htmlInline) + md.block.ruler.at('html_block', htmlBlock) }) } } as Plugin