mirror of https://github.com/vuejs/vitepress
refactor: externalize markdown component plugin (#843)
parent
22d796aeea
commit
8a2d4f39e4
@ -1,202 +0,0 @@
|
|||||||
import MarkdownIt from 'markdown-it'
|
|
||||||
import { RuleBlock } from 'markdown-it/lib/parser_block'
|
|
||||||
import blockNames from 'markdown-it/lib/common/html_blocks'
|
|
||||||
import { HTML_OPEN_CLOSE_TAG_RE } from 'markdown-it/lib/common/html_re'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vue reserved tags
|
|
||||||
*
|
|
||||||
* @see https://vuejs.org/api/built-in-components.html
|
|
||||||
*/
|
|
||||||
const vueReservedTags = [
|
|
||||||
'template',
|
|
||||||
'component',
|
|
||||||
'transition',
|
|
||||||
'transition-group',
|
|
||||||
'keep-alive',
|
|
||||||
'slot',
|
|
||||||
'teleport'
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* According to markdown spec, all non-block html tags are treated as "inline"
|
|
||||||
* tags (wrapped with <p></p>), including those "unknown" tags.
|
|
||||||
*
|
|
||||||
* Therefore, markdown-it processes "inline" tags and "unknown" tags in the
|
|
||||||
* same way, and does not care if a tag is "inline" or "unknown".
|
|
||||||
*
|
|
||||||
* As we want to take those "unknown" tags as custom components, we should
|
|
||||||
* treat them as "block" tags.
|
|
||||||
*
|
|
||||||
* So we have to distinguish between "inline" and "unknown" tags ourselves.
|
|
||||||
*
|
|
||||||
* The inline tags list comes from MDN.
|
|
||||||
*
|
|
||||||
* @see https://spec.commonmark.org/0.29/#raw-html
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
|
|
||||||
*/
|
|
||||||
const inlineTags = [
|
|
||||||
'a',
|
|
||||||
'abbr',
|
|
||||||
'acronym',
|
|
||||||
'audio',
|
|
||||||
'b',
|
|
||||||
'bdi',
|
|
||||||
'bdo',
|
|
||||||
'big',
|
|
||||||
'br',
|
|
||||||
'button',
|
|
||||||
'canvas',
|
|
||||||
'cite',
|
|
||||||
'code',
|
|
||||||
'data',
|
|
||||||
'datalist',
|
|
||||||
'del',
|
|
||||||
'dfn',
|
|
||||||
'em',
|
|
||||||
'embed',
|
|
||||||
'i',
|
|
||||||
// iframe is treated as HTML blocks in markdown spec
|
|
||||||
// 'iframe',
|
|
||||||
'img',
|
|
||||||
'input',
|
|
||||||
'ins',
|
|
||||||
'kbd',
|
|
||||||
'label',
|
|
||||||
'map',
|
|
||||||
'mark',
|
|
||||||
'meter',
|
|
||||||
'noscript',
|
|
||||||
'object',
|
|
||||||
'output',
|
|
||||||
'picture',
|
|
||||||
'progress',
|
|
||||||
'q',
|
|
||||||
'ruby',
|
|
||||||
's',
|
|
||||||
'samp',
|
|
||||||
'script',
|
|
||||||
'select',
|
|
||||||
'slot',
|
|
||||||
'small',
|
|
||||||
'span',
|
|
||||||
'strong',
|
|
||||||
'sub',
|
|
||||||
'sup',
|
|
||||||
'svg',
|
|
||||||
'template',
|
|
||||||
'textarea',
|
|
||||||
'time',
|
|
||||||
'u',
|
|
||||||
'tt',
|
|
||||||
'var',
|
|
||||||
'video',
|
|
||||||
'wbr'
|
|
||||||
]
|
|
||||||
|
|
||||||
// replacing the default htmlBlock rule to allow using custom components at
|
|
||||||
// root level
|
|
||||||
//
|
|
||||||
// an array of opening and corresponding closing sequences for html tags,
|
|
||||||
// last argument defines whether it can terminate a paragraph or not
|
|
||||||
const HTML_SEQUENCES: [RegExp, RegExp, boolean][] = [
|
|
||||||
[/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true],
|
|
||||||
[/^<!--/, /-->/, true],
|
|
||||||
[/^<\?/, /\?>/, true],
|
|
||||||
[/^<![A-Z]/, />/, true],
|
|
||||||
[/^<!\[CDATA\[/, /\]\]>/, true],
|
|
||||||
|
|
||||||
// MODIFIED HERE: treat vue reserved tags as block tags
|
|
||||||
[
|
|
||||||
new RegExp('^</?(' + vueReservedTags.join('|') + ')(?=(\\s|/?>|$))', 'i'),
|
|
||||||
/^$/,
|
|
||||||
true
|
|
||||||
],
|
|
||||||
|
|
||||||
// MODIFIED HERE: treat unknown tags as block tags (custom components),
|
|
||||||
// excluding known inline tags
|
|
||||||
[
|
|
||||||
new RegExp(
|
|
||||||
'^</?(?!(' + inlineTags.join('|') + ')(?![\\w-]))\\w[\\w-]*[\\s/>]'
|
|
||||||
),
|
|
||||||
/^$/,
|
|
||||||
true
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
new RegExp('^</?(' + blockNames.join('|') + ')(?=(\\s|/?>|$))', 'i'),
|
|
||||||
/^$/,
|
|
||||||
true
|
|
||||||
],
|
|
||||||
[new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false]
|
|
||||||
]
|
|
||||||
|
|
||||||
export const componentPlugin = (md: MarkdownIt) => {
|
|
||||||
md.block.ruler.at('html_block', htmlBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlBlock: RuleBlock = (state, startLine, endLine, silent): boolean => {
|
|
||||||
let i, nextLine, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
lineText = state.src.slice(pos, max)
|
|
||||||
|
|
||||||
for (i = 0; i < HTML_SEQUENCES.length; i++) {
|
|
||||||
if (HTML_SEQUENCES[i][0].test(lineText)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === HTML_SEQUENCES.length) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (silent) {
|
|
||||||
// true if this sequence can be a terminator, false otherwise
|
|
||||||
return HTML_SEQUENCES[i][2]
|
|
||||||
}
|
|
||||||
|
|
||||||
nextLine = startLine + 1
|
|
||||||
|
|
||||||
// if we are here - we detected HTML block. let's roll down till block end
|
|
||||||
if (!HTML_SEQUENCES[i][1].test(lineText)) {
|
|
||||||
for (; 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 (HTML_SEQUENCES[i][1].test(lineText)) {
|
|
||||||
if (lineText.length !== 0) {
|
|
||||||
nextLine++
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.line = nextLine
|
|
||||||
|
|
||||||
const token = state.push('html_block', '', 0)
|
|
||||||
token.map = [startLine, nextLine]
|
|
||||||
token.content = state.getLines(startLine, nextLine, state.blkIndent, true)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
Loading…
Reference in new issue