mirror of https://github.com/vuejs/vitepress
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.
133 lines
4.2 KiB
133 lines
4.2 KiB
import type { MarkdownItAsync } from 'markdown-it-async'
|
|
import container from 'markdown-it-container'
|
|
import type { RenderRule } from 'markdown-it/lib/renderer.mjs'
|
|
import type Token from 'markdown-it/lib/token.mjs'
|
|
import type { MarkdownEnv } from '../../shared'
|
|
import { extractTitle } from './preWrapper'
|
|
|
|
export const containerPlugin = (
|
|
md: MarkdownItAsync,
|
|
options?: ContainerOptions
|
|
) => {
|
|
md.use(...createContainer('tip', options?.tipLabel || 'TIP', md))
|
|
.use(...createContainer('info', options?.infoLabel || 'INFO', md))
|
|
.use(...createContainer('warning', options?.warningLabel || 'WARNING', md))
|
|
.use(...createContainer('danger', options?.dangerLabel || 'DANGER', md))
|
|
.use(...createContainer('details', options?.detailsLabel || 'Details', md))
|
|
// explicitly escape Vue syntax
|
|
.use(container, 'v-pre', {
|
|
render: (tokens: Token[], idx: number) =>
|
|
tokens[idx].nesting === 1 ? `<div v-pre>\n` : `</div>\n`
|
|
})
|
|
.use(container, 'raw', {
|
|
render: (tokens: Token[], idx: number) =>
|
|
tokens[idx].nesting === 1 ? `<div class="vp-raw">\n` : `</div>\n`
|
|
})
|
|
.use(...createCodeGroup(md))
|
|
}
|
|
|
|
type ContainerArgs = [typeof container, string, { render: RenderRule }]
|
|
|
|
function createContainer(
|
|
klass: string,
|
|
defaultTitle: string,
|
|
md: MarkdownItAsync
|
|
): ContainerArgs {
|
|
return [
|
|
container,
|
|
klass,
|
|
{
|
|
render(tokens, idx, _options, env: MarkdownEnv & { references?: any }) {
|
|
const token = tokens[idx]
|
|
if (token.nesting === 1) {
|
|
token.attrJoin('class', `${klass} custom-block`)
|
|
const attrs = md.renderer.renderAttrs(token)
|
|
const info = token.info.trim().slice(klass.length).trim()
|
|
const title = md.renderInline(info || defaultTitle, {
|
|
references: env.references
|
|
})
|
|
const titleClass =
|
|
'custom-block-title' + (info ? '' : ' custom-block-title-default')
|
|
if (klass === 'details')
|
|
return `<details ${attrs}><summary>${title}</summary>\n`
|
|
return `<div ${attrs}><p class="${titleClass}">${title}</p>\n`
|
|
} else return klass === 'details' ? `</details>\n` : `</div>\n`
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
function createCodeGroup(md: MarkdownItAsync): ContainerArgs {
|
|
return [
|
|
container,
|
|
'code-group',
|
|
{
|
|
render(tokens, idx) {
|
|
if (tokens[idx].nesting === 1) {
|
|
const token = tokens[idx]
|
|
const info = token.info.trim()
|
|
|
|
// Extract name parameter
|
|
const nameMatch = info.match(/name=(\S+)/)
|
|
let name = nameMatch ? nameMatch[1] : null
|
|
|
|
// Validate: only allow alphanumeric, hyphens, and underscores
|
|
if (name && !/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
name = null
|
|
}
|
|
|
|
// Build data attribute
|
|
const nameAttr = name
|
|
? ` data-group-name="${md.utils.escapeHtml(name)}"`
|
|
: ''
|
|
|
|
let tabs = ''
|
|
let checked = 'checked'
|
|
|
|
for (
|
|
let i = idx + 1;
|
|
!(
|
|
tokens[i].nesting === -1 &&
|
|
tokens[i].type === 'container_code-group_close'
|
|
);
|
|
++i
|
|
) {
|
|
const isHtml = tokens[i].type === 'html_block'
|
|
|
|
if (
|
|
(tokens[i].type === 'fence' && tokens[i].tag === 'code') ||
|
|
isHtml
|
|
) {
|
|
const title = extractTitle(
|
|
isHtml ? tokens[i].content : tokens[i].info,
|
|
isHtml
|
|
)
|
|
|
|
if (title) {
|
|
tabs += `<input type="radio" name="group-${idx}" id="tab-${i}" ${checked}><label data-title="${md.utils.escapeHtml(title)}" for="tab-${i}">${title}</label>`
|
|
|
|
if (checked && !isHtml) tokens[i].info += ' active'
|
|
checked = ''
|
|
}
|
|
}
|
|
}
|
|
|
|
return `<div class="vp-code-group"${nameAttr}><div class="tabs">${tabs}</div><div class="blocks">\n`
|
|
}
|
|
return `</div></div>\n`
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
export interface ContainerOptions {
|
|
infoLabel?: string
|
|
noteLabel?: string
|
|
tipLabel?: string
|
|
warningLabel?: string
|
|
dangerLabel?: string
|
|
detailsLabel?: string
|
|
importantLabel?: string
|
|
cautionLabel?: string
|
|
}
|