You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vitepress/src/node/markdown/markdown.ts

117 lines
2.8 KiB

import MarkdownIt from 'markdown-it'
import { parseHeader } from '../utils/parseHeader'
import { highlight } from './plugins/highlight'
import { slugify } from './plugins/slugify'
import { highlightLinePlugin } from './plugins/highlightLines'
import { lineNumberPlugin } from './plugins/lineNumbers'
import { componentPlugin } from './plugins/component'
import { containerPlugin } from './plugins/containers'
import { snippetPlugin } from './plugins/snippet'
import { hoistPlugin } from './plugins/hoist'
import { preWrapperPlugin } from './plugins/preWrapper'
import { linkPlugin } from './plugins/link'
import { extractHeaderPlugin } from './plugins/header'
import { Header } from '../../../types/shared'
const emoji = require('markdown-it-emoji')
const anchor = require('markdown-it-anchor')
const toc = require('markdown-it-table-of-contents')
export interface MarkdownOptions extends MarkdownIt.Options {
lineNumbers?: boolean
config?: (md: MarkdownIt) => void
anchor?: {
permalink?: boolean
permalinkBefore?: boolean
permalinkSymbol?: string
}
// https://github.com/Oktavilla/markdown-it-table-of-contents
toc?: any
externalLinks?: Record<string, string>
}
export interface MarkdownParsedData {
hoistedTags?: string[]
links?: string[]
headers?: Header[]
}
export interface MarkdownRenderer {
__data: MarkdownParsedData
render: (src: string, env?: any) => { html: string; data: any }
}
export const createMarkdownRenderer = (
options: MarkdownOptions = {}
): MarkdownRenderer => {
const md = MarkdownIt({
html: true,
linkify: true,
highlight,
...options
})
// custom plugins
md.use(componentPlugin)
.use(highlightLinePlugin)
.use(preWrapperPlugin)
.use(snippetPlugin)
.use(hoistPlugin)
.use(containerPlugin)
.use(extractHeaderPlugin)
.use(linkPlugin, {
target: '_blank',
rel: 'noopener noreferrer',
...options.externalLinks
})
// 3rd party plugins
.use(emoji)
.use(
anchor,
Object.assign(
{
slugify,
permalink: true,
permalinkBefore: true,
permalinkSymbol: '#'
},
options.anchor
)
)
.use(
toc,
Object.assign(
{
slugify,
includeLevel: [2, 3],
format: parseHeader
},
options.toc
)
)
// apply user config
if (options.config) {
options.config(md)
}
if (options.lineNumbers) {
md.use(lineNumberPlugin)
}
// wrap render so that we can return both the html and extracted data.
const render = md.render
const wrappedRender: MarkdownRenderer['render'] = (src) => {
;(md as any).__data = {}
const html = render.call(md, src)
return {
html,
data: (md as any).__data
}
}
;(md as any).render = wrappedRender
return md as any
}