perf: improve markdown container plugin (#320)

* perf: improve markdown container plugin

* chore: add unit test

* chore: improve styles
This commit is contained in:
pengzhanbo 2024-10-31 01:42:54 +08:00 committed by GitHub
parent 611f625185
commit 30d707036e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 357 additions and 270 deletions

View File

@ -0,0 +1,33 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`demoWrapperPlugin > should work 1`] = `
"<div class="demo-wrapper">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" >
<p>content</p>
</div></div><div class="demo-wrapper has-title">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
<h4 class="demo-title"><p>test</p></h4>
</div>
<div class="demo-container" >
<p>content</p>
</div></div><div class="demo-wrapper only-img no-padding has-height">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" style="--demo-container-height: 100px;">
<p><a href="/img.jpg">xxx</a></p>
</div></div><div class="demo-wrapper only-img no-padding has-height">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" style="--demo-container-height: 100px;">
<p><a href="/img.jpg">xxx</a></p>
</div></div>"
`;

View File

@ -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: () => `<div class="test">`,
after: () => `</div>`,
})
expect(md.render(':::test\ncontent\n:::')).toContain('class="test"')
})
})

View File

@ -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()
})
})

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
@charset "UTF-8";
@import url("./demo-wrapper.css");
@import url("./steps.css");

View File

@ -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;
}

View File

@ -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 `<div style="text-align:${name}">`
}
else {
return '</div>'
}
},
createContainerPlugin(md, name, {
before: () => `<div style="text-align:${name}">`,
})
}
}

View File

@ -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<CardAttrs>(info)
const { title, icon } = attrs
return `<VPCard${title ? ` title="${title}"` : ''}${icon ? ` icon="${icon}"` : ''}>`
},
after: () => '</VPCard>',
})
/**
* :::: card-grid
* ::: card
* xxx
* :::
* ::: card
* xxx
* :::
* ::::
*/
createContainerPlugin(md, 'card-grid', {
before: () => '<VPCardGrid>',
after: () => '</VPCardGrid>',
})
}

View File

@ -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) || `<div class="custom-container ${type}">`
}
else {
return options.after?.(info, tokens, index) || '</div>'
}
}
md.use(container, type, { render })
}

View File

@ -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<DemoWrapperAttrs>(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 `<div class="${wrapperClasses.join(' ')}">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
${attrs.title ? `<h4 class="demo-title"><p>${attrs.title}</p></h4>` : ''}
</div>
<div class="demo-container" ${containerStyle ? `style="${containerStyle}"` : ''}>\n`
},
after: () => '</div></div>',
})
}

View File

@ -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<FileTreeAttrs>(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(

View File

@ -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) {

View File

@ -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 {

View File

@ -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: () => '<div class="vp-steps">',
})
}

View File

@ -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),
}
}
}

View File

@ -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)

View File

@ -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())

14
pnpm-lock.yaml generated
View File

@ -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)

View File

@ -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",

View File

@ -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");

View File

@ -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 `<div class="${wrapperClasses.join(' ')}">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
${title ? `<h4 class="demo-title"><p>${title}</p></h4>` : ''}
</div>
<div class="demo-container" ${containerStyle ? `style="${containerStyle}"` : ''}>\n`
},
after() {
return '</div></div>'
},
}),
/**
* :::steps
* 1. 1
* xxx
* 2. 2
* xxx
* 3. ...
* :::
*/
containerPlugin({
type: 'steps',
before() {
return '<div class="vp-steps">'
},
after() {
return '</div>'
},
}),
/**
* ::: card title="xxx" icon="xxx"
* xxx
* :::
*/
containerPlugin({
type: 'card',
before(info) {
const title = resolveAttr(info, 'title')
const icon = resolveAttr(info, 'icon')
return `<VPCard${title ? ` title="${title}"` : ''}${icon ? ` icon="${icon}"` : ''}>`
},
after() {
return '</VPCard>'
},
}),
/**
* :::: card-grid
* ::: card
* xxx
* :::
* ::: card
* xxx
* :::
* ::::
*/
containerPlugin({
type: 'card-grid',
before() {
return '<VPCardGrid>'
},
after() {
return '</VPCardGrid>'
},
}),
]
/**
* 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*(?<quote>['"])(?<content>.+?)\\k<quote>(\\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
}

View File

@ -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 : {},
}))
}

View File

@ -1,2 +1 @@
export * from './containerPlugins.js'
export * from './getPlugins.js'

View File

@ -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),
}
}
}