diff --git a/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts b/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts
index 4f3efa00..61b38a0a 100644
--- a/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts
+++ b/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts
@@ -1,6 +1,6 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
-import { createContainerPlugin } from '../src/node/container/createContainer.js'
+import { createContainerPlugin, createContainerSyntaxPlugin } from '../src/node/container/createContainer.js'
describe('createContainerPlugin', () => {
it('should work with default options', () => {
@@ -20,3 +20,37 @@ describe('createContainerPlugin', () => {
expect(md.render(':::test\ncontent\n:::')).toContain('class="test"')
})
})
+
+describe('createContainerSyntaxPlugin', () => {
+ it('should work with default options', () => {
+ const md = new MarkdownIt()
+ createContainerSyntaxPlugin(md, 'test')
+ const rendered = md.render(':::test\ncontent\n:::')
+ expect(rendered).toContain('class="custom-container test"')
+ expect(rendered).toContain('content')
+ })
+
+ it('should work with more than 3 markers', () => {
+ const md = new MarkdownIt()
+ createContainerSyntaxPlugin(md, 'test')
+ expect(md.render('::::test\ncontent\n::::')).toContain('class="custom-container test"')
+ expect(md.render(':::::test\ncontent\n:::::')).toContain('class="custom-container test"')
+ })
+
+ it('should work with custom render', () => {
+ const md = new MarkdownIt()
+ createContainerSyntaxPlugin(md, 'test', (tokens, index) => `
${tokens[index].content} ${tokens[index].meta.title}
`)
+ const rendered = md.render(':::test title="title"\ncontent\n:::')
+ expect(rendered).toContain('class="test"')
+ expect(rendered).toContain('content\n title')
+ })
+
+ it('should not work', () => {
+ const md = new MarkdownIt()
+ createContainerSyntaxPlugin(md, 'test')
+
+ expect(md.render('::test\ncontent\n::')).not.toContain('class="custom-container test"')
+ expect(md.render(':::text\ncontent\n:::')).not.toContain('class="custom-container text"')
+ expect(md.render(':::test\ncontent\n:::')).not.toContain('class="custom-container test"')
+ })
+})
diff --git a/plugins/plugin-md-power/src/node/container/chat.ts b/plugins/plugin-md-power/src/node/container/chat.ts
index d66506af..db967016 100644
--- a/plugins/plugin-md-power/src/node/container/chat.ts
+++ b/plugins/plugin-md-power/src/node/container/chat.ts
@@ -10,15 +10,9 @@
* :::
*/
import type { PluginSimple } from 'markdown-it'
-import type StateBlock from 'markdown-it/lib/rules_block/state_block.mjs'
-import type Token from 'markdown-it/lib/token.mjs'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
-import { resolveAttrs } from '.././utils/resolveAttrs.js'
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
-
-interface ChatMeta {
- title?: string
-}
+import { createContainerSyntaxPlugin } from './createContainer.js'
interface ChatMessage {
sender: 'user' | 'self'
@@ -27,63 +21,6 @@ interface ChatMessage {
content: string[]
}
-export const chatPlugin: PluginSimple = (md) => {
- md.block.ruler.before('fence', 'chat_def', chatDef)
-
- md.renderer.rules.chat_container = (tokens: Token[], idx: number, _, env) => {
- const { meta, content } = tokens[idx]
- const { title } = meta as ChatMeta
- const messages = parseChatContent(content)
- return `
-
-
- ${chatMessagesRender(md, env, messages)}
-
-
`
- }
-}
-
-function chatDef(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
- const start = state.bMarks[startLine] + state.tShift[startLine]
- const max = state.eMarks[startLine]
- let pos = start
-
- if (state.src.slice(pos, pos + 3) !== ':::')
- return false
-
- pos += 3
-
- const info = state.src.slice(start + 3, max).trim()
- if (!info.startsWith('chat'))
- return false
-
- /* istanbul ignore if -- @preserve */
- if (silent)
- return true
-
- let line = startLine
- let content = ''
- while (++line < endLine) {
- if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === ':::') {
- break
- }
-
- content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`
- }
-
- const token = state.push('chat_container', '', 0)
- token.meta = resolveAttrs(info).attrs
- token.content = content
- token.markup = '::: chat'
- token.map = [startLine, line + 1]
-
- state.line = line + 1
-
- return true
-}
-
function chatMessagesRender(md: Markdown, env: MarkdownEnv, messages: ChatMessage[]): string {
let currentDate = ''
return messages.map(({ sender, username, date, content }) => {
@@ -142,3 +79,16 @@ function parseChatContent(content: string): ChatMessage[] {
}
return messages
}
+
+export const chatPlugin: PluginSimple = md => createContainerSyntaxPlugin(
+ md,
+ 'chat',
+ (tokens, idx, _, env) => `
+
+
+ ${chatMessagesRender(md, env, parseChatContent(tokens[idx].content))}
+
+
`,
+)
diff --git a/plugins/plugin-md-power/src/node/container/createContainer.ts b/plugins/plugin-md-power/src/node/container/createContainer.ts
index bb598aca..6fa2304a 100644
--- a/plugins/plugin-md-power/src/node/container/createContainer.ts
+++ b/plugins/plugin-md-power/src/node/container/createContainer.ts
@@ -1,6 +1,8 @@
import type { RenderRule } from 'markdown-it/lib/renderer.mjs'
+import type StateBlock from 'markdown-it/lib/rules_block/state_block.mjs'
import type { Markdown } from 'vuepress/markdown'
import container from 'markdown-it-container'
+import { resolveAttrs } from '../utils/resolveAttrs.js'
type RenderRuleParams = Parameters extends [...infer Args, infer _] ? Args : never
@@ -27,3 +29,88 @@ export function createContainerPlugin(
md.use(container, type, { render })
}
+
+/**
+ * 创建一个自定义的容器规则,内容不会交给 markdown-it 处理。
+ * 需要自定义 content 的处理逻辑
+ * ```md
+ * ::: type
+ * xxxx <-- content: 这部分的内容不会交给 markdown-it 处理
+ * :::
+ * ```
+ *
+ * @example
+ * ```ts
+ * const example = createContainerSyntaxPlugin(md, 'example', (tokens, index, options, env) => {
+ * const { content, meta } = tokens[index]
+ * return `${meta.title} | ${content}
`
+ * })
+ * ```
+ */
+export function createContainerSyntaxPlugin(
+ md: Markdown,
+ type: string,
+ render?: RenderRule,
+) {
+ const maker = ':'
+ const markerMinLen = 3
+
+ function defineContainer(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
+ const start = state.bMarks[startLine] + state.tShift[startLine]
+ const max = state.eMarks[startLine]
+ let pos = start
+
+ // check marker
+ if (state.src[pos] !== maker)
+ return false
+
+ pos += markerMinLen
+
+ for (pos = start + 1; pos <= max; pos++) {
+ if (state.src[pos] !== maker)
+ break
+ }
+
+ if (pos - start < markerMinLen)
+ return false
+
+ const markup = state.src.slice(start, pos)
+ const info = state.src.slice(pos, max).trim()
+
+ // ::: type
+ if (!info.startsWith(type))
+ return false
+
+ /* istanbul ignore if -- @preserve */
+ if (silent)
+ return true
+
+ let line = startLine
+ let content = ''
+ while (++line < endLine) {
+ if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) {
+ break
+ }
+
+ content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`
+ }
+
+ const token = state.push(`${type}_container`, '', 0)
+ token.meta = resolveAttrs(info.slice(type.length)).attrs
+ token.content = content
+ token.markup = `${markup} ${type}`
+ token.map = [startLine, line + 1]
+
+ state.line = line + 1
+
+ return true
+ }
+
+ const defaultRender: RenderRule = (tokens, index) => {
+ const { content } = tokens[index]
+ return `${content}
`
+ }
+
+ md.block.ruler.before('fence', `${type}_definition`, defineContainer)
+ md.renderer.rules[`${type}_container`] = render ?? defaultRender
+}