feat: support code group container

This commit is contained in:
purocean
2024-07-12 15:43:53 +08:00
parent fbb349a5b6
commit c8a467d24a
3 changed files with 91 additions and 22 deletions
+12 -1
View File
@@ -514,7 +514,7 @@ Support functions similiar to [VuePress Container Block](https://v2.vuepress.vue
`type` is required, `title` and `content` are optional.
The supported `types` are: tip, warning, danger, details, group, group-item, row, col, section, div
The supported `types` are: tip, warning, danger, details, code-group, group, group-item, row, col, section, div
**Example**
@@ -557,6 +557,17 @@ test 3
:::
::::
::: code-group
```js [test.js]
let a = 1
```
```ts [test.ts]
let a: number = 1
```
:::
::::: row Columns
:::: col TODO
::: warning
+11 -1
View File
@@ -512,7 +512,7 @@ chart.setOption(option, true)
`type` 是必需的, `title` 和 `content` 是可选的。
支持的 `type` 有:tip, warning, danger, details, group, group-item, row, col, section, div
支持的 `type` 有:tip, warning, danger, details, code-group, group, group-item, row, col, section, div
**示例**
@@ -555,6 +555,16 @@ test 3
:::
::::
::: code-group
```js [test.js]
let a = 1
```
```ts [test.ts]
let a: number = 1
```
:::
::::: row 分列示例
:::: col TODO
::: warning
+68 -20
View File
@@ -230,6 +230,7 @@ export default {
let groupItemSeq = 0
let groupItemBase = Date.now()
let groupItemName = groupItemBase + groupItemSeq
const groupItemContentClass = 'group-item-content'
ctx.registerHook('MARKDOWN_BEFORE_RENDER', ({ env }) => {
// first render, reset count
@@ -240,8 +241,24 @@ export default {
groupItemSeq = 0
})
function buildGroupItem (token: Token, title: string, attrs?: Record<string, string>) {
const parent = h('div', attrs || Object.fromEntries(token.attrs || []), [])
const radioName = `group-item-${groupItemName}`
const id = `group-item-${groupItemName}-${groupItemIdx++}`
const checked = groupItemIdx === 1 || title.startsWith('*')
return {
node: h(Fragment, [
h('input', { key: id, class: 'group-item-radio', id, name: radioName, type: 'radio', 'data-default-checked': checked, checked }),
h('label', { class: 'group-item-label', for: id }, title.replace('*', '').trim() || 'Group Item'),
parent
]),
parent
}
}
ctx.markdown.registerPlugin(md => {
['tip', 'warning', 'danger', 'details', 'group-item', 'group', 'row', 'col', 'section', 'div'].forEach(name => {
['tip', 'warning', 'danger', 'details', 'group-item', 'group', 'row', 'col', 'section', 'div', 'code-group'].forEach(name => {
const reg = new RegExp(`^${name}\\s*(.*)$`)
md.use(MarkdownItContainer, name, {
@@ -265,24 +282,14 @@ export default {
const match = token.info.trim().match(reg)
const title = md.utils.escapeHtml(match![1])
const containerClass = `custom-container ${name}`
const isGroup = name === 'group' || name === 'code-group'
const containerClass = isGroup ? 'custom-container group' : `custom-container ${name}`
if (name === 'group-item') {
token.attrJoin('class', 'group-item-content')
const parent = h('div', Object.fromEntries(token.attrs || []), [])
const radioName = `group-item-${groupItemName}`
const id = `group-item-${groupItemName}-${groupItemIdx++}`
const checked = groupItemIdx === 1 || title.startsWith('*')
return {
node: h(Fragment, [
h('input', { key: id, class: 'group-item-radio', id, name: radioName, type: 'radio', 'data-default-checked': checked, checked }),
h('label', { class: 'group-item-label', for: id }, title.replace('*', '').trim() || 'Group Item'),
parent
]),
parent
}
} else if (name === 'group') {
token.attrJoin('class', groupItemContentClass)
return buildGroupItem(token, title)
} else if (isGroup) {
groupItemIdx = 0
groupItemSeq++
groupItemName = groupItemBase + groupItemSeq
@@ -292,7 +299,7 @@ export default {
const titleTag = name === 'details' ? 'summary' : 'p'
const titleClass = name === 'details' ? '' : 'custom-container-title'
const children = (title || name === 'group') ? [h(titleTag, { class: titleClass }, title)] : []
const children = (title || isGroup) ? [h(titleTag, { class: titleClass }, title)] : []
token.attrJoin('class', containerClass)
if (title) {
@@ -301,15 +308,55 @@ export default {
const props: Record<string, any> = Object.fromEntries(token.attrs || [])
if (name === 'group') {
if (isGroup) {
props.key = groupItemName
}
if (name === 'code-group') {
for (
let i = idx + 1;
!(
tokens[i].nesting === -1 &&
tokens[i].type === 'container_code-group_close'
);
++i
) {
const token = tokens[i]
if (token.type === 'fence' && token.tag === 'code') {
if (!token.meta) {
token.meta = {}
}
token.meta.isCodeGroupItem = true
}
}
}
return h(containerTag, props, children)
}
}
})
})
const renderFence = md.renderer.rules.fence!
const infoTitleReg = /\[([^\]]*)\]/
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
if (token.meta && token.meta.isCodeGroupItem) {
const title = token.info.match(infoTitleReg)?.[1] || token.info || 'txt'
token.info = token.info.replace(infoTitleReg, '').trim()
const groupItem = buildGroupItem(token, title, { class: groupItemContentClass })
;(groupItem.parent.children as any[]).push(
renderFence.call(slf, tokens, idx, options, env, slf)
)
return groupItem.node as any
}
return renderFence.call(slf, tokens, idx, options, env, slf)
}
})
ctx.registerHook('VIEW_ON_GET_HTML_FILTER_NODE', ({ node }) => {
@@ -322,8 +369,9 @@ export default {
/* eslint-disable no-template-curly-in-string */
items.push(
{ label: '/ ::: Container', insertText: '${3|:::,::::,:::::|} ${1|tip,warning,danger,details,group,group-item,row,col,section,div|} ${2:Title}\n${4:Content}\n${3|:::,::::,:::::|}\n', block: true },
{ label: '/ ::: Container', insertText: '${3|:::,::::,:::::|} ${1|tip,warning,danger,details,code-group,group,group-item,row,col,section,div|} ${2:Title}\n${4:Content}\n${3|:::,::::,:::::|}\n', block: true },
{ label: '/ ::: Group Container', insertText: ':::: group ${1:Title}\n::: group-item Tab 1\ntest 1\n:::\n::: group-item *Tab 2\ntest 2\n:::\n::: group-item Tab 3\ntest 3\n:::\n::::\n', block: true },
{ label: '/ ::: Code Group Container', insertText: '::: code-group ${1:Title}\n${2:```js [test.js]\nlet a = 1\n```\n\n```ts [test.ts]\nlet a: number = 1\n```}\n:::\n', block: true },
{ label: '/ ::: Column Container', insertText: ':::: row ${1:Title}\n::: col\ntest 1\n:::\n::: col\ntest 2\n:::\n::::\n', block: true },
)
})