vitepress
的源码function createCodeGroup(options, md) {
return [
container_plugin,
'code-group',
{
render(tokens, idx) {
if (tokens[idx].nesting === 1) {
const name = nanoid$1(5)
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) {
const id = nanoid$1(7)
tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label data-title="${md.utils.escapeHtml(title)}" for="tab-${id}">${title}</label>`
if (checked && !isHtml)
tokens[i].info += ' active'
checked = ''
}
}
}
return `<div class="vp-code-group${getAdaptiveThemeMarker(
options
)}"><div class="tabs">${tabs}</div><div class="blocks">
`
}
return `</div></div>
`
}
}
]
}
token type 的开始和结束是
和 container_code-group_open
container_code-group_close
拿到这两个 token 之间的所有 token,然后根据他们 token 的类型来判断是 fence 和 html_block
获取 title,然后生成 tabs 和 blocks,最后返回 html 字符串
code_group
由于我是用
来做的主题,所以这种 tab 切换的效果我是用 naive-ui
的 naive-ui
来实现的n-tabs
但是
是需要 n-tabs
来配合使用的, 所以我需要找到包裹在 n-tab-pane
和 container_code-group_open
里面的 fence 和 html_block token,然后使用 container_code-group_close
包裹这两个类型的 tokenn-tab-pane
由于本人水平实在有限,没有认真研究
里面的 markdown-it
的用法, (不知道 ruler/token
能不能够实现)Ruler.before/after
已下是我变通的实践
/*
* @Author : peter peter@qingcongai.com
* @Date : 2024-11-30 10:22:15
* @LastEditors : peter peter@qingcongai.com
* @LastEditTime : 2024-12-06 17:35:08
* @Description :
*/
import type { MarkdownIt } from './index.ts'
import { extractTitle } from '../../utils/index.ts'
export default (md: MarkdownIt) => {
md.renderer.rules['container_code-group_open'] = (tokens, idx) => {
const closeTokenIndex = tokens.findIndex((token, index) => {
return index > idx && token.type === 'container_code-group_close'
})
const childTokens = tokens
.slice(idx + 1, closeTokenIndex)
.filter((t) => t.level === tokens[idx].level + 1)
childTokens.forEach((t) => {
const isHtml = t.type === 'html_block'
const title = extractTitle(isHtml ? t.content : t.info, isHtml)
t.attrSet('tabName', title)
})
return `<n-card
:class="isMobile ? '-mx-3 !w-auto' : ''"
class="code_group_card"
embedded
:bordered="false"
content-class="!p-0"
>
<n-tabs type="line" animated>
`
}
md.renderer.rules['container_code-group_close'] = () => {
return `</n-tabs>
</n-card>`
}
}
找到所有的直接子
(看上面的源码也就知道只有 token
和 fence
两种)html_block
根据子
来生成 token
, 并设置给 tab title
的 token
tabName attr
html_block
和 fence
有
,就用 tabName attr
包裹n-tab-pane
html_block
/*
* @Author : peter peter@qingcongai.com
* @Date : 2024-11-23 10:49:38
* @LastEditors : peter peter@qingcongai.com
* @LastEditTime : 2024-12-03 14:03:29
* @Description :
*/
import type { MarkdownIt } from './index.ts'
export default (md: MarkdownIt) => {
const defaultRender = md.renderer.rules.html_block
md.renderer.rules.html_block = (...arg) => {
const [tokens, idx] = arg
const token = tokens[idx]
const tabName = token.attrGet('tabName')
token.attrSet('tabName', '')
let prev = ''
let post = ''
if (tabName) {
prev = `<n-tab-pane name="${tabName}" tab="${tabName}">`
post = '</n-tab-pane>'
}
return `${prev}${defaultRender!(...arg)}${post}`
}
}
fence
/*
* @Author : peter peter@qingcongai.com
* @Date : 2024-11-23 10:49:38
* @LastEditors : huchaomin iisa_peter@163.com
* @LastEditTime : 2024-12-01 23:35:01
* @Description :
*/
import { parse } from 'node-html-parser'
import type { MarkdownIt } from './index.ts'
export default (md: MarkdownIt) => {
const defaultRender = md.renderer.rules.fence
md.renderer.rules.fence = (...arg) => {
const result = defaultRender!(...arg)
const root = parse(result).clone() as unknown as HTMLElement
root.querySelector('button.copy')?.remove()
root.classList.remove('vp-adaptive-theme')
const [tokens, idx] = arg
const token = tokens[idx]
const tabName = token.attrGet('tabName')
token.attrSet('tabName', '')
let prev = ''
let post = ''
if (tabName) {
prev = `<n-tab-pane name="${tabName}" tab="${tabName}">`
post = '</n-tab-pane>'
}
return `${prev}<FenceWrapper :inCodeGroup=${!!tabName} content="${md.utils
.escapeHtml(tokens[idx].content)
.replace(/\/\/ \[!code .*\]/g, '')
.trim()}">${root.outerHTML}</FenceWrapper>${post}`
}
}
::: code-group
<h1 data-title="我是标题">这里是html_block</h1>
\```ts-vue [frontmatter]
{{ $frontmatter }}
\```
:::