@tnotesjs/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @tnotesjs/core might be problematic. Click here for more details.
- package/README.md +105 -0
- package/dist/chunk-K3X5OP3N.js +1532 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4199 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +9 -0
- package/package.json +74 -0
- package/types/config.ts +61 -0
- package/types/index.ts +11 -0
- package/types/note.ts +33 -0
- package/vitepress/assets/icons/icon__check.svg +3 -0
- package/vitepress/assets/icons/icon__clipboard.svg +8 -0
- package/vitepress/assets/icons/icon__close.svg +1 -0
- package/vitepress/assets/icons/icon__collapse.svg +1 -0
- package/vitepress/assets/icons/icon__confirm.svg +1 -0
- package/vitepress/assets/icons/icon__copy.svg +4 -0
- package/vitepress/assets/icons/icon__focus.svg +1 -0
- package/vitepress/assets/icons/icon__fold.svg +3 -0
- package/vitepress/assets/icons/icon__folder.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -0
- package/vitepress/assets/icons/icon__github.svg +4 -0
- package/vitepress/assets/icons/icon__mindmap.svg +1 -0
- package/vitepress/assets/icons/icon__next.svg +1 -0
- package/vitepress/assets/icons/icon__number_gray.svg +1 -0
- package/vitepress/assets/icons/icon__number_purple.svg +1 -0
- package/vitepress/assets/icons/icon__prev.svg +1 -0
- package/vitepress/assets/icons/icon__restore.svg +1 -0
- package/vitepress/assets/icons/icon__rotate.svg +4 -0
- package/vitepress/assets/icons/icon__search.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -0
- package/vitepress/assets/icons/icon__totop.svg +6 -0
- package/vitepress/assets/icons/icon__vscode.svg +6 -0
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -0
- package/vitepress/assets/icons/index.ts +38 -0
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -0
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -0
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -0
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -0
- package/vitepress/components/Discussions/Discussions.module.scss +32 -0
- package/vitepress/components/Discussions/Discussions.vue +211 -0
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -0
- package/vitepress/components/EnWordList/EnWordList.vue +543 -0
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -0
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -0
- package/vitepress/components/Footprints/Footprints.module.scss +93 -0
- package/vitepress/components/Footprints/Footprints.vue +377 -0
- package/vitepress/components/Layout/AboutModal.module.scss +233 -0
- package/vitepress/components/Layout/AboutModal.vue +105 -0
- package/vitepress/components/Layout/AboutPanel.vue +266 -0
- package/vitepress/components/Layout/ContentCollapse.vue +603 -0
- package/vitepress/components/Layout/CustomSidebar.vue +605 -0
- package/vitepress/components/Layout/DocBeforeControls.vue +139 -0
- package/vitepress/components/Layout/DocFooter.vue +225 -0
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -0
- package/vitepress/components/Layout/ImagePreview.vue +281 -0
- package/vitepress/components/Layout/Layout.module.scss +661 -0
- package/vitepress/components/Layout/Layout.vue +542 -0
- package/vitepress/components/Layout/NoteStatus.vue +140 -0
- package/vitepress/components/Layout/SidebarItems.vue +263 -0
- package/vitepress/components/Layout/SidebarNavBefore.vue +92 -0
- package/vitepress/components/Layout/Swiper.vue +167 -0
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -0
- package/vitepress/components/Layout/ToggleFullContent.vue +34 -0
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -0
- package/vitepress/components/Layout/ToggleSidebar.vue +35 -0
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -0
- package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -0
- package/vitepress/components/Layout/composables/useNoteSave.ts +173 -0
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -0
- package/vitepress/components/Layout/composables/useRedirect.ts +110 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -0
- package/vitepress/components/Layout/homeReadme.data.ts +124 -0
- package/vitepress/components/LoadingPage/LoadingPage.vue +192 -0
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -0
- package/vitepress/components/MarkMap/MarkMap.vue +404 -0
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -0
- package/vitepress/components/Mermaid/Mermaid.vue +364 -0
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -0
- package/vitepress/components/NotesTable/NotesTable.vue +98 -0
- package/vitepress/components/NotesTable/README.md +67 -0
- package/vitepress/components/Settings/Settings.module.scss +433 -0
- package/vitepress/components/Settings/Settings.vue +306 -0
- package/vitepress/components/SidebarCard/MindMapView.vue +483 -0
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -0
- package/vitepress/components/SidebarCard/SidebarCard.vue +948 -0
- package/vitepress/components/Tooltip/Tooltip.vue +70 -0
- package/vitepress/components/constants.ts +91 -0
- package/vitepress/components/notesConfig.data.ts +73 -0
- package/vitepress/components/sidebar.data.ts +59 -0
- package/vitepress/components/tnotes-config.data.ts +21 -0
- package/vitepress/components/utils.ts +26 -0
- package/vitepress/config/index.ts +126 -0
- package/vitepress/configs/constants.ts +26 -0
- package/vitepress/configs/head.config.ts +25 -0
- package/vitepress/configs/index.ts +9 -0
- package/vitepress/configs/markdown-it.d.ts +23 -0
- package/vitepress/configs/markdown.config.ts +366 -0
- package/vitepress/configs/theme.config.ts +108 -0
- package/vitepress/plugins/buildProgressPlugin.ts +390 -0
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -0
- package/vitepress/plugins/renameNotePlugin.ts +60 -0
- package/vitepress/plugins/updateConfigPlugin.ts +63 -0
- package/vitepress/theme/index.ts +95 -0
- package/vitepress/theme/styles/base.scss +50 -0
- package/vitepress/theme/styles/components/404.scss +31 -0
- package/vitepress/theme/styles/components/collapse.scss +175 -0
- package/vitepress/theme/styles/components/markmap.scss +101 -0
- package/vitepress/theme/styles/components/swiper.scss +255 -0
- package/vitepress/theme/styles/index.scss +25 -0
- package/vitepress/theme/styles/layout.scss +62 -0
- package/vitepress/theme/styles/utilities.scss +39 -0
- package/vitepress/theme/styles/vitepress-override.scss +25 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .vitepress/config/markdown.config.ts
|
|
3
|
+
*
|
|
4
|
+
* Markdown 配置
|
|
5
|
+
*/
|
|
6
|
+
import MarkdownIt from 'markdown-it'
|
|
7
|
+
import markdownItContainer from 'markdown-it-container'
|
|
8
|
+
import mila from 'markdown-it-link-attributes'
|
|
9
|
+
import markdownItTaskLists from 'markdown-it-task-lists'
|
|
10
|
+
import { MarkdownOptions } from 'vitepress'
|
|
11
|
+
import { generateAnchor } from '../../utils'
|
|
12
|
+
import fs from 'fs'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 辅助函数:HTML 转义
|
|
17
|
+
*/
|
|
18
|
+
function esc(s = '') {
|
|
19
|
+
return s.replace(
|
|
20
|
+
/[&<>"']/g,
|
|
21
|
+
(ch) =>
|
|
22
|
+
({
|
|
23
|
+
'&': '&',
|
|
24
|
+
'<': '<',
|
|
25
|
+
'>': '>',
|
|
26
|
+
'"': '"',
|
|
27
|
+
"'": ''',
|
|
28
|
+
}[ch]!)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 简化的 Mermaid 处理函数
|
|
34
|
+
*/
|
|
35
|
+
const simpleMermaidMarkdown = (md: MarkdownIt) => {
|
|
36
|
+
const fence = md.renderer.rules.fence
|
|
37
|
+
? md.renderer.rules.fence.bind(md.renderer.rules)
|
|
38
|
+
: () => ''
|
|
39
|
+
|
|
40
|
+
md.renderer.rules.fence = (tokens, index, options, env, slf) => {
|
|
41
|
+
const token = tokens[index]
|
|
42
|
+
|
|
43
|
+
// 检查是否为 mermaid 代码块
|
|
44
|
+
if (token.info.trim() === 'mermaid') {
|
|
45
|
+
try {
|
|
46
|
+
const key = `mermaid-${Date.now()}-${Math.random()
|
|
47
|
+
.toString(36)
|
|
48
|
+
.substr(2, 9)}`
|
|
49
|
+
const content = token.content
|
|
50
|
+
return `<Mermaid id="${key}" graph="${encodeURIComponent(content)}" />`
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return `<pre>${err}</pre>`
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 允许使用 mmd 标记显示 Mermaid 代码本身
|
|
57
|
+
if (token.info.trim() === 'mmd') {
|
|
58
|
+
tokens[index].info = 'mermaid'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return fence(tokens, index, options, env, slf)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* MarkMap 容器配置
|
|
67
|
+
*/
|
|
68
|
+
function configureMarkMapContainer(md: MarkdownIt) {
|
|
69
|
+
// 先保留 container 的解析(负责把 ```markmap ``` 识别成 container tokens)
|
|
70
|
+
// 但让它本身不输出任何 HTML(render 返回空)
|
|
71
|
+
md.use(markdownItContainer, 'markmap', {
|
|
72
|
+
marker: '`',
|
|
73
|
+
validate(params: string) {
|
|
74
|
+
// 接受 "markmap", "markmap{...}" 或 "markmap key=val ..." 等写法
|
|
75
|
+
const p = (params || '').trim()
|
|
76
|
+
return p.startsWith('markmap')
|
|
77
|
+
},
|
|
78
|
+
render() {
|
|
79
|
+
return ''
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// 在 core 阶段把整个 container 区间替换成一个 html_block(MarkMap 组件标签)
|
|
84
|
+
// 这样渲染时就只输出 <MarkMap ...>,中间的列表 token 已被移除
|
|
85
|
+
md.core.ruler.after('block', 'tn_replace_markmap_container', (state) => {
|
|
86
|
+
const src = state.env.source || ''
|
|
87
|
+
const lines = src.split('\n')
|
|
88
|
+
const tokens = state.tokens
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
91
|
+
const t = tokens[i]
|
|
92
|
+
if (t.type === 'container_markmap_open') {
|
|
93
|
+
// 找到对应的 close token
|
|
94
|
+
let j = i + 1
|
|
95
|
+
while (
|
|
96
|
+
j < tokens.length &&
|
|
97
|
+
tokens[j].type !== 'container_markmap_close'
|
|
98
|
+
)
|
|
99
|
+
j++
|
|
100
|
+
if (j >= tokens.length) continue // safety
|
|
101
|
+
|
|
102
|
+
// 使用 token.map 提取源文件对应行(open.token.map 存着 container 起止行)
|
|
103
|
+
const open = t
|
|
104
|
+
const startLine = open.map ? open.map[0] + 1 : null
|
|
105
|
+
const endLine = open.map ? open.map[1] - 1 : null
|
|
106
|
+
|
|
107
|
+
// 1) 从开头 fence 行解析参数(支持 `{a=1 b="x"}`、`a=1 b="x"`,并支持单个数字 shorthand)
|
|
108
|
+
let params: { [key: string]: any; initialExpandLevel?: number } = {}
|
|
109
|
+
|
|
110
|
+
if (open.map && typeof open.map[0] === 'number') {
|
|
111
|
+
const openLine = (lines[open.map[0]] || '').trim()
|
|
112
|
+
let paramPart = ''
|
|
113
|
+
|
|
114
|
+
// 优先匹配大括号形式 ```markmap{...}
|
|
115
|
+
const braceMatch = openLine.match(/\{([^}]*)\}/)
|
|
116
|
+
if (braceMatch) {
|
|
117
|
+
paramPart = braceMatch[1].trim()
|
|
118
|
+
} else {
|
|
119
|
+
// 否则尝试去掉前缀 ``` 和 markmap,剩下的作为参数部分
|
|
120
|
+
const after = openLine.replace(/^`+\s*/, '')
|
|
121
|
+
if (after.startsWith('markmap')) {
|
|
122
|
+
paramPart = after.slice('markmap'.length).trim()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (paramPart) {
|
|
127
|
+
// 使用正则按 token 切分:保持用引号包裹的片段为单个 token(支持包含空格)
|
|
128
|
+
const tokenArr = paramPart.match(/"[^"]*"|'[^']*'|\S+/g) || []
|
|
129
|
+
|
|
130
|
+
// 如果第一个 token 是纯数字,把它当作 initialExpandLevel
|
|
131
|
+
let startIdx = 0
|
|
132
|
+
if (tokenArr.length > 0 && /^\d+$/.test(tokenArr[0] as string)) {
|
|
133
|
+
params.initialExpandLevel = Number(tokenArr[0])
|
|
134
|
+
startIdx = 1
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 解析剩余 token 为 key=value(支持 key=val 或 key:val)
|
|
138
|
+
for (let k = startIdx; k < tokenArr.length; k++) {
|
|
139
|
+
const pair = tokenArr[k]
|
|
140
|
+
if (!pair) continue
|
|
141
|
+
const m = pair.match(/^([^=:\s]+)\s*(=|:)\s*(.+)$/)
|
|
142
|
+
if (m) {
|
|
143
|
+
const key = m[1]
|
|
144
|
+
let val = m[3]
|
|
145
|
+
|
|
146
|
+
// 去除外层引号(若存在)
|
|
147
|
+
if (
|
|
148
|
+
(/^".*"$/.test(val) && val.length >= 2) ||
|
|
149
|
+
(/^'.*'$/.test(val) && val.length >= 2)
|
|
150
|
+
) {
|
|
151
|
+
val = val.slice(1, -1)
|
|
152
|
+
} else if (/^\d+$/.test(val)) {
|
|
153
|
+
// 纯数字转字符串
|
|
154
|
+
val = String(Number(val))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
params[key] = val
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2) 提取内容(支持文件引用语法 `<<< ./path/to/file.md`)
|
|
164
|
+
let content = ''
|
|
165
|
+
if (startLine !== null && endLine !== null) {
|
|
166
|
+
for (let k = startLine; k <= endLine && k < lines.length; k++) {
|
|
167
|
+
content += lines[k] + '\n'
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
// 回退:如果没有 map 信息,尝试用中间 tokens 拼接文本
|
|
171
|
+
for (let k = i + 1; k < j; k++) {
|
|
172
|
+
content += tokens[k].content || ''
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- 检查第一非空行是否为引用语法 ---
|
|
177
|
+
const firstNonEmptyLine =
|
|
178
|
+
(content || '').split('\n').find((ln) => ln.trim() !== '') || ''
|
|
179
|
+
const refMatch = firstNonEmptyLine.trim().match(/^<<<\s*(.+)$/)
|
|
180
|
+
if (refMatch) {
|
|
181
|
+
// 提取引用路径,支持引号包裹
|
|
182
|
+
let refRaw = refMatch[1].trim().replace(/^['"]|['"]$/g, '')
|
|
183
|
+
|
|
184
|
+
// 尝试同步读取文件内容(兼容常见 Node 环境)
|
|
185
|
+
try {
|
|
186
|
+
// 尝试根据当前 markdown 文件位置解析相对路径
|
|
187
|
+
const env = state.env || {}
|
|
188
|
+
const possibleRel =
|
|
189
|
+
env.relativePath || env.path || env.filePath || env.file || ''
|
|
190
|
+
let refFullPath = refRaw
|
|
191
|
+
|
|
192
|
+
if (!path.isAbsolute(refRaw)) {
|
|
193
|
+
if (possibleRel) {
|
|
194
|
+
// 将 relativePath 视作相对于项目根的路径(例如 'notes/foo/bar.md'),取其目录
|
|
195
|
+
const currentDir = path.dirname(possibleRel)
|
|
196
|
+
// 解析到 process.cwd()
|
|
197
|
+
refFullPath = path.resolve(process.cwd(), currentDir, refRaw)
|
|
198
|
+
} else {
|
|
199
|
+
// 没有相对文件信息,则相对于项目根解析
|
|
200
|
+
refFullPath = path.resolve(process.cwd(), refRaw)
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// 绝对路径直接使用(按系统路径)
|
|
204
|
+
refFullPath = refRaw
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// console.log('refFullPath:', refFullPath)
|
|
208
|
+
const fileContent = fs.readFileSync(refFullPath, 'utf-8')
|
|
209
|
+
content = fileContent
|
|
210
|
+
} catch (err) {
|
|
211
|
+
// 读取失败:将错误写入 content 以便排查(不会让流程直接崩溃)
|
|
212
|
+
const errorMsg = err instanceof Error ? err.message : String(err)
|
|
213
|
+
content = `Failed to load referenced file: ${esc(
|
|
214
|
+
String(refRaw)
|
|
215
|
+
)}\n\nError: ${esc(errorMsg)}`
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 3) 构造组件标签并把参数注入为 props
|
|
220
|
+
const encodedContent = encodeURIComponent(content.trim())
|
|
221
|
+
let propsStr = `content="${encodedContent}"`
|
|
222
|
+
|
|
223
|
+
for (const [k, v] of Object.entries(params)) {
|
|
224
|
+
if (typeof v === 'number' || /^\d+$/.test(String(v))) {
|
|
225
|
+
propsStr += ` :${k}="${v}"`
|
|
226
|
+
} else {
|
|
227
|
+
const safe = String(v).replace(/"/g, '"')
|
|
228
|
+
propsStr += ` ${k}="${safe}"`
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const html = `<MarkMap ${propsStr}></MarkMap>\n`
|
|
233
|
+
|
|
234
|
+
// 创建 html_block token
|
|
235
|
+
const htmlToken = new state.Token('html_block', '', 0)
|
|
236
|
+
htmlToken.content = html
|
|
237
|
+
|
|
238
|
+
// 用单个 html_token 替换 open..close 区间
|
|
239
|
+
tokens.splice(i, j - i + 1, htmlToken as any)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return true
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Swiper 容器配置
|
|
249
|
+
*/
|
|
250
|
+
function configureSwiperContainer(md: MarkdownIt) {
|
|
251
|
+
let __tn_swiper_uid = 0
|
|
252
|
+
|
|
253
|
+
interface TN_RULES_STACK_ITEM {
|
|
254
|
+
image: any
|
|
255
|
+
pOpen: any
|
|
256
|
+
pClose: any
|
|
257
|
+
}
|
|
258
|
+
let __tn_rules_stack: Array<TN_RULES_STACK_ITEM> = []
|
|
259
|
+
|
|
260
|
+
// 每个文档渲染前重置计数器
|
|
261
|
+
md.core.ruler.before('block', 'tn_swiper_reset_uid', () => {
|
|
262
|
+
__tn_swiper_uid = 0
|
|
263
|
+
__tn_rules_stack = []
|
|
264
|
+
return true
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
md.use(markdownItContainer, 'swiper', {
|
|
268
|
+
render: (tokens: any[], idx: number) => {
|
|
269
|
+
if (tokens[idx].nesting === 1) {
|
|
270
|
+
// 进容器:保存原规则 & 局部覆盖
|
|
271
|
+
__tn_rules_stack.push({
|
|
272
|
+
image: md.renderer.rules.image,
|
|
273
|
+
pOpen: md.renderer.rules.paragraph_open,
|
|
274
|
+
pClose: md.renderer.rules.paragraph_close,
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
md.renderer.rules.paragraph_open = () => ''
|
|
278
|
+
md.renderer.rules.paragraph_close = () => ''
|
|
279
|
+
md.renderer.rules.image = (tokens: any[], i: number) => {
|
|
280
|
+
const token: any = tokens[i]
|
|
281
|
+
const src = token.attrGet('src') || ''
|
|
282
|
+
const alt = token.content || ''
|
|
283
|
+
const title = alt && alt.trim() ? alt : 'img'
|
|
284
|
+
return `<div class="swiper-slide" data-title="${esc(
|
|
285
|
+
title
|
|
286
|
+
)}"><img src="${esc(src)}" alt="${esc(alt)}"></div>`
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const id = `tn-swiper-${++__tn_swiper_uid}`
|
|
290
|
+
return `
|
|
291
|
+
<div class="tn-swiper" data-swiper-id="${id}">
|
|
292
|
+
<div class="tn-swiper-tabs"></div>
|
|
293
|
+
<div class="swiper-container">
|
|
294
|
+
<div class="swiper-wrapper">
|
|
295
|
+
`
|
|
296
|
+
} else {
|
|
297
|
+
// 出容器:恢复原规则并收尾
|
|
298
|
+
const prev: TN_RULES_STACK_ITEM = __tn_rules_stack.pop() || {
|
|
299
|
+
image: null,
|
|
300
|
+
pOpen: null,
|
|
301
|
+
pClose: null,
|
|
302
|
+
}
|
|
303
|
+
md.renderer.rules.image = prev.image
|
|
304
|
+
md.renderer.rules.paragraph_open = prev.pOpen
|
|
305
|
+
md.renderer.rules.paragraph_close = prev.pClose
|
|
306
|
+
|
|
307
|
+
return `
|
|
308
|
+
</div>
|
|
309
|
+
<!-- 下一页按钮 -->
|
|
310
|
+
<!-- <div class="swiper-button-next"></div> -->
|
|
311
|
+
<!-- 上一页按钮 -->
|
|
312
|
+
<!-- <div class="swiper-button-prev"></div> -->
|
|
313
|
+
<!-- 分页导航 -->
|
|
314
|
+
<!-- <div class="swiper-pagination"></div> -->
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
`
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Markdown 配置
|
|
325
|
+
*/
|
|
326
|
+
export function getMarkdownConfig(): MarkdownOptions {
|
|
327
|
+
const markdown: MarkdownOptions = {
|
|
328
|
+
lineNumbers: true,
|
|
329
|
+
math: true,
|
|
330
|
+
config(md) {
|
|
331
|
+
// 添加前置规则保存原始内容
|
|
332
|
+
md.core.ruler.before('normalize', 'save-source', (state) => {
|
|
333
|
+
state.env.source = state.src
|
|
334
|
+
return true
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// 添加 Mermaid 支持
|
|
338
|
+
simpleMermaidMarkdown(md)
|
|
339
|
+
|
|
340
|
+
// 添加 MarkMap 支持
|
|
341
|
+
configureMarkMapContainer(md)
|
|
342
|
+
|
|
343
|
+
// 添加任务列表支持
|
|
344
|
+
md.use(markdownItTaskLists)
|
|
345
|
+
|
|
346
|
+
// 添加链接属性支持
|
|
347
|
+
md.use(mila, {
|
|
348
|
+
attrs: {
|
|
349
|
+
target: '_self',
|
|
350
|
+
rel: 'noopener',
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
// 添加 Swiper 支持
|
|
355
|
+
configureSwiperContainer(md)
|
|
356
|
+
},
|
|
357
|
+
anchor: {
|
|
358
|
+
slugify: generateAnchor,
|
|
359
|
+
},
|
|
360
|
+
image: {
|
|
361
|
+
lazyLoading: true,
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return markdown
|
|
366
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .vitepress/config/theme.config.ts
|
|
3
|
+
*
|
|
4
|
+
* 主题配置
|
|
5
|
+
*
|
|
6
|
+
* doc:
|
|
7
|
+
* https://vitepress.dev/reference/default-theme-config
|
|
8
|
+
*/
|
|
9
|
+
import type { DefaultTheme } from 'vitepress'
|
|
10
|
+
import type { TNotesConfig } from '../../types'
|
|
11
|
+
|
|
12
|
+
export function getThemeConfig(config: TNotesConfig): DefaultTheme.Config {
|
|
13
|
+
const themeConfig: DefaultTheme.Config = {
|
|
14
|
+
docFooter: {
|
|
15
|
+
prev: '上一篇',
|
|
16
|
+
next: '下一篇',
|
|
17
|
+
},
|
|
18
|
+
externalLinkIcon: true,
|
|
19
|
+
outline: {
|
|
20
|
+
level: [2, 3],
|
|
21
|
+
label: '目录',
|
|
22
|
+
},
|
|
23
|
+
nav: [
|
|
24
|
+
{
|
|
25
|
+
text: '👀 README',
|
|
26
|
+
link: '/README',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
text: 'Menus',
|
|
30
|
+
items: config.menuItems,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
search: {
|
|
34
|
+
// 使用本地搜索(不依赖远程服务器)
|
|
35
|
+
provider: 'local',
|
|
36
|
+
options: {
|
|
37
|
+
miniSearch: {
|
|
38
|
+
/**
|
|
39
|
+
* 控制如何对文档进行分词、字段提取等预处理
|
|
40
|
+
* @type {Pick<import('minisearch').Options, 'extractField' | 'tokenize' | 'processTerm'>}
|
|
41
|
+
*/
|
|
42
|
+
options: {
|
|
43
|
+
// 自定义分词逻辑
|
|
44
|
+
tokenize: (text, language) => {
|
|
45
|
+
if (language === 'zh') {
|
|
46
|
+
return text.match(/[\u4e00-\u9fa5]+|\S+/g) || []
|
|
47
|
+
}
|
|
48
|
+
return text.split(/\s+/)
|
|
49
|
+
},
|
|
50
|
+
// 将所有词转为小写,确保大小写不敏感匹配
|
|
51
|
+
processTerm: (term) => term.toLowerCase(),
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* 控制搜索时的行为(如模糊匹配、权重)
|
|
55
|
+
* @type {import('minisearch').SearchOptions}
|
|
56
|
+
* @default
|
|
57
|
+
* { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } }
|
|
58
|
+
*/
|
|
59
|
+
searchOptions: {
|
|
60
|
+
fuzzy: 0.2, // 模糊匹配阈值(0-1),允许拼写错误的阈值(数值越低越严格)
|
|
61
|
+
prefix: true, // 是否启用前缀匹配(输入"jav"可匹配"javascript")
|
|
62
|
+
boost: {
|
|
63
|
+
title: 10, // 文件名作为 h1 标题,权重最高
|
|
64
|
+
headings: 5, // h2 - h6
|
|
65
|
+
text: 3, // 正文内容索引
|
|
66
|
+
code: 1, // 代码块索引权重
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* 控制哪些 Markdown 内容参与本地搜索引擎索引
|
|
72
|
+
* @param {string} src 当前 Markdown 文件的原始内容(即 .md 文件中的文本)
|
|
73
|
+
* @param {import('vitepress').MarkdownEnv} env 包含当前页面环境信息的对象,比如 frontmatter、路径等
|
|
74
|
+
* @param {import('markdown-it-async')} md 一个 Markdown 渲染器实例,用来将 Markdown 转换为 HTML
|
|
75
|
+
*/
|
|
76
|
+
async _render(src, env, md) {
|
|
77
|
+
const filePath = env.relativePath
|
|
78
|
+
if (filePath.includes('TOC.md')) return ''
|
|
79
|
+
|
|
80
|
+
// 提取路径中 "notes/..." 后面的第一个目录名
|
|
81
|
+
const notesIndex = filePath.indexOf('notes/')
|
|
82
|
+
let folderName = ''
|
|
83
|
+
|
|
84
|
+
if (notesIndex !== -1) {
|
|
85
|
+
const pathAfterNotes = filePath.slice(notesIndex + 'notes/'.length)
|
|
86
|
+
folderName = pathAfterNotes.split('/')[0]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 显式添加一个高权重字段,例如 "title"
|
|
90
|
+
const titleField = `# ${folderName}\n`
|
|
91
|
+
const html = md.render(titleField + '\n\n' + src, env)
|
|
92
|
+
|
|
93
|
+
return html
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// sidebar: [...sidebar],
|
|
98
|
+
sidebar: [
|
|
99
|
+
{
|
|
100
|
+
text: '👀 README',
|
|
101
|
+
link: '/README',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
socialLinks: config.socialLinks as DefaultTheme.SocialLink[],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return themeConfig
|
|
108
|
+
}
|