@tnotesjs/core 0.2.2 → 0.4.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.
- package/README.md +0 -1
- package/config/defaultConfig.ts +33 -4
- package/config/index.ts +6 -0
- package/core/NoteIndexCache.ts +7 -1
- package/core/NoteManager.ts +18 -30
- package/dist/{chunk-5Y5IYS6C.js → chunk-UJG3UU5X.js} +21 -5
- package/dist/{chunk-5QN44ES5.js → chunk-XCWFZLER.js} +48 -368
- package/dist/cli/index.js +26 -78
- package/dist/index.js +1 -1
- package/dist/vitepress/config/index.js +159 -18
- package/package.json +3 -6
- package/services/index.ts +0 -1
- package/services/init-sub-repo/initSubRepoLogic.ts +0 -1
- package/services/note/service.ts +0 -1
- package/types/config.ts +0 -3
- package/types/note.ts +0 -2
- package/vitepress/assets/icons/icon__cursor.svg +4 -0
- package/vitepress/assets/icons/index.ts +1 -0
- package/vitepress/components/Layout/AboutPanel.vue +0 -24
- package/vitepress/components/Layout/DocBeforeControls.vue +6 -14
- package/vitepress/components/Layout/DocFooter.vue +3 -3
- package/vitepress/components/Layout/Layout.vue +0 -30
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +20 -11
- package/vitepress/components/Layout/homeReadme.data.ts +0 -48
- package/vitepress/components/MindmapPreview/InlineRuns.ts +25 -0
- package/vitepress/components/MindmapPreview/MindmapOutlineNode.vue +62 -0
- package/vitepress/components/MindmapPreview/MindmapPreview.vue +287 -0
- package/vitepress/components/MindmapPreview/compat.test.ts +93 -0
- package/vitepress/components/MindmapPreview/compat.ts +128 -0
- package/vitepress/components/MindmapPreview/expandLevel.test.ts +40 -0
- package/vitepress/components/MindmapPreview/expandLevel.ts +28 -0
- package/vitepress/components/MindmapPreview/legacyCorpus.test.ts +57 -0
- package/vitepress/components/Settings/Settings.vue +76 -73
- package/vitepress/components/SidebarCard/FolderTreeItems.vue +16 -6
- package/vitepress/components/SidebarCard/SidebarCard.vue +21 -27
- package/vitepress/components/composables/useLocalIde.ts +83 -0
- package/vitepress/components/constants.ts +5 -6
- package/vitepress/components/utils/vscodePaths.test.ts +22 -0
- package/vitepress/components/utils/vscodePaths.ts +24 -2
- package/vitepress/configs/markdown.config.ts +77 -20
- package/vitepress/theme/index.ts +4 -2
- package/vitepress/theme/styles/doc-layout.scss +4 -4
- package/vitepress/theme/styles/index.scss +0 -1
- package/services/timestamp/index.ts +0 -7
- package/services/timestamp/service.ts +0 -465
- package/vitepress/components/MarkMap/MarkMap.vue +0 -625
- package/vitepress/theme/styles/components/markmap.scss +0 -122
|
@@ -9,13 +9,13 @@ import { ref, computed, watch } from 'vue'
|
|
|
9
9
|
import {
|
|
10
10
|
icon__fold,
|
|
11
11
|
icon__github,
|
|
12
|
-
icon__vscode,
|
|
13
12
|
icon__folder,
|
|
14
13
|
icon__search,
|
|
15
14
|
icon__number_gray,
|
|
16
15
|
icon__number_purple,
|
|
17
16
|
} from '../../assets/icons'
|
|
18
17
|
import { NOTES_DIR_KEY, REPO_NAME, AUTHOR, ROOT_ITEM } from '../constants.ts'
|
|
18
|
+
import { useLocalIde } from '../composables/useLocalIde'
|
|
19
19
|
import FolderTreeItems from './FolderTreeItems.vue'
|
|
20
20
|
import NotesTrendChart from './NotesTrendChart.vue'
|
|
21
21
|
import { useSettingsDialog } from '../Settings/composables/useSettingsDialog'
|
|
@@ -29,9 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
getRepoRootPath,
|
|
31
31
|
resolveNoteReadmePath,
|
|
32
|
-
toVscodeFileUrl,
|
|
33
32
|
} from '../utils/vscodePaths'
|
|
34
|
-
import { formatDate } from '../utils.ts'
|
|
35
33
|
|
|
36
34
|
// #region props
|
|
37
35
|
const props = defineProps({
|
|
@@ -46,6 +44,14 @@ const props = defineProps({
|
|
|
46
44
|
})
|
|
47
45
|
// #endregion
|
|
48
46
|
|
|
47
|
+
const {
|
|
48
|
+
icon: localIdeIcon,
|
|
49
|
+
openNoteTitle,
|
|
50
|
+
openRepoTitle,
|
|
51
|
+
openReadmeTitle,
|
|
52
|
+
toFileUrl,
|
|
53
|
+
} = useLocalIde()
|
|
54
|
+
|
|
49
55
|
// #region data
|
|
50
56
|
const viewMode = ref('folder')
|
|
51
57
|
const expandedTreeKeys = ref(new Set())
|
|
@@ -217,7 +223,7 @@ function openGithubRepo() {
|
|
|
217
223
|
window.open(`https://github.com/${AUTHOR}/${REPO_NAME}`, '_blank')
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
//
|
|
226
|
+
// 打开本地 IDE 知识库
|
|
221
227
|
function openVSCodeRepo() {
|
|
222
228
|
const notesDir = localStorage.getItem(NOTES_DIR_KEY)
|
|
223
229
|
|
|
@@ -231,10 +237,10 @@ function openVSCodeRepo() {
|
|
|
231
237
|
return
|
|
232
238
|
}
|
|
233
239
|
|
|
234
|
-
window.open(
|
|
240
|
+
window.open(toFileUrl(notesDir), '_blank')
|
|
235
241
|
}
|
|
236
242
|
|
|
237
|
-
//
|
|
243
|
+
// 在本地 IDE 中打开笔记 README.md
|
|
238
244
|
function openVSCodeArticle(article) {
|
|
239
245
|
const notesDir = localStorage.getItem(NOTES_DIR_KEY)
|
|
240
246
|
|
|
@@ -251,7 +257,7 @@ function openVSCodeArticle(article) {
|
|
|
251
257
|
const notePath = resolveNoteReadmePath(notesDir, article.relativePath)
|
|
252
258
|
if (!notePath) return
|
|
253
259
|
|
|
254
|
-
window.open(
|
|
260
|
+
window.open(toFileUrl(notePath), '_blank')
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
function openVSCodeReadme() {
|
|
@@ -268,7 +274,7 @@ function openVSCodeReadme() {
|
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
const readmePath = `${getRepoRootPath(notesDir)}/README.md`
|
|
271
|
-
window.open(
|
|
277
|
+
window.open(toFileUrl(readmePath), '_blank')
|
|
272
278
|
}
|
|
273
279
|
</script>
|
|
274
280
|
|
|
@@ -318,9 +324,9 @@ function openVSCodeReadme() {
|
|
|
318
324
|
<img :src="icon__github" alt="GitHub仓库" />
|
|
319
325
|
</button>
|
|
320
326
|
|
|
321
|
-
<!--
|
|
322
|
-
<button class="vscode-open" @click="openVSCodeRepo">
|
|
323
|
-
<img :src="
|
|
327
|
+
<!-- 本地 IDE 按钮 -->
|
|
328
|
+
<button class="vscode-open" @click="openVSCodeRepo" :title="openRepoTitle">
|
|
329
|
+
<img :src="localIdeIcon" :alt="openRepoTitle" />
|
|
324
330
|
</button>
|
|
325
331
|
|
|
326
332
|
<!-- 关于按钮 -->
|
|
@@ -346,18 +352,6 @@ function openVSCodeReadme() {
|
|
|
346
352
|
{{ AUTHOR }}/{{ REPO_NAME }}
|
|
347
353
|
</a>
|
|
348
354
|
</div>
|
|
349
|
-
<div class="about-item">
|
|
350
|
-
<span class="about-label">📅 创建时间</span>
|
|
351
|
-
<span class="about-value">{{
|
|
352
|
-
formatDate(ROOT_ITEM.created_at)
|
|
353
|
-
}}</span>
|
|
354
|
-
</div>
|
|
355
|
-
<div class="about-item">
|
|
356
|
-
<span class="about-label">♻️ 最近更新</span>
|
|
357
|
-
<span class="about-value">{{
|
|
358
|
-
formatDate(ROOT_ITEM.updated_at)
|
|
359
|
-
}}</span>
|
|
360
|
-
</div>
|
|
361
355
|
</div>
|
|
362
356
|
</div>
|
|
363
357
|
</div>
|
|
@@ -395,9 +389,9 @@ function openVSCodeReadme() {
|
|
|
395
389
|
<button
|
|
396
390
|
class="vscode-article"
|
|
397
391
|
@click.stop="openVSCodeArticle(article)"
|
|
398
|
-
title="
|
|
392
|
+
:title="openNoteTitle"
|
|
399
393
|
>
|
|
400
|
-
<img :src="
|
|
394
|
+
<img :src="localIdeIcon" :alt="openNoteTitle" />
|
|
401
395
|
</button>
|
|
402
396
|
</div>
|
|
403
397
|
</div>
|
|
@@ -415,10 +409,10 @@ function openVSCodeReadme() {
|
|
|
415
409
|
<button
|
|
416
410
|
class="vscode-article"
|
|
417
411
|
type="button"
|
|
418
|
-
title="
|
|
412
|
+
:title="openReadmeTitle"
|
|
419
413
|
@click="openVSCodeReadme"
|
|
420
414
|
>
|
|
421
|
-
<img :src="
|
|
415
|
+
<img :src="localIdeIcon" :alt="openReadmeTitle" />
|
|
422
416
|
</button>
|
|
423
417
|
</div>
|
|
424
418
|
<FolderTreeItems
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vitepress/components/composables/useLocalIde.ts
|
|
3
|
+
*
|
|
4
|
+
* 本地 IDE(VS Code / Cursor)选择状态与展示信息
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
import { icon__cursor, icon__vscode } from '../../assets/icons'
|
|
10
|
+
import { LOCAL_IDE_KEY } from '../constants'
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_LOCAL_IDE,
|
|
13
|
+
normalizeLocalIde,
|
|
14
|
+
toIdeFileUrl,
|
|
15
|
+
type LocalIdeId,
|
|
16
|
+
} from '../utils/vscodePaths'
|
|
17
|
+
|
|
18
|
+
const ide = ref<LocalIdeId>(DEFAULT_LOCAL_IDE)
|
|
19
|
+
let hydrated = false
|
|
20
|
+
|
|
21
|
+
function readIdeFromStorage(): LocalIdeId {
|
|
22
|
+
if (typeof window === 'undefined') return DEFAULT_LOCAL_IDE
|
|
23
|
+
return normalizeLocalIde(localStorage.getItem(LOCAL_IDE_KEY))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hydrateFromStorage() {
|
|
27
|
+
if (typeof window === 'undefined' || hydrated) return
|
|
28
|
+
ide.value = readIdeFromStorage()
|
|
29
|
+
hydrated = true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useLocalIde() {
|
|
33
|
+
hydrateFromStorage()
|
|
34
|
+
|
|
35
|
+
const icon = computed(() =>
|
|
36
|
+
ide.value === 'cursor' ? icon__cursor : icon__vscode,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const shortLabel = computed(() =>
|
|
40
|
+
ide.value === 'cursor' ? 'Cursor' : 'VS Code',
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const openNoteTitle = computed(
|
|
44
|
+
() => `用 ${shortLabel.value} 打开笔记目录`,
|
|
45
|
+
)
|
|
46
|
+
const openRepoTitle = computed(
|
|
47
|
+
() => `用 ${shortLabel.value} 打开本地知识库`,
|
|
48
|
+
)
|
|
49
|
+
const openReadmeTitle = computed(
|
|
50
|
+
() => `用 ${shortLabel.value} 打开 README.md`,
|
|
51
|
+
)
|
|
52
|
+
const openCurrentTitle = computed(
|
|
53
|
+
() => `用 ${shortLabel.value} 打开当前笔记`,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
function setLocalIde(next: LocalIdeId) {
|
|
57
|
+
ide.value = normalizeLocalIde(next)
|
|
58
|
+
if (typeof window !== 'undefined') {
|
|
59
|
+
localStorage.setItem(LOCAL_IDE_KEY, ide.value)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function refreshLocalIde() {
|
|
64
|
+
ide.value = readIdeFromStorage()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toFileUrl(filePath: string) {
|
|
68
|
+
return toIdeFileUrl(filePath, ide.value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
ide,
|
|
73
|
+
icon,
|
|
74
|
+
shortLabel,
|
|
75
|
+
openNoteTitle,
|
|
76
|
+
openRepoTitle,
|
|
77
|
+
openReadmeTitle,
|
|
78
|
+
openCurrentTitle,
|
|
79
|
+
setLocalIde,
|
|
80
|
+
refreshLocalIde,
|
|
81
|
+
toFileUrl,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -37,6 +37,11 @@ export const ROOT_ITEM = __TNOTES_ROOT_ITEM__
|
|
|
37
37
|
*/
|
|
38
38
|
export const NOTES_DIR_KEY: string = 'NOTES_DIR_KEY__' + REPO_NAME
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 本地打开所用 IDE(vscode | cursor)
|
|
42
|
+
*/
|
|
43
|
+
export const LOCAL_IDE_KEY: string = 'LOCAL_IDE_KEY__' + REPO_NAME
|
|
44
|
+
|
|
40
45
|
/**
|
|
41
46
|
* 用户选择的视图
|
|
42
47
|
*/
|
|
@@ -53,12 +58,6 @@ export const EN_WORD_LIST_COMP_IS_AUTO_SHOW_CARD: string =
|
|
|
53
58
|
*/
|
|
54
59
|
export const MARKMAP_THEME_KEY: string = 'MARKMAP_THEME_KEY__' + REPO_NAME
|
|
55
60
|
|
|
56
|
-
/**
|
|
57
|
-
* MarkMap 默认展开层级配置
|
|
58
|
-
*/
|
|
59
|
-
export const MARKMAP_EXPAND_LEVEL_KEY: string =
|
|
60
|
-
'MARKMAP_EXPAND_LEVEL_KEY__' + REPO_NAME
|
|
61
|
-
|
|
62
61
|
/**
|
|
63
62
|
* 侧边栏是否显示笔记编号配置
|
|
64
63
|
*/
|
|
@@ -2,7 +2,9 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
getRepoRootPath,
|
|
5
|
+
normalizeLocalIde,
|
|
5
6
|
resolveNoteReadmePath,
|
|
7
|
+
toIdeFileUrl,
|
|
6
8
|
toVscodeFileUrl,
|
|
7
9
|
} from './vscodePaths'
|
|
8
10
|
|
|
@@ -33,6 +35,26 @@ describe('vscodePaths', () => {
|
|
|
33
35
|
)
|
|
34
36
|
})
|
|
35
37
|
|
|
38
|
+
it('normalizeLocalIde falls back to vscode', () => {
|
|
39
|
+
expect(normalizeLocalIde(null)).toBe('vscode')
|
|
40
|
+
expect(normalizeLocalIde('')).toBe('vscode')
|
|
41
|
+
expect(normalizeLocalIde('foo')).toBe('vscode')
|
|
42
|
+
expect(normalizeLocalIde('cursor')).toBe('cursor')
|
|
43
|
+
expect(normalizeLocalIde('vscode')).toBe('vscode')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('toIdeFileUrl uses vscode scheme by default', () => {
|
|
47
|
+
expect(toIdeFileUrl('C:\\notes\\0001\\README.md')).toContain(
|
|
48
|
+
'vscode://file/',
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('toIdeFileUrl uses cursor scheme when selected', () => {
|
|
53
|
+
expect(toIdeFileUrl('C:\\notes\\0001\\README.md', 'cursor')).toContain(
|
|
54
|
+
'cursor://file/',
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
|
|
36
58
|
it('toVscodeFileUrl encodes file path', () => {
|
|
37
59
|
expect(toVscodeFileUrl('C:\\notes\\0001\\README.md')).toContain(
|
|
38
60
|
'vscode://file/',
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vitepress/components/utils/vscodePaths.ts
|
|
3
3
|
*
|
|
4
|
-
* 将站点内路径解析为本地
|
|
4
|
+
* 将站点内路径解析为本地 IDE 可打开的文件路径与 URL。
|
|
5
5
|
* NOTES_DIR 通常配置为仓库下的 notes 目录(如 .../TNotes.introduction/notes)。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
export type LocalIdeId = 'vscode' | 'cursor'
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_LOCAL_IDE: LocalIdeId = 'vscode'
|
|
11
|
+
|
|
12
|
+
const IDE_SCHEMES: Record<LocalIdeId, string> = {
|
|
13
|
+
vscode: 'vscode',
|
|
14
|
+
cursor: 'cursor',
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
export function getRepoRootPath(notesDir: string): string {
|
|
9
18
|
const normalized = notesDir.replace(/[/\\]+$/, '')
|
|
10
19
|
if (/[/\\]notes$/i.test(normalized)) {
|
|
@@ -47,6 +56,19 @@ export function resolveNoteReadmePath(
|
|
|
47
56
|
return `${base}${sep}${folderPath}${sep}README.md`
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
export function normalizeLocalIde(value: string | null | undefined): LocalIdeId {
|
|
60
|
+
return value === 'cursor' ? 'cursor' : DEFAULT_LOCAL_IDE
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function toIdeFileUrl(
|
|
64
|
+
filePath: string,
|
|
65
|
+
ide: LocalIdeId = DEFAULT_LOCAL_IDE,
|
|
66
|
+
): string {
|
|
67
|
+
const scheme = IDE_SCHEMES[normalizeLocalIde(ide)]
|
|
68
|
+
return `${scheme}://file/${encodeURI(filePath)}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @deprecated 使用 toIdeFileUrl(filePath, 'vscode') */
|
|
50
72
|
export function toVscodeFileUrl(filePath: string): string {
|
|
51
|
-
return
|
|
73
|
+
return toIdeFileUrl(filePath, 'vscode')
|
|
52
74
|
}
|
|
@@ -11,6 +11,11 @@ import markdownItTaskLists from 'markdown-it-task-lists'
|
|
|
11
11
|
import path from 'path'
|
|
12
12
|
|
|
13
13
|
import { generateAnchor } from '../../utils'
|
|
14
|
+
import {
|
|
15
|
+
normalizeMindmapMarkdown,
|
|
16
|
+
parseMarkmapFence,
|
|
17
|
+
parseMindmapReference,
|
|
18
|
+
} from '../components/MindmapPreview/compat'
|
|
14
19
|
|
|
15
20
|
import type MarkdownIt from 'markdown-it'
|
|
16
21
|
import type { MarkdownOptions } from 'vitepress'
|
|
@@ -66,17 +71,13 @@ const simpleMermaidMarkdown = (md: MarkdownIt) => {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
/**
|
|
69
|
-
*
|
|
74
|
+
* Mindmap 容器配置(兼容旧的 markmap 围栏名)
|
|
70
75
|
*/
|
|
71
|
-
function
|
|
72
|
-
// 先保留 container 的解析(负责把 ```markmap ``` 识别成 container tokens)
|
|
73
|
-
// 但让它本身不输出任何 HTML(render 返回空)
|
|
76
|
+
function configureMindmapContainer(md: MarkdownIt) {
|
|
74
77
|
md.use(markdownItContainer, 'markmap', {
|
|
75
78
|
marker: '`',
|
|
76
79
|
validate(params: string) {
|
|
77
|
-
|
|
78
|
-
const p = (params || '').trim()
|
|
79
|
-
return p.startsWith('markmap')
|
|
80
|
+
return (params || '').trim().startsWith('markmap')
|
|
80
81
|
},
|
|
81
82
|
render() {
|
|
82
83
|
return ''
|
|
@@ -93,11 +94,13 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
93
94
|
for (let i = 0; i < tokens.length; i++) {
|
|
94
95
|
const t = tokens[i]
|
|
95
96
|
if (t.type === 'container_markmap_open') {
|
|
97
|
+
const containerName = 'markmap'
|
|
98
|
+
const closeType = 'container_markmap_close'
|
|
96
99
|
// 找到对应的 close token
|
|
97
100
|
let j = i + 1
|
|
98
101
|
while (
|
|
99
102
|
j < tokens.length &&
|
|
100
|
-
tokens[j].type !==
|
|
103
|
+
tokens[j].type !== closeType
|
|
101
104
|
)
|
|
102
105
|
j++
|
|
103
106
|
if (j >= tokens.length) continue // safety
|
|
@@ -109,9 +112,12 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
109
112
|
|
|
110
113
|
// 1) 从开头 fence 行解析参数(支持 `{a=1 b="x"}`、`a=1 b="x"`,并支持单个数字 shorthand)
|
|
111
114
|
const params: { [key: string]: any; initialExpandLevel?: number } = {}
|
|
115
|
+
let explicitTitle: string | undefined
|
|
112
116
|
|
|
113
117
|
if (open.map && typeof open.map[0] === 'number') {
|
|
114
118
|
const openLine = (lines[open.map[0]] || '').trim()
|
|
119
|
+
const fenceOptions = parseMarkmapFence(openLine)
|
|
120
|
+
explicitTitle = fenceOptions.title
|
|
115
121
|
let paramPart = ''
|
|
116
122
|
|
|
117
123
|
// 优先匹配大括号形式 ```markmap{...}
|
|
@@ -121,10 +127,13 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
121
127
|
} else {
|
|
122
128
|
// 否则尝试去掉前缀 ``` 和 markmap,剩下的作为参数部分
|
|
123
129
|
const after = openLine.replace(/^`+\s*/, '')
|
|
124
|
-
if (after.startsWith(
|
|
125
|
-
paramPart = after.slice(
|
|
130
|
+
if (after.startsWith(containerName)) {
|
|
131
|
+
paramPart = after.slice(containerName.length).trim()
|
|
126
132
|
}
|
|
127
133
|
}
|
|
134
|
+
if (fenceOptions.initialExpandLevel !== undefined) {
|
|
135
|
+
params.initialExpandLevel = fenceOptions.initialExpandLevel
|
|
136
|
+
}
|
|
128
137
|
|
|
129
138
|
if (paramPart) {
|
|
130
139
|
// 使用正则按 token 切分:保持用引号包裹的片段为单个 token(支持包含空格)
|
|
@@ -179,10 +188,11 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
179
188
|
// --- 检查第一非空行是否为引用语法 ---
|
|
180
189
|
const firstNonEmptyLine =
|
|
181
190
|
(content || '').split('\n').find((ln) => ln.trim() !== '') || ''
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const refRaw =
|
|
191
|
+
const reference = parseMindmapReference(firstNonEmptyLine)
|
|
192
|
+
let referencedTitle: string | undefined
|
|
193
|
+
if (reference) {
|
|
194
|
+
const refRaw = reference.path
|
|
195
|
+
referencedTitle = reference.title
|
|
186
196
|
|
|
187
197
|
// 尝试同步读取文件内容(兼容常见 Node 环境)
|
|
188
198
|
try {
|
|
@@ -213,12 +223,14 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
213
223
|
} catch (err) {
|
|
214
224
|
// 读取失败:将错误写入 content 以便排查(不会让流程直接崩溃)
|
|
215
225
|
const errorMsg = err instanceof Error ? err.message : String(err)
|
|
216
|
-
content =
|
|
217
|
-
String(refRaw)
|
|
218
|
-
)}\n\nError: ${esc(errorMsg)}`
|
|
226
|
+
content = `- Failed to load referenced file: ${esc(String(refRaw))}\n - Error: ${esc(errorMsg)}`
|
|
219
227
|
}
|
|
220
228
|
}
|
|
221
229
|
|
|
230
|
+
content = normalizeMindmapMarkdown(content, {
|
|
231
|
+
title: explicitTitle || referencedTitle,
|
|
232
|
+
})
|
|
233
|
+
|
|
222
234
|
// 3) 构造组件标签并把参数注入为 props
|
|
223
235
|
const encodedContent = encodeURIComponent(content.trim())
|
|
224
236
|
let propsStr = `content="${encodedContent}"`
|
|
@@ -232,7 +244,7 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
const html = `<
|
|
247
|
+
const html = `<MindmapPreview ${propsStr}></MindmapPreview>\n`
|
|
236
248
|
|
|
237
249
|
// 创建 html_block token
|
|
238
250
|
const htmlToken = new state.Token('html_block', '', 0)
|
|
@@ -247,6 +259,50 @@ function configureMarkMapContainer(md: MarkdownIt) {
|
|
|
247
259
|
})
|
|
248
260
|
}
|
|
249
261
|
|
|
262
|
+
/** Canonical `mindmap` fence. Legacy `markmap` stays on its historical container path. */
|
|
263
|
+
function configureMindmapFence(md: MarkdownIt) {
|
|
264
|
+
const fence = md.renderer.rules.fence
|
|
265
|
+
? md.renderer.rules.fence.bind(md.renderer.rules)
|
|
266
|
+
: () => ''
|
|
267
|
+
|
|
268
|
+
md.renderer.rules.fence = (tokens, index, options, env, slf) => {
|
|
269
|
+
const token = tokens[index]
|
|
270
|
+
const info = token.info.trim()
|
|
271
|
+
if (!/^mindmap(?=\s|\{|\[|$)/.test(info)) {
|
|
272
|
+
return fence(tokens, index, options, env, slf)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const fenceOptions = parseMarkmapFence(info)
|
|
276
|
+
let content = token.content
|
|
277
|
+
const firstNonEmptyLine = content.split('\n').find((line) => line.trim()) ?? ''
|
|
278
|
+
const reference = parseMindmapReference(firstNonEmptyLine)
|
|
279
|
+
|
|
280
|
+
if (reference) {
|
|
281
|
+
const possibleRel = env?.relativePath || env?.path || env?.filePath || env?.file || ''
|
|
282
|
+
const refFullPath = path.isAbsolute(reference.path)
|
|
283
|
+
? reference.path
|
|
284
|
+
: path.resolve(process.cwd(), possibleRel ? path.dirname(possibleRel) : '', reference.path)
|
|
285
|
+
try {
|
|
286
|
+
content = fs.readFileSync(refFullPath, 'utf8')
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
289
|
+
content = `- Failed to load referenced file: ${reference.path}\n - Error: ${message}`
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
content = normalizeMindmapMarkdown(content, {
|
|
294
|
+
title: fenceOptions.title || reference?.title,
|
|
295
|
+
})
|
|
296
|
+
const props = [
|
|
297
|
+
`content="${encodeURIComponent(content.trim())}"`,
|
|
298
|
+
fenceOptions.initialExpandLevel === undefined
|
|
299
|
+
? ''
|
|
300
|
+
: `:initialExpandLevel="${fenceOptions.initialExpandLevel}"`,
|
|
301
|
+
].filter(Boolean).join(' ')
|
|
302
|
+
return `<MindmapPreview ${props}></MindmapPreview>\n`
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
250
306
|
/**
|
|
251
307
|
* Swiper 容器配置
|
|
252
308
|
*/
|
|
@@ -340,8 +396,9 @@ export function getMarkdownConfig(): MarkdownOptions {
|
|
|
340
396
|
// 添加 Mermaid 支持
|
|
341
397
|
simpleMermaidMarkdown(md)
|
|
342
398
|
|
|
343
|
-
// 添加 MarkMap
|
|
344
|
-
|
|
399
|
+
// 添加 Mindmap 支持,并继续兼容旧 MarkMap 围栏
|
|
400
|
+
configureMindmapContainer(md)
|
|
401
|
+
configureMindmapFence(md)
|
|
345
402
|
|
|
346
403
|
// 添加任务列表支持
|
|
347
404
|
md.use(markdownItTaskLists)
|
package/vitepress/theme/index.ts
CHANGED
|
@@ -22,8 +22,8 @@ import Footprints from '../components/Footprints/Footprints.vue'
|
|
|
22
22
|
import { useRenameOverlay } from '../components/Layout/composables/useRenameOverlay'
|
|
23
23
|
import { redirectAfterRename } from '../components/Layout/composables/useRenameRedirect'
|
|
24
24
|
import Layout from '../components/Layout/Layout.vue'
|
|
25
|
-
import MarkMap from '../components/MarkMap/MarkMap.vue'
|
|
26
25
|
import Mermaid from '../components/Mermaid/Mermaid.vue'
|
|
26
|
+
import MindmapPreview from '../components/MindmapPreview/MindmapPreview.vue'
|
|
27
27
|
import NotesTable from '../components/NotesTable/NotesTable.vue'
|
|
28
28
|
import SidebarCard from '../components/SidebarCard/SidebarCard.vue'
|
|
29
29
|
import Tooltip from '../components/Tooltip/Tooltip.vue'
|
|
@@ -45,7 +45,9 @@ function registerCoreComponents(ctx: EnhanceAppContext) {
|
|
|
45
45
|
app.component('Footprints', Footprints)
|
|
46
46
|
app.component('F', Footprints)
|
|
47
47
|
app.component('SidebarCard', SidebarCard)
|
|
48
|
-
|
|
48
|
+
// Keep the historical component name so existing note fences need no migration.
|
|
49
|
+
app.component('MarkMap', MindmapPreview)
|
|
50
|
+
app.component('MindmapPreview', MindmapPreview)
|
|
49
51
|
app.component('Mermaid', Mermaid)
|
|
50
52
|
app.component('NotesTable', NotesTable)
|
|
51
53
|
app.component('N', NotesTable)
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
width: 100% !important;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/* aside 已 fixed
|
|
104
|
-
.VPDoc.has-aside .container {
|
|
103
|
+
/* aside 已 fixed,仅改 VPDoc 直接子级 container,避免误伤 DocFooter 等内部 .container */
|
|
104
|
+
.VPDoc.has-aside > .container {
|
|
105
105
|
display: block !important;
|
|
106
106
|
max-width: none !important;
|
|
107
107
|
}
|
|
@@ -188,8 +188,8 @@ html.content-fullscreen {
|
|
|
188
188
|
padding-right: 0 !important;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
.VPDoc.has-aside .container,
|
|
192
|
-
.VPDoc .container {
|
|
191
|
+
.VPDoc.has-aside > .container,
|
|
192
|
+
.VPDoc > .container {
|
|
193
193
|
max-width: none !important;
|
|
194
194
|
width: 100% !important;
|
|
195
195
|
}
|