refactor: externalize markdown component plugin (#843)

pull/1207/head
meteorlxy 2 years ago committed by GitHub
parent 22d796aeea
commit 8a2d4f39e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -92,6 +92,7 @@
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"devDependencies": { "devDependencies": {
"@mdit-vue/plugin-component": "^0.9.0",
"@rollup/plugin-alias": "^3.1.9", "@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^22.0.2", "@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",

@ -6,6 +6,7 @@ importers:
specifiers: specifiers:
'@docsearch/css': ^3.2.1 '@docsearch/css': ^3.2.1
'@docsearch/js': ^3.2.1 '@docsearch/js': ^3.2.1
'@mdit-vue/plugin-component': ^0.9.0
'@rollup/plugin-alias': ^3.1.9 '@rollup/plugin-alias': ^3.1.9
'@rollup/plugin-commonjs': ^22.0.2 '@rollup/plugin-commonjs': ^22.0.2
'@rollup/plugin-json': ^4.1.0 '@rollup/plugin-json': ^4.1.0
@ -88,6 +89,7 @@ importers:
vite: 3.0.8 vite: 3.0.8
vue: 3.2.37 vue: 3.2.37
devDependencies: devDependencies:
'@mdit-vue/plugin-component': 0.9.0
'@rollup/plugin-alias': 3.1.9_rollup@2.78.0 '@rollup/plugin-alias': 3.1.9_rollup@2.78.0
'@rollup/plugin-commonjs': 22.0.2_rollup@2.78.0 '@rollup/plugin-commonjs': 22.0.2_rollup@2.78.0
'@rollup/plugin-json': 4.1.0_rollup@2.78.0 '@rollup/plugin-json': 4.1.0_rollup@2.78.0
@ -392,6 +394,13 @@ packages:
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@mdit-vue/plugin-component/0.9.0:
resolution: {integrity: sha512-HfrIQL8EiIyBeg42aSdTJJ/r2STI88fDFexPv7pI+a/qq1BG5AR0FTHTuzP3vwHn1ysgYB1qk4/hV/hodPYzhQ==}
dependencies:
'@types/markdown-it': 12.2.3
markdown-it: 13.0.1
dev: true
/@nodelib/fs.scandir/2.1.5: /@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}

@ -1,11 +1,15 @@
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import anchorPlugin from 'markdown-it-anchor'
import attrsPlugin from 'markdown-it-attrs'
import emojiPlugin from 'markdown-it-emoji'
import tocPlugin from 'markdown-it-toc-done-right'
import { componentPlugin } from '@mdit-vue/plugin-component'
import { IThemeRegistration } from 'shiki' import { IThemeRegistration } from 'shiki'
import { parseHeader } from '../utils/parseHeader' import { parseHeader } from '../utils/parseHeader'
import { highlight } from './plugins/highlight' import { highlight } from './plugins/highlight'
import { slugify } from './plugins/slugify' import { slugify } from './plugins/slugify'
import { highlightLinePlugin } from './plugins/highlightLines' import { highlightLinePlugin } from './plugins/highlightLines'
import { lineNumberPlugin } from './plugins/lineNumbers' import { lineNumberPlugin } from './plugins/lineNumbers'
import { componentPlugin } from './plugins/component'
import { containerPlugin } from './plugins/containers' import { containerPlugin } from './plugins/containers'
import { snippetPlugin } from './plugins/snippet' import { snippetPlugin } from './plugins/snippet'
import { hoistPlugin } from './plugins/hoist' import { hoistPlugin } from './plugins/hoist'
@ -14,10 +18,6 @@ import { linkPlugin } from './plugins/link'
import { headingPlugin } from './plugins/headings' import { headingPlugin } from './plugins/headings'
import { imagePlugin } from './plugins/image' import { imagePlugin } from './plugins/image'
import { Header } from '../shared' import { Header } from '../shared'
import anchor from 'markdown-it-anchor'
import attrs from 'markdown-it-attrs'
import emoji from 'markdown-it-emoji'
import toc from 'markdown-it-toc-done-right'
export type ThemeOptions = export type ThemeOptions =
| IThemeRegistration | IThemeRegistration
@ -27,7 +27,7 @@ export interface MarkdownOptions extends MarkdownIt.Options {
lineNumbers?: boolean lineNumbers?: boolean
config?: (md: MarkdownIt) => void config?: (md: MarkdownIt) => void
anchor?: { anchor?: {
permalink?: anchor.AnchorOptions['permalink'] permalink?: anchorPlugin.AnchorOptions['permalink']
} }
attrs?: { attrs?: {
leftDelimiter?: string leftDelimiter?: string
@ -88,15 +88,15 @@ export const createMarkdownRenderer = async (
// 3rd party plugins // 3rd party plugins
if (!options.attrs?.disable) { if (!options.attrs?.disable) {
md.use(attrs, options.attrs) md.use(attrsPlugin, options.attrs)
} }
md.use(anchor, { md.use(anchorPlugin, {
slugify, slugify,
permalink: anchor.permalink.ariaHidden({}), permalink: anchorPlugin.permalink.ariaHidden({}),
...options.anchor ...options.anchor
}) })
.use(toc, { .use(tocPlugin, {
slugify, slugify,
level: [2, 3], level: [2, 3],
format: (x: string, htmlencode: (s: string) => string) => format: (x: string, htmlencode: (s: string) => string) =>
@ -104,7 +104,7 @@ export const createMarkdownRenderer = async (
listType: 'ul', listType: 'ul',
...options.toc ...options.toc
}) })
.use(emoji) .use(emojiPlugin)
// apply user config // apply user config
if (options.config) { if (options.config) {

@ -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…
Cancel
Save