diff --git a/plugins/plugin-md-power/__test__/__snapshots__/demoWrapperPlugin.spec.ts.snap b/plugins/plugin-md-power/__test__/__snapshots__/demoWrapperPlugin.spec.ts.snap new file mode 100644 index 00000000..eef06d09 --- /dev/null +++ b/plugins/plugin-md-power/__test__/__snapshots__/demoWrapperPlugin.spec.ts.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`demoWrapperPlugin > should work 1`] = ` +"
+
+
+ +
+
+

content

+
+
+
+

test

+
+
+

content

+
+
+
+ +
+
+

xxx

+
+
+
+ +
+
+

xxx

+
" +`; diff --git a/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts b/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts new file mode 100644 index 00000000..4f3efa00 --- /dev/null +++ b/plugins/plugin-md-power/__test__/createContainerPlugin.spec.ts @@ -0,0 +1,22 @@ +import MarkdownIt from 'markdown-it' +import { describe, expect, it } from 'vitest' +import { createContainerPlugin } from '../src/node/container/createContainer.js' + +describe('createContainerPlugin', () => { + it('should work with default options', () => { + const md = new MarkdownIt() + createContainerPlugin(md, 'test') + + expect(md.render(':::test\ncontent\n:::')).toContain('class="custom-container test"') + }) + + it('should work with custom render', () => { + const md = new MarkdownIt() + createContainerPlugin(md, 'test', { + before: () => `
`, + after: () => `
`, + }) + + expect(md.render(':::test\ncontent\n:::')).toContain('class="test"') + }) +}) diff --git a/plugins/plugin-md-power/__test__/demoWrapperPlugin.spec.ts b/plugins/plugin-md-power/__test__/demoWrapperPlugin.spec.ts new file mode 100644 index 00000000..a40601bf --- /dev/null +++ b/plugins/plugin-md-power/__test__/demoWrapperPlugin.spec.ts @@ -0,0 +1,29 @@ +import MarkdownIt from 'markdown-it' +import { describe, expect, it } from 'vitest' +import { demoWrapperPlugin } from '../src/node/container/demoWrapper.js' + +describe('demoWrapperPlugin', () => { + const md = new MarkdownIt().use(demoWrapperPlugin) + it('should work', () => { + const code = `\ +::: demo-wrapper +content +::: + +::: demo-wrapper title="test" +content +::: + +::: demo-wrapper no-padding img height="100px" + +[xxx](/img.jpg) +::: + +::: demo-wrapper no-padding img height="100" + +[xxx](/img.jpg) +::: +` + expect(md.render(code)).toMatchSnapshot() + }) +}) diff --git a/theme/src/client/styles/custom-block.css b/plugins/plugin-md-power/src/client/styles/demo-wrapper.css similarity index 64% rename from theme/src/client/styles/custom-block.css rename to plugins/plugin-md-power/src/client/styles/demo-wrapper.css index 09762c03..cfbf67a4 100644 --- a/theme/src/client/styles/custom-block.css +++ b/plugins/plugin-md-power/src/client/styles/demo-wrapper.css @@ -1,4 +1,3 @@ -/* --------------------- demo-wrapper ------------------------ */ .vp-doc .demo-wrapper { display: flex; flex-direction: column; @@ -139,74 +138,3 @@ margin: 40px 0; } } - -/* ---------------------------- Steps --------------------------- */ -.vp-doc .vp-steps { - margin: 16px 0; -} - -.vp-doc .vp-steps > ol, -.vp-doc .vp-steps > ul { - padding-inline-start: 0; - list-style: none; -} - -.vp-doc .vp-steps > ol > li, -.vp-doc .vp-steps > ul > li { - position: relative; - min-height: 22px; - padding-bottom: 1px; - padding-left: 44px; -} - -.vp-doc .vp-steps > ol > li::before, -.vp-doc .vp-steps > ul > li::before { - position: absolute; - inset-inline-start: 0; - top: 0; - width: 28px; - height: 28px; - font-size: 16px; - font-weight: 400; - line-height: 28px; - color: var(--vp-c-text-1); - text-align: center; - content: counter(list-item); - background-color: var(--vp-c-bg-soft); - border: solid 1px var(--vp-c-divider); - border-radius: 100%; - transition: var(--vp-t-color); - transition-property: color, background-color, border-color; -} - -.vp-doc .vp-steps > ol > li:not(:last-of-type)::after, -.vp-doc .vp-steps > ul > li:not(:last-of-type)::after { - position: absolute; - inset-inline-start: 14px; - top: 34px; - bottom: 5px; - width: 1px; - content: ""; - background-color: var(--vp-c-divider); - transition: background-color var(--vp-t-color); -} - -.vp-doc .vp-steps > ol > li > :first-child, -.vp-doc .vp-steps > ul > li > :first-child { - margin-top: 0; -} - -.vp-doc .vp-steps > ol > li > :first-child:where(h1,h2,h3,h4,h5,h6), -.vp-doc .vp-steps > ul > li > :first-child:where(h1,h2,h3,h4,h5,h6) { - padding-top: 0; - border-top: none; -} - -.vp-doc .vp-steps > ol > li > :first-child:where(p) { - line-height: 28px; -} - -.vp-doc .vp-steps > ol > li + li, -.vp-doc .vp-steps > ul > li + li { - margin-top: 1px; -} diff --git a/plugins/plugin-md-power/src/client/styles/index.css b/plugins/plugin-md-power/src/client/styles/index.css new file mode 100644 index 00000000..b20a88b0 --- /dev/null +++ b/plugins/plugin-md-power/src/client/styles/index.css @@ -0,0 +1,4 @@ +@charset "UTF-8"; + +@import url("./demo-wrapper.css"); +@import url("./steps.css"); diff --git a/plugins/plugin-md-power/src/client/styles/steps.css b/plugins/plugin-md-power/src/client/styles/steps.css new file mode 100644 index 00000000..604cd8c6 --- /dev/null +++ b/plugins/plugin-md-power/src/client/styles/steps.css @@ -0,0 +1,69 @@ +.vp-doc .vp-steps { + margin: 16px 0; +} + +.vp-doc .vp-steps > ol, +.vp-doc .vp-steps > ul { + padding-inline-start: 0; + list-style: none; +} + +.vp-doc .vp-steps > ol > li, +.vp-doc .vp-steps > ul > li { + position: relative; + min-height: 22px; + padding-bottom: 1px; + padding-left: 44px; +} + +.vp-doc .vp-steps > ol > li::before, +.vp-doc .vp-steps > ul > li::before { + position: absolute; + inset-inline-start: 0; + top: 0; + width: 28px; + height: 28px; + font-size: 16px; + font-weight: 400; + line-height: 28px; + color: var(--vp-c-text-1); + text-align: center; + content: counter(list-item); + background-color: var(--vp-c-bg-soft); + border: solid 1px var(--vp-c-divider); + border-radius: 100%; + transition: var(--vp-t-color); + transition-property: color, background-color, border-color; +} + +.vp-doc .vp-steps > ol > li:not(:last-of-type)::after, +.vp-doc .vp-steps > ul > li:not(:last-of-type)::after { + position: absolute; + inset-inline-start: 14px; + top: 34px; + bottom: 5px; + width: 1px; + content: ""; + background-color: var(--vp-c-divider); + transition: background-color var(--vp-t-color); +} + +.vp-doc .vp-steps > ol > li > :first-child, +.vp-doc .vp-steps > ul > li > :first-child { + margin-top: 0; +} + +.vp-doc .vp-steps > ol > li > :first-child:where(h1,h2,h3,h4,h5,h6), +.vp-doc .vp-steps > ul > li > :first-child:where(h1,h2,h3,h4,h5,h6) { + padding-top: 0; + border-top: none; +} + +.vp-doc .vp-steps > ol > li > :first-child:where(p) { + line-height: 28px; +} + +.vp-doc .vp-steps > ol > li + li, +.vp-doc .vp-steps > ul > li + li { + margin-top: 1px; +} diff --git a/plugins/plugin-md-power/src/node/container/align.ts b/plugins/plugin-md-power/src/node/container/align.ts index ec70fd41..0658759f 100644 --- a/plugins/plugin-md-power/src/node/container/align.ts +++ b/plugins/plugin-md-power/src/node/container/align.ts @@ -1,21 +1,12 @@ -import type Token from 'markdown-it/lib/token.mjs' import type { Markdown } from 'vuepress/markdown' -import container from 'markdown-it-container' +import { createContainerPlugin } from './createContainer.js' const alignList = ['left', 'center', 'right', 'justify'] export function alignPlugin(md: Markdown): void { for (const name of alignList) { - md.use(container, name, { - validate: (info: string) => info.trim() === name, - render: (tokens: Token[], idx: number): string => { - if (tokens[idx].nesting === 1) { - return `
` - } - else { - return '
' - } - }, + createContainerPlugin(md, name, { + before: () => `
`, }) } } diff --git a/plugins/plugin-md-power/src/node/container/card.ts b/plugins/plugin-md-power/src/node/container/card.ts new file mode 100644 index 00000000..bfae0dc8 --- /dev/null +++ b/plugins/plugin-md-power/src/node/container/card.ts @@ -0,0 +1,39 @@ +import type { Markdown } from 'vuepress/markdown' +import { resolveAttrs } from '.././utils/resolveAttrs.js' +import { createContainerPlugin } from './createContainer.js' + +interface CardAttrs { + title?: string + icon?: string +} + +export function cardPlugin(md: Markdown) { + /** + * ::: card title="xxx" icon="xxx" + * xxx + * ::: + */ + createContainerPlugin(md, 'card', { + before(info) { + const { attrs } = resolveAttrs(info) + const { title, icon } = attrs + return `` + }, + after: () => '', + }) + + /** + * :::: card-grid + * ::: card + * xxx + * ::: + * ::: card + * xxx + * ::: + * :::: + */ + createContainerPlugin(md, 'card-grid', { + before: () => '', + after: () => '', + }) +} diff --git a/plugins/plugin-md-power/src/node/container/createContainer.ts b/plugins/plugin-md-power/src/node/container/createContainer.ts new file mode 100644 index 00000000..9c9e251f --- /dev/null +++ b/plugins/plugin-md-power/src/node/container/createContainer.ts @@ -0,0 +1,23 @@ +import type Token from 'markdown-it/lib/token.mjs' +import type { Markdown } from 'vuepress/markdown' +import container from 'markdown-it-container' + +export interface ContainerOptions { + before?: (info: string, tokens: Token[], idx: number) => string + after?: (info: string, tokens: Token[], idx: number) => string +} + +export function createContainerPlugin(md: Markdown, type: string, options: ContainerOptions = {}) { + const render = (tokens: Token[], index: number): string => { + const token = tokens[index] + const info = token.info.trim().slice(type.length).trim() || '' + if (token.nesting === 1) { + return options.before?.(info, tokens, index) || `
` + } + else { + return options.after?.(info, tokens, index) || '
' + } + } + + md.use(container, type, { render }) +} diff --git a/plugins/plugin-md-power/src/node/container/demoWrapper.ts b/plugins/plugin-md-power/src/node/container/demoWrapper.ts new file mode 100644 index 00000000..446d3558 --- /dev/null +++ b/plugins/plugin-md-power/src/node/container/demoWrapper.ts @@ -0,0 +1,46 @@ +import type { Markdown } from 'vuepress/markdown' +import { resolveAttrs } from '.././utils/resolveAttrs.js' +import { createContainerPlugin } from './createContainer.js' + +interface DemoWrapperAttrs { + title?: string + img?: string + noPadding?: boolean + height?: string +} + +/** + * :::demo-wrapper img no-padding title="xxx" height="100px" + * ::: + */ +export function demoWrapperPlugin(md: Markdown): void { + createContainerPlugin(md, 'demo-wrapper', { + before: (info: string) => { + const { attrs } = resolveAttrs(info) + const wrapperClasses: string[] = ['demo-wrapper'] + let containerStyle = '' + if (attrs.title) + wrapperClasses.push('has-title') + + if (attrs.img) + wrapperClasses.push('only-img') + + if (attrs.noPadding) + wrapperClasses.push('no-padding') + + if (attrs.height) { + const h = Number.parseFloat(attrs.height) === Number(attrs.height) ? `${attrs.height}px` : attrs.height + containerStyle += `--demo-container-height: ${h};` + wrapperClasses.push('has-height') + } + + return `
+
+
+ ${attrs.title ? `

${attrs.title}

` : ''} +
+
\n` + }, + after: () => '
', + }) +} diff --git a/plugins/plugin-md-power/src/node/container/fileTree.ts b/plugins/plugin-md-power/src/node/container/fileTree.ts index ab56dddc..2d87e8b5 100644 --- a/plugins/plugin-md-power/src/node/container/fileTree.ts +++ b/plugins/plugin-md-power/src/node/container/fileTree.ts @@ -33,8 +33,6 @@ export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}) { return getFileIcon(filename, type) } - const validate = (info: string): boolean => info.trim().startsWith(type) - const render = (tokens: Token[], idx: number): string => { const { attrs } = resolveAttrs(tokens[idx].info.slice(type.length - 1)) @@ -81,7 +79,7 @@ export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}) { } } - md.use(container, type, { validate, render }) + md.use(container, type, { render }) } export function resolveTreeNodeInfo( diff --git a/plugins/plugin-md-power/src/node/container/index.ts b/plugins/plugin-md-power/src/node/container/index.ts index 63df661d..0c930bd7 100644 --- a/plugins/plugin-md-power/src/node/container/index.ts +++ b/plugins/plugin-md-power/src/node/container/index.ts @@ -3,10 +3,13 @@ import type { Markdown } from 'vuepress/markdown' import type { MarkdownPowerPluginOptions } from '../../shared/index.js' import { isPlainObject } from '@vuepress/helper' import { alignPlugin } from './align.js' +import { cardPlugin } from './card.js' import { codeTabs } from './codeTabs.js' +import { demoWrapperPlugin } from './demoWrapper.js' import { fileTreePlugin } from './fileTree.js' import { langReplPlugin } from './langRepl.js' import { npmToPlugins } from './npmTo.js' +import { stepsPlugin } from './steps.js' import { tabs } from './tabs.js' export async function containerPlugin( @@ -21,11 +24,22 @@ export async function containerPlugin( // ::: code-tabs codeTabs(md, options.codeTabs) + // ::: demo-wrapper + demoWrapperPlugin(md) + + // ::: steps + stepsPlugin(md) + + // ::: card / card-grid + cardPlugin(md) + if (options.npmTo) { + // ::: npm-to npmToPlugins(md, typeof options.npmTo === 'boolean' ? {} : options.npmTo) } if (options.repl) + // ::: rust-repl / go-repl / kotlin-repl await langReplPlugin(app, md, options.repl) if (options.fileTree) { diff --git a/plugins/plugin-md-power/src/node/container/npmTo.ts b/plugins/plugin-md-power/src/node/container/npmTo.ts index 0ed23d3a..f5159ef1 100644 --- a/plugins/plugin-md-power/src/node/container/npmTo.ts +++ b/plugins/plugin-md-power/src/node/container/npmTo.ts @@ -191,7 +191,6 @@ const MANAGERS_CONFIG: CommandConfigs = { export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void { const type = 'npm-to' - const validate = (info: string): boolean => info.trim().startsWith(type) const opt = isArray(options) ? { tabs: options } : options const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS @@ -214,7 +213,7 @@ export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void { return '' } - md.use(container, type, { validate, render }) + md.use(container, type, { render }) } function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPackageManager[]): string { diff --git a/plugins/plugin-md-power/src/node/container/steps.ts b/plugins/plugin-md-power/src/node/container/steps.ts new file mode 100644 index 00000000..ef3b8759 --- /dev/null +++ b/plugins/plugin-md-power/src/node/container/steps.ts @@ -0,0 +1,17 @@ +import type { Markdown } from 'vuepress/markdown' +import { createContainerPlugin } from './createContainer.js' + +/** + * :::steps + * 1. 步骤 1 + * xxx + * 2. 步骤 2 + * xxx + * 3. ... + * ::: + */ +export function stepsPlugin(md: Markdown) { + createContainerPlugin(md, 'steps', { + before: () => '
', + }) +} diff --git a/plugins/plugin-md-power/src/node/enhance/docsTitle.ts b/plugins/plugin-md-power/src/node/enhance/docsTitle.ts new file mode 100644 index 00000000..47a77d11 --- /dev/null +++ b/plugins/plugin-md-power/src/node/enhance/docsTitle.ts @@ -0,0 +1,45 @@ +import type { Markdown, MarkdownEnv } from 'vuepress/markdown' + +const REG_HEADING = /^#\s*?([^#\s].*)?\n/ + +/** + * 适配 主题的 文档页面标题,将 markdown 中的 h1 标题提取到 frontmatter 中,并将其删除, + * 以避免重复显示标题。 + */ +export function docsTitlePlugin(md: Markdown): void { + const render = md.render + md.render = (source, env: MarkdownEnv) => { + if (!env.filePathRelative) + return render(source, env) + + let { matter, content } = parseSource(source.trim()) + let title = '' + content = content.trim().replace(REG_HEADING, (_, match) => { + title = match.trim() + return '' + }) + source = `${matter}\n${content}` + const result = render(source, env) + if (title) { + env.frontmatter ??= {} + env.frontmatter.title ??= title + } + return result + } +} + +function parseSource(source: string) { + const char = '---' + + if (!source.startsWith(char)) { + return { matter: '', content: source } + } + else { + const end = source.indexOf(`\n${char}`) + const len = char.length + 1 + return { + matter: source.slice(0, end + len), + content: source.slice(end + len), + } + } +} diff --git a/plugins/plugin-md-power/src/node/plugin.ts b/plugins/plugin-md-power/src/node/plugin.ts index dcf805a9..f7b16333 100644 --- a/plugins/plugin-md-power/src/node/plugin.ts +++ b/plugins/plugin-md-power/src/node/plugin.ts @@ -3,6 +3,7 @@ import type { MarkdownPowerPluginOptions } from '../shared/index.js' import { addViteOptimizeDepsInclude } from '@vuepress/helper' import { containerPlugin } from './container/index.js' import { embedSyntaxPlugin } from './embed/index.js' +import { docsTitlePlugin } from './enhance/docsTitle.js' import { imageSizePlugin } from './enhance/imageSize.js' import { inlineSyntaxPlugin } from './inline/index.js' import { prepareConfigFile } from './prepareConfigFile.js' @@ -21,11 +22,16 @@ export function markdownPowerPlugin( extendsBundlerOptions(bundlerOptions, app) { if (options.repl) { - addViteOptimizeDepsInclude(bundlerOptions, app, ['shiki/core', 'shiki/wasm', 'shiki/engine/oniguruma']) + addViteOptimizeDepsInclude( + bundlerOptions, + app, + ['shiki/core', 'shiki/wasm', 'shiki/engine/oniguruma'], + ) } }, extendsMarkdown: async (md, app) => { + docsTitlePlugin(md) embedSyntaxPlugin(md, options) inlineSyntaxPlugin(md, options) diff --git a/plugins/plugin-md-power/src/node/prepareConfigFile.ts b/plugins/plugin-md-power/src/node/prepareConfigFile.ts index 817eb353..72f2da5e 100644 --- a/plugins/plugin-md-power/src/node/prepareConfigFile.ts +++ b/plugins/plugin-md-power/src/node/prepareConfigFile.ts @@ -71,6 +71,8 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp import { defineClientConfig } from 'vuepress/client' ${Array.from(imports.values()).join('\n')} +import '${CLIENT_FOLDER}styles/index.css' + export default defineClientConfig({ enhance({ router, app }) { ${Array.from(enhances.values()) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f37b1a07..f385c597 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,9 +361,6 @@ importers: '@vuepress/plugin-git': specifier: 2.0.0-rc.56 version: 2.0.0-rc.56(vuepress@2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))) - '@vuepress/plugin-markdown-container': - specifier: 2.0.0-rc.54 - version: 2.0.0-rc.54(vuepress@2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))) '@vuepress/plugin-markdown-hint': specifier: 2.0.0-rc.56 version: 2.0.0-rc.56(markdown-it@14.1.0)(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))(vuepress@2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))) @@ -2166,11 +2163,6 @@ packages: peerDependencies: vuepress: 2.0.0-rc.18 - '@vuepress/plugin-markdown-container@2.0.0-rc.54': - resolution: {integrity: sha512-00TzBHfBDd6nbZlmVRgWdmLb1MFcCg22FcD39n6gwgcFym9C/oZMDcdPnxsRT+sKPGqtxrlJYxKOXdp3Z8OJCA==} - peerDependencies: - vuepress: 2.0.0-rc.18 - '@vuepress/plugin-markdown-hint@2.0.0-rc.56': resolution: {integrity: sha512-qVOlqBIMjySormRde0uo/rILIC8BP59GIz+lRk8XpO5G92ejmJlRck27Pjrzm5NngR+pOonWfZ7yjGtT35U6nA==} peerDependencies: @@ -8192,12 +8184,6 @@ snapshots: execa: 9.4.1 vuepress: 2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3)) - '@vuepress/plugin-markdown-container@2.0.0-rc.54(vuepress@2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3)))': - dependencies: - '@types/markdown-it': 14.1.2 - markdown-it-container: 4.0.0 - vuepress: 2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3)) - '@vuepress/plugin-markdown-hint@2.0.0-rc.56(markdown-it@14.1.0)(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))(vuepress@2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@20.12.10)(jiti@1.21.6)(sass-embedded@1.80.3)(sass@1.80.3)(typescript@5.6.3)(yaml@2.5.1))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3)))': dependencies: '@mdit/plugin-alert': 0.13.1(markdown-it@14.1.0) diff --git a/theme/package.json b/theme/package.json index 50a5ffc0..d9725cfd 100644 --- a/theme/package.json +++ b/theme/package.json @@ -103,7 +103,6 @@ "@vuepress/plugin-comment": "2.0.0-rc.56", "@vuepress/plugin-docsearch": "2.0.0-rc.56", "@vuepress/plugin-git": "2.0.0-rc.56", - "@vuepress/plugin-markdown-container": "2.0.0-rc.54", "@vuepress/plugin-markdown-hint": "2.0.0-rc.56", "@vuepress/plugin-markdown-image": "2.0.0-rc.56", "@vuepress/plugin-markdown-math": "2.0.0-rc.56", diff --git a/theme/src/client/styles/index.css b/theme/src/client/styles/index.css index 101c356f..d23cfc3a 100644 --- a/theme/src/client/styles/index.css +++ b/theme/src/client/styles/index.css @@ -8,7 +8,6 @@ @import url("./utils.css"); @import url("./content.css"); @import url("./code.css"); -@import url("./custom-block.css"); @import url("./hint-container.css"); @import url("./twoslash.css"); @import url("./md-enhance.css"); diff --git a/theme/src/node/plugins/containerPlugins.ts b/theme/src/node/plugins/containerPlugins.ts deleted file mode 100644 index 5abf961e..00000000 --- a/theme/src/node/plugins/containerPlugins.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { Plugin } from 'vuepress/core' -import { markdownContainerPlugin as containerPlugin } from '@vuepress/plugin-markdown-container' - -export const customContainerPlugins: Plugin[] = [ - /** - * :::demo-wrapper img no-padding title="xxx" height="100px" - * ::: - */ - containerPlugin({ - type: 'demo-wrapper', - before(info) { - const title = resolveAttr(info, 'title') - const wrapperClasses: string[] = ['demo-wrapper'] - let containerStyle = '' - if (title) - wrapperClasses.push('has-title') - - if (info.includes('img')) - wrapperClasses.push('only-img') - - if (info.includes('no-padding')) - wrapperClasses.push('no-padding') - - const height = resolveAttr(info, 'height') - if (height) { - const h = Number.parseFloat(height) === Number(height) ? `${height}px` : height - containerStyle += `--demo-container-height: ${h};` - wrapperClasses.push('has-height') - } - - return `
-
-
- ${title ? `

${title}

` : ''} -
-
\n` - }, - after() { - return '
' - }, - }), - /** - * :::steps - * 1. 步骤 1 - * xxx - * 2. 步骤 2 - * xxx - * 3. ... - * ::: - */ - containerPlugin({ - type: 'steps', - before() { - return '
' - }, - after() { - return '
' - }, - }), - /** - * ::: card title="xxx" icon="xxx" - * xxx - * ::: - */ - containerPlugin({ - type: 'card', - before(info) { - const title = resolveAttr(info, 'title') - const icon = resolveAttr(info, 'icon') - return `` - }, - after() { - return '' - }, - }), - - /** - * :::: card-grid - * ::: card - * xxx - * ::: - * ::: card - * xxx - * ::: - * :::: - */ - containerPlugin({ - type: 'card-grid', - before() { - return '' - }, - after() { - return '' - }, - }), -] - -/** - * Resolve the specified attribute from token info - */ -function resolveAttr(info: string, attr: string): string | null { - // try to match specified attr mark - const pattern = `\\b${attr}\\s*=\\s*(?['"])(?.+?)\\k(\\s|$)` - const regex = new RegExp(pattern, 'i') - const match = info.match(regex) - - // return content if matched, null if not specified - return match?.groups?.content ?? null -} diff --git a/theme/src/node/plugins/getPlugins.ts b/theme/src/node/plugins/getPlugins.ts index 1b8d48e2..13c1f4fe 100644 --- a/theme/src/node/plugins/getPlugins.ts +++ b/theme/src/node/plugins/getPlugins.ts @@ -1,5 +1,6 @@ import type { App, PluginConfig } from 'vuepress/core' import type { PlumeThemePluginOptions } from '../../shared/index.js' +import { isPlainObject } from '@vuepress/helper' import { cachePlugin } from '@vuepress/plugin-cache' import { commentPlugin } from '@vuepress/plugin-comment' import { docsearchPlugin } from '@vuepress/plugin-docsearch' @@ -21,8 +22,6 @@ import { type MarkdownEnhancePluginOptions, mdEnhancePlugin } from 'vuepress-plu import { markdownPowerPlugin } from 'vuepress-plugin-md-power' import { resolveDocsearchOptions, resolveSearchOptions } from '../config/index.js' import { deleteAttrs } from '../utils/index.js' -import { customContainerPlugins } from './containerPlugins.js' -import { markdownTitlePlugin } from './markdown-title.js' export interface SetupPluginOptions { app: App @@ -40,12 +39,9 @@ export function getPlugins({ const isProd = app.env.isBuild const plugins: PluginConfig = [ - markdownTitlePlugin(), fontsPlugin(), contentUpdatePlugin(), markdownHintPlugin({ hint: true, alert: true, injectStyles: false }), - - ...customContainerPlugins, ] if (pluginOptions.readingTime !== false) { @@ -132,7 +128,7 @@ export function getPlugins({ plugins.push(watermarkPlugin({ delay: 300, enabled: true, - ...typeof pluginOptions.watermark === 'object' ? pluginOptions.watermark : {}, + ...isPlainObject(pluginOptions.watermark) ? pluginOptions.watermark : {}, })) } diff --git a/theme/src/node/plugins/index.ts b/theme/src/node/plugins/index.ts index 9c8f78a2..62d0b452 100644 --- a/theme/src/node/plugins/index.ts +++ b/theme/src/node/plugins/index.ts @@ -1,2 +1 @@ -export * from './containerPlugins.js' export * from './getPlugins.js' diff --git a/theme/src/node/plugins/markdown-title.ts b/theme/src/node/plugins/markdown-title.ts deleted file mode 100644 index af20b620..00000000 --- a/theme/src/node/plugins/markdown-title.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Plugin } from 'vuepress/core' -import type { MarkdownEnv } from 'vuepress/markdown' - -const REG_HEADING = /^#\s*?([^#\s].*)?\n/ - -export function markdownTitlePlugin(): Plugin { - return { - name: '@vuepress-plume/plugin-markdown-title', - - extendsMarkdown(md) { - const render = md.render - md.render = (source, env: MarkdownEnv) => { - if (!env.filePathRelative) - return render(source, env) - - let { matter, content } = parseSource(source.trim()) - let title = '' - content = content.trim().replace(REG_HEADING, (_, match) => { - title = match.trim() - return '' - }) - source = `${matter}\n${content}` - const result = render(source, env) - if (title) { - env.frontmatter ??= {} - env.frontmatter.title ??= title - } - return result - } - }, - } -} - -function parseSource(source: string) { - const char = '---' - - if (!source.startsWith(char)) { - return { matter: '', content: source } - } - else { - const end = source.indexOf(`\n${char}`) - const len = char.length + 1 - return { - matter: source.slice(0, end + len), - content: source.slice(end + len), - } - } -}