@@ -3,35 +3,71 @@ import { readFileSync } from "node:fs";
3
3
import { basename } from "node:path" ;
4
4
import matter from "gray-matter" ;
5
5
6
+ const specialPrefix = `return (_openBlock(), _createElementBlock("div", null, [` ;
7
+ const createStaticVNodeTag = `_createStaticVNode("` ;
8
+
6
9
export default function VitePluginVitePressMdH1 ( ) : Plugin & { name : string } {
7
10
return {
8
11
name : "vite-plugin-vitepress-md-h1" ,
9
12
transform : ( code : string , id : string ) => {
10
13
if ( ! id . endsWith ( ".md" ) ) return code ;
11
14
12
15
const content = readFileSync ( id , "utf-8" ) ;
13
-
14
16
const { data = { } , content : mdContent } = matter ( content , { } ) ;
17
+ // 如果已经存在一级标题,则不需要往下处理
18
+ if ( mdContent . trimStart ( ) . split ( / \r ? \n / ) [ 0 ] . startsWith ( "# " ) ) return code ;
19
+
20
+ // 获取文章标题,如果为目录,则默认为文件夹名。如果为 md 文件,则尝试获取 frontmatter 中的 title,否则为文件名为标题
15
21
const title = data . title || getMdFileTitle ( basename ( id ) ) || "" ;
22
+ const titleId = formatSpecialStr ( title ) ;
23
+
24
+ // 将 " 替换为 \",因为 " 会导致页面解析失败
25
+ const finalTitle = title . replace ( / " + / g, '\\"' ) ;
26
+ // 提前截取 code,防止 code 太长导致 replace 性能边差
27
+ const newCode = code . split ( createStaticVNodeTag ) ;
28
+
29
+ if ( newCode . length === 2 && newCode [ 0 ] . includes ( specialPrefix ) ) {
30
+ // 第一个 replace 先将 _cache 的下标加 1,第二个 replace 把 h1 标题加到 _cache[0] 里
31
+ return newCode [ 0 ]
32
+ . replace ( / _ c a c h e \[ ( \d + ) \] / g, ( _ , p1 ) => {
33
+ const newIndex = parseInt ( p1 , 10 ) + 1 ;
34
+ return `_cache[${ newIndex } ]` ;
35
+ } )
36
+ . replace (
37
+ specialPrefix ,
38
+ `${ specialPrefix }
39
+ _cache[0] || (_cache[0] = _createElementVNode("h1", {
40
+ id: "${ titleId } ",
41
+ tabindex: "-1"
42
+ }, [
43
+ _createTextVNode("${ finalTitle } "),
44
+ _createElementVNode("a", {
45
+ class: "header-anchor",
46
+ href: "#${ titleId } ",
47
+ "aria-label": "Permalink to \\"${ finalTitle } \\""
48
+ }, "")
49
+ ], -1 /* HOISTED */)),
50
+ `
51
+ )
52
+ . concat ( createStaticVNodeTag + newCode [ 1 ] ) ; // 最后拼接上截取的代码
53
+ }
16
54
17
- return mdContent . trimStart ( ) . split ( / \r ? \n / ) [ 0 ] . startsWith ( "# " )
18
- ? code
19
- : code . replace (
20
- `_createStaticVNode("` ,
21
- `_createStaticVNode("<h1 id=\\"${ title } \\" tabindex=\\"-1\\">${ title } <a class=\\"header-anchor\\" href=\\"#${ title } \\" aria-label=\\"Permalink to "${ title } "\\"></a></h1>`
22
- ) ;
55
+ return code . replace (
56
+ createStaticVNodeTag ,
57
+ `${ createStaticVNodeTag } <h1 id=\\"${ titleId } \\" tabindex=\\"-1\\">${ finalTitle } <a class=\\"header-anchor\\" href=\\"#${ titleId } \\" aria-label=\\"Permalink to "${ finalTitle } "\\"></a></h1>`
58
+ ) ;
23
59
} ,
24
60
} ;
25
61
}
26
62
27
63
/**
28
- * 解析文件名
64
+ * 获取实际的文件名
29
65
*
30
66
* @param filename 文件名
31
67
*/
32
68
export const getMdFileTitle = ( filename : string ) => {
33
- // 文章标题,如果为目录,则默认为文件夹名。如果为 md 文件,则尝试获取 frontmatter 中的 title,否则为文件名为标题
34
69
let title = "" ;
70
+ // 如果文件名带序号,如【1.xx.md】,则取 xx
35
71
const fileNameArr = filename . split ( "." ) ;
36
72
37
73
if ( fileNameArr . length === 2 ) title = fileNameArr [ 0 ] ;
@@ -44,3 +80,14 @@ export const getMdFileTitle = (filename: string) => {
44
80
45
81
return title ;
46
82
} ;
83
+
84
+ /**
85
+ * 格式化字符串
86
+ * @param str 字符串
87
+ */
88
+ export const formatSpecialStr = ( str : string ) : string => {
89
+ return str
90
+ . toLowerCase ( )
91
+ . replace ( / [ \s + ] / g, "" ) // 清除空格
92
+ . replace ( / [ ' " ` * ] + / g, "" ) ; // 去除反引号、星号等 Markdown 语法字符
93
+ } ;
0 commit comments