我的插件
vitepress
My Plugin
vitepress 使用 vite 构建的,所以我们可以根据 vite 的插件来实现。
VitePluginAutoSidebar
自动构建侧边栏,如本页面侧边栏。
- 侧边栏发生变化自动重启
- 文章发生变化不自动重启,提高速度。
使用方法
- category: 侧边栏目录
text
1---
2title: Plugin
3editLink: true
4navbar: true
5category: 'VitePress'
6---text
1import { defineConfig } from 'vitepress';
2import { demoblockPlugin, demoblockVitePlugin } from 'vitepress-theme-demoblock';
3import nav from './nav';
4import { tocPlugin } from '@mdit-vue/plugin-toc';
5import suggestionHighlightPlugin from './theme/custom-markdown.cjs';
6import { pagefindPlugin } from 'vitepress-plugin-pagefind';
7import VitePluginAutoSidebar from './plugin/VitePluginAutoSidebar';
8import { resolve } from 'path';
9
10// https://vitepress.dev/reference/site-config
11export default defineConfig({
12 vite: {
13 plugins: [demoblockVitePlugin(), VitePluginAutoSidebar(
14 [
15 resolve(__dirname, '../post/java'),
16 resolve(__dirname, '../post/guide'),
17 resolve(__dirname, '../post/design'),
18 resolve(__dirname, '../post/test'),
19 resolve(__dirname, '../components'),
20 resolve(__dirname, '../post/scm'),
21 resolve(__dirname, '../post/project'),
22 resolve(__dirname, '../post/ai'),
23 resolve(__dirname, '../post/other'),
24 resolve(__dirname, '../post/python'),
25 ],
26 ), pagefindPlugin({
27 btnPlaceholder: '搜索',
28 placeholder: '搜索文档',
29 emptyText: '空空如也',
30 heading: '共: {{searchResult}} 条结果',
31 })]
32
33 ...
34 });
35实现代码
text
1import { buildSidebars } from './sidebar';
2import { ViteDevServer } from 'vite';
3
4// https://www.vitejs.net/guide/api-plugin.html#config
5
6type Cache = {
7 fileName: string,
8 title: string,
9 category: string
10}
11
12const fileNameCache = new Map<string, Cache>();
13
14const titleCache = new Map<string, Cache>();
15
16
17function removeFileExtension(filePath) {
18 // 标准化路径分隔符为斜杠(/)
19 const normalizedPath = filePath.replace(//g, '/');
20
21 // 分割路径为数组
22 const parts = normalizedPath.split('/');
23
24 // 获取文件名
25 let fileName = parts.pop();
26
27 // 使用正则表达式匹配文件名中的后缀部分(假设后缀是由点(.)和至少一个字符组成)
28 const extensionMatch = fileName.match(/.([^.]+)$/);
29
30 // 如果匹配到后缀,则移除它
31 if (extensionMatch) {
32 fileName = fileName.slice(0, -extensionMatch[0].length);
33 }
34
35 return fileName;
36}
37
38function rebuildSide(config: any, paths: string[]) {
39 let autoSidebars = buildSidebars(paths);
40 Object.keys(autoSidebars).forEach(key => {
41 const sideList = autoSidebars[key];
42 for (let i = 0; i < sideList.length; i++) {
43 const side = sideList[i];
44 // 第一个目录打开,其他目录收缩
45 if (i > 0) {
46 side['collapsed'] = true;
47 }
48 const side2List = side['items'];
49 side2List.forEach((side2) => {
50 const category = side['text'];
51 const fileName = removeFileExtension(side2['link']);
52 const title = side2['text'];
53 const cache = {
54 category, title, fileName,
55 };
56 fileNameCache.set(fileName, cache);
57 titleCache.set(title, cache);
58 });
59 }
60 });
61 config.vitepress.site.themeConfig.sidebar = autoSidebars;
62 return config;
63}
64
65export default function VitePluginAutoSidebar(paths: []) {
66 return {
67 name: 'VitePluginAutoSidebar',
68 // 新增
69 config(config) {
70 return rebuildSide(config, paths);
71 },
72 configureServer: ({ watcher, restart }: ViteDevServer) => {
73 const fsWatcher = watcher.add('*.md');
74 fsWatcher.on('all', async (event, filePath) => {
75 if (event === 'addDir') return;
76 if (event === 'unlinkDir') return;
77 if (event == 'add') return;
78 let fileName = removeFileExtension(filePath);
79 console.log(`fs Watcher[${filePath}]:${event}:fileName:${fileName}`);
80 if (event === 'unlink' || event === 'change') {
81 let cache = fileNameCache.get(fileName);
82 try {
83 // 如果根据文件名
84 if (!titleCache.get(cache.title)) {
85 await restart();
86 }
87 } catch {
88 console.log('update sidebar failed');
89 }
90 }
91 });
92 },
93 };
94}
95Markdown 插件
使用方法
js
1import { defineConfig } from 'vitepress';
2import suggestionHighlightPlugin from './theme/custom-markdown.cjs';
3// https://vitepress.dev/reference/site-config
4export default defineConfig({
5 markdown: {
6 lineNumbers: true,
7 config: (md) => {
8 md.use(suggestionHighlightPlugin);
9 },
10 },
11 })实现原理
= 红色高亮 =
- 语法:
*= 红色高亮 =*
js
1// custom-markdown.js
2module.exports = function suggestionPlugin(md) {
3 // 使用 markdown-it 内置的 parser 规则注册新的 inline 规则
4 md.inline.ruler.before('emphasis', 'suggestion', function (state, silent) {
5 const start = state.pos;
6 const markerStart = '*=';
7 const markerEnd = '=*';
8
9 // 检查当前位置是否为 *= 起始标记
10 if (state.src.startsWith(markerStart, start)) {
11 const end = state.src.indexOf(markerEnd, start + markerStart.length);
12 if (end === -1) return false; // 如果没有找到 =* 结束标记,返回 false
13
14 if (!silent) {
15 // 插入 suggestion_open token
16 const token = state.push('suggestion_open', 'span', 1);
17 token.attrs = [['style', 'color: #e64b7d;font-weight: bold;background-color: var(--vp-code-bg);border-radius: 4px;
18' +
19 ' padding: 3px 6px;']];
20
21 // 获取 *= 和 =* 之间的内容
22 const contentToken = state.push('text', '', 0);
23 contentToken.content = state.src.slice(start + markerStart.length, end).trim();
24
25 // 插入 suggestion_close token
26 state.push('suggestion_close', 'span', -1);
27 }
28
29 // 更新位置到结束标记之后
30 state.pos = end + markerEnd.length;
31 return true;
32 }
33
34 return false;
35 });
36
37 // 渲染 suggestion_open 标签
38 md.renderer.rules.suggestion_open = function (tokens, idx) {
39 return `<span style="${tokens[idx].attrs[0][1]}">`;
40 };
41
42 // 渲染 suggestion_close 标签
43 md.renderer.rules.suggestion_close = function () {
44 return '</span>';
45 };
46};
47