@tnotesjs/core 0.4.2 → 0.6.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/dist/chunk-3R7QSABB.cjs +502 -0
- package/dist/chunk-CZXRQPFJ.js +11 -0
- package/dist/{chunk-XCWFZLER.js → chunk-DUTS75WQ.js} +27 -493
- package/dist/chunk-HXED7KVT.cjs +11 -0
- package/dist/chunk-IHFUHB4J.cjs +176 -0
- package/dist/chunk-MZIXIY2Y.cjs +216 -0
- package/dist/chunk-TJ4527KA.cjs +5264 -0
- package/dist/chunk-U6IBLREL.js +502 -0
- package/dist/chunk-UNFSW5C2.js +176 -0
- package/dist/cli/index.cjs +936 -0
- package/dist/cli/index.d.cts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +9 -6
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/markdown/index.cjs +82 -0
- package/dist/markdown/index.d.cts +36 -0
- package/dist/markdown/index.d.ts +36 -0
- package/dist/markdown/index.js +82 -0
- package/dist/note-oGm44Rw8.d.cts +84 -0
- package/dist/note-oGm44Rw8.d.ts +84 -0
- package/dist/types-CpsEy8lA.d.cts +221 -0
- package/dist/types-CwBWwNVv.d.ts +221 -0
- package/dist/vitepress/config/index.cjs +1669 -0
- package/dist/vitepress/config/index.d.cts +12 -0
- package/dist/vitepress/config/index.d.ts +12 -0
- package/dist/vitepress/config/index.js +26 -192
- package/dist/workspace/index.cjs +1207 -0
- package/dist/workspace/index.d.cts +14 -0
- package/dist/workspace/index.d.ts +14 -0
- package/dist/workspace/index.js +1207 -0
- package/package.json +16 -3
- package/vitepress/components/MindmapPreview/MindmapPreview.vue +189 -34
- package/vitepress/components/MindmapPreview/MindmapViewIcon.vue +41 -0
- package/vitepress/components/MindmapPreview/markdown.test.ts +67 -0
- package/vitepress/components/MindmapPreview/markdown.ts +83 -0
- package/vitepress/components/MindmapPreview/wheelInteraction.test.ts +23 -0
- package/vitepress/components/MindmapPreview/wheelInteraction.ts +7 -0
- package/vitepress/components/Settings/Settings.vue +3 -3
- package/vitepress/components/SidebarCard/SidebarCard.vue +3 -3
- package/vitepress/components/constants.ts +0 -5
- package/vitepress/configs/markdown.config.ts +6 -199
- package/vitepress/plugins/localSearchReindexLogic.ts +11 -3
- package/vitepress/theme/index.ts +0 -2
- package/vitepress/components/MindmapPreview/compat.test.ts +0 -93
- package/vitepress/components/MindmapPreview/compat.ts +0 -128
- package/vitepress/components/MindmapPreview/legacyCorpus.test.ts +0 -57
package/package.json
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tnotesjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "TNotes 知识库核心框架 —— 基于 VitePress 的笔记管理系统",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.17.1",
|
|
7
7
|
"bin": {
|
|
8
|
-
"tnotes": "
|
|
8
|
+
"tnotes": "dist/cli/index.js"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
13
14
|
"import": "./dist/index.js"
|
|
14
15
|
},
|
|
16
|
+
"./workspace": {
|
|
17
|
+
"types": "./dist/workspace/index.d.ts",
|
|
18
|
+
"require": "./dist/workspace/index.cjs",
|
|
19
|
+
"import": "./dist/workspace/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./markdown": {
|
|
22
|
+
"types": "./dist/markdown/index.d.ts",
|
|
23
|
+
"require": "./dist/markdown/index.cjs",
|
|
24
|
+
"import": "./dist/markdown/index.js"
|
|
25
|
+
},
|
|
15
26
|
"./vitepress/config": {
|
|
27
|
+
"require": "./dist/vitepress/config/index.cjs",
|
|
16
28
|
"import": "./dist/vitepress/config/index.js"
|
|
17
29
|
},
|
|
18
30
|
"./vitepress/theme": {
|
|
@@ -59,6 +71,7 @@
|
|
|
59
71
|
"marked": "^15.0.11",
|
|
60
72
|
"mermaid": "^11.5.0",
|
|
61
73
|
"minisearch": "^7.2.0",
|
|
74
|
+
"prettier": "^3.6.2",
|
|
62
75
|
"sass-embedded": "^1.90.0",
|
|
63
76
|
"swiper": "^11.2.1",
|
|
64
77
|
"uuid": "^11.1.0",
|
|
@@ -85,7 +98,7 @@
|
|
|
85
98
|
"license": "MIT",
|
|
86
99
|
"repository": {
|
|
87
100
|
"type": "git",
|
|
88
|
-
"url": "https://github.com/tnotesjs/core"
|
|
101
|
+
"url": "git+https://github.com/tnotesjs/core.git"
|
|
89
102
|
},
|
|
90
103
|
"keywords": [
|
|
91
104
|
"tnotes",
|
|
@@ -3,9 +3,12 @@ import { CanvasViewer, MindmapSession } from '@tnotesjs/mindmap-core'
|
|
|
3
3
|
import { onContentUpdated, useData } from 'vitepress'
|
|
4
4
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
|
5
5
|
|
|
6
|
-
import { normalizeMindmapMarkdown } from './compat'
|
|
7
6
|
import { applyInitialExpandLevel } from './expandLevel'
|
|
7
|
+
import { normalizeMindmapMarkdown } from './markdown'
|
|
8
8
|
import MindmapOutlineNode from './MindmapOutlineNode.vue'
|
|
9
|
+
import MindmapViewIcon from './MindmapViewIcon.vue'
|
|
10
|
+
import { gateMindmapWheel } from './wheelInteraction'
|
|
11
|
+
import { icon__fullscreen, icon__fullscreen_exit, icon__zoom_fit } from '../../assets/icons'
|
|
9
12
|
|
|
10
13
|
type PreviewView = 'mindmap' | 'outline' | 'source'
|
|
11
14
|
|
|
@@ -19,12 +22,21 @@ const props = withDefaults(defineProps<{
|
|
|
19
22
|
|
|
20
23
|
const { isDark } = useData()
|
|
21
24
|
const activeView = ref<PreviewView>('mindmap')
|
|
25
|
+
const previewRoot = ref<HTMLElement | null>(null)
|
|
22
26
|
const canvasHost = ref<HTMLElement | null>(null)
|
|
23
27
|
const session = shallowRef<MindmapSession | null>(null)
|
|
24
28
|
const renderVersion = ref(0)
|
|
29
|
+
const isFullscreen = ref(false)
|
|
30
|
+
const isCanvasActive = ref(false)
|
|
25
31
|
let viewer: CanvasViewer | null = null
|
|
26
32
|
let mounted = false
|
|
27
33
|
|
|
34
|
+
const viewOptions = [
|
|
35
|
+
{ value: 'mindmap', label: '脑图' },
|
|
36
|
+
{ value: 'outline', label: '大纲' },
|
|
37
|
+
{ value: 'source', label: '源码' },
|
|
38
|
+
] as const
|
|
39
|
+
|
|
28
40
|
function decodeContent(value: string): string {
|
|
29
41
|
try {
|
|
30
42
|
return decodeURIComponent(value)
|
|
@@ -61,6 +73,7 @@ function rebuildSession(): void {
|
|
|
61
73
|
|
|
62
74
|
function setView(view: PreviewView): void {
|
|
63
75
|
activeView.value = view
|
|
76
|
+
if (view !== 'mindmap') isCanvasActive.value = false
|
|
64
77
|
if (view === 'mindmap') void nextTick(() => viewer?.zoomToFit())
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -72,11 +85,51 @@ function exitFocusTo(index: number): void {
|
|
|
72
85
|
session.value?.exitFocusTo(index)
|
|
73
86
|
}
|
|
74
87
|
|
|
88
|
+
async function toggleFullscreen(): Promise<void> {
|
|
89
|
+
const root = previewRoot.value
|
|
90
|
+
if (!root) return
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (document.fullscreenElement === root) await document.exitFullscreen()
|
|
94
|
+
else await root.requestFullscreen()
|
|
95
|
+
} catch {
|
|
96
|
+
// Fullscreen can be rejected by the browser or an embedded document policy.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleFullscreenChange(): void {
|
|
101
|
+
isFullscreen.value = document.fullscreenElement === previewRoot.value
|
|
102
|
+
if (activeView.value === 'mindmap') void nextTick(() => viewer?.zoomToFit())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function activateCanvas(): void {
|
|
106
|
+
isCanvasActive.value = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleCanvasWheelCapture(event: WheelEvent): void {
|
|
110
|
+
gateMindmapWheel(event, isCanvasActive.value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleDocumentPointerDown(event: PointerEvent): void {
|
|
114
|
+
if (event.target instanceof Node && !previewRoot.value?.contains(event.target)) {
|
|
115
|
+
isCanvasActive.value = false
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleDocumentKeydown(event: KeyboardEvent): void {
|
|
120
|
+
if (event.key !== 'Escape' || !isCanvasActive.value) return
|
|
121
|
+
isCanvasActive.value = false
|
|
122
|
+
canvasHost.value?.blur()
|
|
123
|
+
}
|
|
124
|
+
|
|
75
125
|
watch([normalizedContent, () => props.initialExpandLevel], rebuildSession, { immediate: true })
|
|
76
126
|
watch(isDark, (dark) => viewer?.setTheme(dark ? 'dark' : 'light'))
|
|
77
127
|
|
|
78
128
|
onMounted(() => {
|
|
79
129
|
mounted = true
|
|
130
|
+
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
131
|
+
document.addEventListener('pointerdown', handleDocumentPointerDown, true)
|
|
132
|
+
document.addEventListener('keydown', handleDocumentKeydown, true)
|
|
80
133
|
createViewer()
|
|
81
134
|
})
|
|
82
135
|
|
|
@@ -86,29 +139,58 @@ onContentUpdated(() => {
|
|
|
86
139
|
|
|
87
140
|
onBeforeUnmount(() => {
|
|
88
141
|
mounted = false
|
|
142
|
+
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
143
|
+
document.removeEventListener('pointerdown', handleDocumentPointerDown, true)
|
|
144
|
+
document.removeEventListener('keydown', handleDocumentKeydown, true)
|
|
89
145
|
viewer?.destroy()
|
|
90
146
|
viewer = null
|
|
91
147
|
})
|
|
92
148
|
</script>
|
|
93
149
|
|
|
94
150
|
<template>
|
|
95
|
-
<section
|
|
96
|
-
|
|
151
|
+
<section
|
|
152
|
+
ref="previewRoot"
|
|
153
|
+
class="mindmap-preview"
|
|
154
|
+
:class="{ 'is-dark': isDark, 'is-fullscreen': isFullscreen }"
|
|
155
|
+
:data-version="renderVersion"
|
|
156
|
+
>
|
|
157
|
+
<div class="mindmap-preview-actions">
|
|
97
158
|
<nav class="mindmap-preview-tabs" aria-label="脑图预览视图">
|
|
98
159
|
<button
|
|
99
|
-
v-for="item in
|
|
100
|
-
:key="item
|
|
160
|
+
v-for="item in viewOptions"
|
|
161
|
+
:key="item.value"
|
|
101
162
|
type="button"
|
|
102
|
-
|
|
103
|
-
|
|
163
|
+
class="mindmap-preview-action"
|
|
164
|
+
:class="{ 'is-active': activeView === item.value }"
|
|
165
|
+
:aria-label="item.label"
|
|
166
|
+
:aria-pressed="activeView === item.value"
|
|
167
|
+
:title="item.label"
|
|
168
|
+
@click="setView(item.value)"
|
|
104
169
|
>
|
|
105
|
-
|
|
170
|
+
<MindmapViewIcon :view="item.value" />
|
|
106
171
|
</button>
|
|
107
172
|
</nav>
|
|
108
|
-
<
|
|
109
|
-
|
|
173
|
+
<span class="mindmap-preview-action-divider" aria-hidden="true" />
|
|
174
|
+
<button
|
|
175
|
+
v-if="activeView === 'mindmap'"
|
|
176
|
+
type="button"
|
|
177
|
+
class="mindmap-preview-action"
|
|
178
|
+
aria-label="适应视口"
|
|
179
|
+
title="适应视口"
|
|
180
|
+
@click="viewer?.zoomToFit()"
|
|
181
|
+
>
|
|
182
|
+
<img :src="icon__zoom_fit" alt="" />
|
|
110
183
|
</button>
|
|
111
|
-
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
class="mindmap-preview-action"
|
|
187
|
+
:aria-label="isFullscreen ? '退出全屏' : '全屏查看'"
|
|
188
|
+
:title="isFullscreen ? '退出全屏' : '全屏查看'"
|
|
189
|
+
@click="toggleFullscreen"
|
|
190
|
+
>
|
|
191
|
+
<img :src="isFullscreen ? icon__fullscreen_exit : icon__fullscreen" alt="" />
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
112
194
|
|
|
113
195
|
<nav v-if="session && session.focusPath.length > 0" class="mindmap-focus-path" aria-label="当前主题路径">
|
|
114
196
|
<button type="button" @click="exitFocusTo(0)">全部</button>
|
|
@@ -118,7 +200,14 @@ onBeforeUnmount(() => {
|
|
|
118
200
|
</template>
|
|
119
201
|
</nav>
|
|
120
202
|
|
|
121
|
-
<div
|
|
203
|
+
<div
|
|
204
|
+
v-show="activeView === 'mindmap'"
|
|
205
|
+
ref="canvasHost"
|
|
206
|
+
class="mindmap-canvas-host"
|
|
207
|
+
:class="{ 'is-interaction-active': isCanvasActive }"
|
|
208
|
+
@pointerdown.capture="activateCanvas"
|
|
209
|
+
@wheel.capture="handleCanvasWheelCapture"
|
|
210
|
+
/>
|
|
122
211
|
|
|
123
212
|
<div v-if="activeView === 'outline' && session" class="mindmap-outline" :data-version="renderVersion">
|
|
124
213
|
<ul class="mindmap-outline-root">
|
|
@@ -139,6 +228,7 @@ onBeforeUnmount(() => {
|
|
|
139
228
|
.mindmap-preview {
|
|
140
229
|
--mindmap-panel: var(--vp-c-bg-soft);
|
|
141
230
|
--mindmap-border: var(--vp-c-divider);
|
|
231
|
+
position: relative;
|
|
142
232
|
margin: 1.5rem 0;
|
|
143
233
|
overflow: hidden;
|
|
144
234
|
border: 1px solid var(--mindmap-border);
|
|
@@ -146,36 +236,75 @@ onBeforeUnmount(() => {
|
|
|
146
236
|
background: var(--vp-c-bg);
|
|
147
237
|
}
|
|
148
238
|
|
|
149
|
-
.mindmap-preview-
|
|
239
|
+
.mindmap-preview-actions {
|
|
240
|
+
position: absolute;
|
|
241
|
+
top: 8px;
|
|
242
|
+
right: 8px;
|
|
243
|
+
z-index: 20;
|
|
150
244
|
display: flex;
|
|
151
245
|
align-items: center;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
border-
|
|
156
|
-
background: var(--
|
|
246
|
+
gap: 4px;
|
|
247
|
+
padding: 2px;
|
|
248
|
+
border: .1px solid var(--vp-c-divider);
|
|
249
|
+
border-radius: 7px;
|
|
250
|
+
background-color: color-mix(in srgb, var(--vp-code-block-bg) 92%, transparent);
|
|
251
|
+
box-shadow: var(--vp-shadow-1);
|
|
252
|
+
opacity: 0;
|
|
253
|
+
pointer-events: none;
|
|
254
|
+
transition: opacity .2s;
|
|
157
255
|
}
|
|
158
256
|
|
|
159
257
|
.mindmap-preview-tabs {
|
|
160
258
|
display: flex;
|
|
161
|
-
gap:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
259
|
+
gap: 4px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.mindmap-preview:hover > .mindmap-preview-actions,
|
|
263
|
+
.mindmap-preview-actions:focus-within {
|
|
264
|
+
opacity: 1;
|
|
265
|
+
pointer-events: auto;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.mindmap-preview-action {
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: center;
|
|
272
|
+
width: 32px;
|
|
273
|
+
height: 32px;
|
|
274
|
+
padding: 0;
|
|
275
|
+
border: 0;
|
|
276
|
+
border-radius: 6px;
|
|
277
|
+
background: transparent;
|
|
278
|
+
color: var(--vp-c-brand-1);
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
transition: background-color .2s, transform .2s;
|
|
281
|
+
|
|
282
|
+
svg,
|
|
283
|
+
img {
|
|
284
|
+
width: 18px;
|
|
285
|
+
height: 18px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
&:hover,
|
|
289
|
+
&.is-active {
|
|
290
|
+
background-color: var(--vp-c-default-soft);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
&.is-active {
|
|
294
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vp-c-brand-1) 35%, transparent);
|
|
175
295
|
}
|
|
296
|
+
|
|
297
|
+
&:hover { transform: scale(1.05); }
|
|
298
|
+
&:active { transform: scale(.95); }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.mindmap-preview-action-divider {
|
|
302
|
+
width: 1px;
|
|
303
|
+
height: 20px;
|
|
304
|
+
margin: 0 1px;
|
|
305
|
+
background: var(--vp-c-divider);
|
|
176
306
|
}
|
|
177
307
|
|
|
178
|
-
.mindmap-fit,
|
|
179
308
|
.mindmap-focus-path button {
|
|
180
309
|
color: var(--vp-c-text-2);
|
|
181
310
|
font-size: 12px;
|
|
@@ -202,6 +331,10 @@ onBeforeUnmount(() => {
|
|
|
202
331
|
background: var(--vp-c-bg);
|
|
203
332
|
touch-action: none;
|
|
204
333
|
user-select: none;
|
|
334
|
+
|
|
335
|
+
&.is-interaction-active {
|
|
336
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vp-c-brand-1) 38%, transparent);
|
|
337
|
+
}
|
|
205
338
|
}
|
|
206
339
|
|
|
207
340
|
.mindmap-canvas-host:deep(.mm-canvas),
|
|
@@ -279,9 +412,31 @@ onBeforeUnmount(() => {
|
|
|
279
412
|
white-space: pre;
|
|
280
413
|
}
|
|
281
414
|
|
|
415
|
+
.mindmap-preview:fullscreen {
|
|
416
|
+
display: flex;
|
|
417
|
+
width: 100%;
|
|
418
|
+
height: 100%;
|
|
419
|
+
margin: 0;
|
|
420
|
+
border: 0;
|
|
421
|
+
border-radius: 0;
|
|
422
|
+
background: var(--vp-c-bg);
|
|
423
|
+
flex-direction: column;
|
|
424
|
+
|
|
425
|
+
.mindmap-canvas-host,
|
|
426
|
+
.mindmap-outline,
|
|
427
|
+
.mindmap-source {
|
|
428
|
+
flex: 1 1 auto;
|
|
429
|
+
max-height: none;
|
|
430
|
+
min-height: 0;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.mindmap-canvas-host { height: auto; }
|
|
434
|
+
}
|
|
435
|
+
|
|
282
436
|
@media (max-width: 768px) {
|
|
283
437
|
.mindmap-canvas-host { height: 360px; }
|
|
284
|
-
.mindmap-preview-
|
|
438
|
+
.mindmap-preview-actions { top: 6px; right: 6px; }
|
|
439
|
+
.mindmap-preview-action { width: 28px; height: 28px; }
|
|
285
440
|
.mindmap-outline { padding-inline: 12px; }
|
|
286
441
|
}
|
|
287
442
|
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
view: 'mindmap' | 'outline' | 'source'
|
|
4
|
+
}>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<svg v-if="view === 'mindmap'" aria-hidden="true" viewBox="0 0 48 48">
|
|
9
|
+
<path d="M0 0h48v48H0z" fill="none" />
|
|
10
|
+
<path
|
|
11
|
+
d="M26 24h16M26 38h16M26 10h16M18 24H6h4m8 14c-6-2-2-14-8-14m8-14c-6 2-2 14-8 14"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="#636cff"
|
|
14
|
+
stroke-linecap="round"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
stroke-width="4"
|
|
17
|
+
/>
|
|
18
|
+
</svg>
|
|
19
|
+
<svg v-else-if="view === 'outline'" aria-hidden="true" viewBox="0 0 48 48">
|
|
20
|
+
<path d="M0 0h48v48H0z" fill="none" />
|
|
21
|
+
<path
|
|
22
|
+
d="M26 24h18m-30 0h4m0 14h26M6 38h4m8-28h26M6 10h4"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="#636cff"
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
stroke-width="4"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
<svg v-else aria-hidden="true" viewBox="0 0 24 24">
|
|
31
|
+
<path d="M0 0h24v24H0z" fill="none" />
|
|
32
|
+
<path
|
|
33
|
+
d="m17 8 1.84 1.85c.773.778 1.16 1.167 1.16 1.65s-.387.872-1.16 1.65L17 15M7 8 5.16 9.85C4.387 10.628 4 11.017 4 11.5s.387.872 1.16 1.65L7 15m7.5-11-5 16"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="#636cff"
|
|
36
|
+
stroke-linecap="round"
|
|
37
|
+
stroke-linejoin="round"
|
|
38
|
+
stroke-width="1.5"
|
|
39
|
+
/>
|
|
40
|
+
</svg>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeMindmapMarkdown,
|
|
5
|
+
parseMindmapFence,
|
|
6
|
+
parseMindmapReference,
|
|
7
|
+
} from './markdown'
|
|
8
|
+
|
|
9
|
+
describe('parseMindmapFence', () => {
|
|
10
|
+
it.each([
|
|
11
|
+
['```mindmap', {}],
|
|
12
|
+
['```mindmap [项目架构]', { title: '项目架构' }],
|
|
13
|
+
['```mindmap [项目架构] 2', { title: '项目架构', initialExpandLevel: 2 }],
|
|
14
|
+
['```mindmap 2 [项目架构]', { title: '项目架构', initialExpandLevel: 2 }],
|
|
15
|
+
['```mindmap 0', { initialExpandLevel: 1 }],
|
|
16
|
+
])('parses %s', (input, expected) => {
|
|
17
|
+
expect(parseMindmapFence(input)).toEqual(expected)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it.each([
|
|
21
|
+
'```markmap',
|
|
22
|
+
'```markmap 2',
|
|
23
|
+
'```markmap {2}',
|
|
24
|
+
'```mindmap {2}',
|
|
25
|
+
'```mindmap {initialExpandLevel=3}',
|
|
26
|
+
])('rejects removed syntax: %s', (input) => {
|
|
27
|
+
expect(parseMindmapFence(input)).toBeNull()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('parseMindmapReference', () => {
|
|
32
|
+
it.each([
|
|
33
|
+
['<<< ./assets/tree.md', { path: './assets/tree.md' }],
|
|
34
|
+
['<<< ./assets/tree.md [项目架构]', { path: './assets/tree.md', title: '项目架构' }],
|
|
35
|
+
['<<< "./assets/tree with spaces.md" [项目架构]', { path: './assets/tree with spaces.md', title: '项目架构' }],
|
|
36
|
+
])('parses %s', (input, expected) => {
|
|
37
|
+
expect(parseMindmapReference(input)).toEqual(expected)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('normalizeMindmapMarkdown', () => {
|
|
42
|
+
it('injects the default root for unordered-list input', () => {
|
|
43
|
+
expect(normalizeMindmapMarkdown('- A\n - B')).toBe('# root\n\n- A\n - B\n')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('does not promote an explicit root list item', () => {
|
|
47
|
+
expect(normalizeMindmapMarkdown('- root\n - item1\n - item2')).toBe(
|
|
48
|
+
'# root\n\n- root\n - item1\n - item2\n',
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('preserves an existing H1 and lets explicit fence metadata override it', () => {
|
|
53
|
+
const source = '# Existing\n\n- A\n'
|
|
54
|
+
expect(normalizeMindmapMarkdown(source)).toBe(source)
|
|
55
|
+
expect(normalizeMindmapMarkdown(source, { title: 'Explicit' })).toBe('# Explicit\n\n- A\n')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('does not convert H2-H6 headings into list nodes', () => {
|
|
59
|
+
const source = '# Existing\n\n## Section\n\n- A'
|
|
60
|
+
expect(normalizeMindmapMarkdown(source)).toBe('# Existing\n\n## Section\n\n- A\n')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('is stable when normalized more than once', () => {
|
|
64
|
+
const once = normalizeMindmapMarkdown('- A\n - B')
|
|
65
|
+
expect(normalizeMindmapMarkdown(once)).toBe(once)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface MindmapFenceOptions {
|
|
2
|
+
title?: string
|
|
3
|
+
initialExpandLevel?: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface MindmapReference {
|
|
7
|
+
path: string
|
|
8
|
+
title?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function cleanHeadingText(value: string): string {
|
|
12
|
+
return value.trim().replace(/\s+#+\s*$/, '').trim()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Parse the canonical `mindmap [title] 2` fence metadata. */
|
|
16
|
+
export function parseMindmapFence(openLine: string): MindmapFenceOptions | null {
|
|
17
|
+
const fenceBody = openLine.trim().replace(/^`+\s*/, '')
|
|
18
|
+
const nameMatch = fenceBody.match(/^mindmap(?=\s|\[|$)/)
|
|
19
|
+
if (!nameMatch) return null
|
|
20
|
+
|
|
21
|
+
let rest = fenceBody.slice(nameMatch[0].length).trim()
|
|
22
|
+
const options: MindmapFenceOptions = {}
|
|
23
|
+
const titleMatch = rest.match(/\[([^\]]+)\]/)
|
|
24
|
+
if (titleMatch) {
|
|
25
|
+
options.title = cleanHeadingText(titleMatch[1]) || undefined
|
|
26
|
+
rest = `${rest.slice(0, titleMatch.index)} ${rest.slice((titleMatch.index ?? 0) + titleMatch[0].length)}`.trim()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (rest && !/^\d+$/.test(rest)) return null
|
|
30
|
+
if (rest) {
|
|
31
|
+
options.initialExpandLevel = Math.max(1, Number(rest))
|
|
32
|
+
}
|
|
33
|
+
return options
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Parse `<<< file.md [title]`; the title is optional and paths may be quoted. */
|
|
37
|
+
export function parseMindmapReference(line: string): MindmapReference | null {
|
|
38
|
+
const match = line.trim().match(/^<<<\s+(.+?)\s*$/)
|
|
39
|
+
if (!match) return null
|
|
40
|
+
|
|
41
|
+
let rest = match[1].trim()
|
|
42
|
+
let title: string | undefined
|
|
43
|
+
const titleMatch = rest.match(/\s+\[([^\]]+)\]\s*$/)
|
|
44
|
+
if (titleMatch) {
|
|
45
|
+
title = cleanHeadingText(titleMatch[1]) || undefined
|
|
46
|
+
rest = rest.slice(0, titleMatch.index).trim()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const path = rest.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, '$1$2').trim()
|
|
50
|
+
return path ? { path, title } : null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface NormalizeMindmapOptions {
|
|
54
|
+
title?: string
|
|
55
|
+
defaultTitle?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Ensure canonical mindmap Markdown has exactly one H1 root title. */
|
|
59
|
+
export function normalizeMindmapMarkdown(
|
|
60
|
+
source: string,
|
|
61
|
+
options: NormalizeMindmapOptions = {},
|
|
62
|
+
): string {
|
|
63
|
+
const lines = source.replace(/\r\n?/g, '\n').split('\n')
|
|
64
|
+
let existingTitle = ''
|
|
65
|
+
let rootIndex = -1
|
|
66
|
+
|
|
67
|
+
for (let index = 0; index < lines.length; index++) {
|
|
68
|
+
const match = lines[index].match(/^\s{0,3}#(?!#)\s+(.+?)\s*$/)
|
|
69
|
+
if (!match) continue
|
|
70
|
+
existingTitle = cleanHeadingText(match[1])
|
|
71
|
+
rootIndex = index
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const rootTitle = cleanHeadingText(options.title || existingTitle || options.defaultTitle || 'root') || 'root'
|
|
76
|
+
const body = lines.filter((_, index) => index !== rootIndex)
|
|
77
|
+
while (body[0]?.trim() === '') body.shift()
|
|
78
|
+
while (body[body.length - 1]?.trim() === '') body.pop()
|
|
79
|
+
|
|
80
|
+
return body.length > 0
|
|
81
|
+
? `# ${rootTitle}\n\n${body.join('\n')}\n`
|
|
82
|
+
: `# ${rootTitle}\n`
|
|
83
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { gateMindmapWheel } from './wheelInteraction'
|
|
4
|
+
|
|
5
|
+
describe('gateMindmapWheel', () => {
|
|
6
|
+
it('keeps document scrolling available before the canvas is activated', () => {
|
|
7
|
+
const stop = vi.fn()
|
|
8
|
+
const event = { stopImmediatePropagation: stop } as unknown as WheelEvent
|
|
9
|
+
|
|
10
|
+
gateMindmapWheel(event, false)
|
|
11
|
+
|
|
12
|
+
expect(stop).toHaveBeenCalledOnce()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('lets CanvasViewer handle wheel input after activation', () => {
|
|
16
|
+
const stop = vi.fn()
|
|
17
|
+
const event = { stopImmediatePropagation: stop } as unknown as WheelEvent
|
|
18
|
+
|
|
19
|
+
gateMindmapWheel(event, true)
|
|
20
|
+
|
|
21
|
+
expect(stop).not.toHaveBeenCalled()
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -180,6 +180,7 @@ vitepress/components/Settings/Settings.vue
|
|
|
180
180
|
<script setup lang="ts">
|
|
181
181
|
import { ref, computed, onMounted } from 'vue'
|
|
182
182
|
|
|
183
|
+
import { useLocalIde } from '../composables/useLocalIde'
|
|
183
184
|
import {
|
|
184
185
|
NOTES_DIR_KEY,
|
|
185
186
|
LOCAL_IDE_KEY,
|
|
@@ -188,14 +189,13 @@ import {
|
|
|
188
189
|
SIDEBAR_UNDONE_PREFIX_KEY,
|
|
189
190
|
CONTENT_WIDTH_MODE_KEY,
|
|
190
191
|
} from '../constants'
|
|
191
|
-
|
|
192
|
+
// @ts-expect-error - VitePress Data Loader
|
|
193
|
+
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
192
194
|
import {
|
|
193
195
|
DEFAULT_LOCAL_IDE,
|
|
194
196
|
normalizeLocalIde,
|
|
195
197
|
type LocalIdeId,
|
|
196
198
|
} from '../utils/vscodePaths'
|
|
197
|
-
// @ts-expect-error - VitePress Data Loader
|
|
198
|
-
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
199
199
|
|
|
200
200
|
type ContentWidthMode = 'wide' | 'standard'
|
|
201
201
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import { useData } from 'vitepress'
|
|
7
7
|
import { ref, computed, watch } from 'vue'
|
|
8
8
|
|
|
9
|
+
import FolderTreeItems from './FolderTreeItems.vue'
|
|
10
|
+
import NotesTrendChart from './NotesTrendChart.vue'
|
|
9
11
|
import {
|
|
10
12
|
icon__fold,
|
|
11
13
|
icon__github,
|
|
@@ -14,10 +16,8 @@ import {
|
|
|
14
16
|
icon__number_gray,
|
|
15
17
|
icon__number_purple,
|
|
16
18
|
} from '../../assets/icons'
|
|
17
|
-
import { NOTES_DIR_KEY, REPO_NAME, AUTHOR, ROOT_ITEM } from '../constants.ts'
|
|
18
19
|
import { useLocalIde } from '../composables/useLocalIde'
|
|
19
|
-
import
|
|
20
|
-
import NotesTrendChart from './NotesTrendChart.vue'
|
|
20
|
+
import { NOTES_DIR_KEY, REPO_NAME, AUTHOR, ROOT_ITEM } from '../constants.ts'
|
|
21
21
|
import { useSettingsDialog } from '../Settings/composables/useSettingsDialog'
|
|
22
22
|
import { data as sidebarConfig } from '../sidebar.data'
|
|
23
23
|
import {
|
|
@@ -53,11 +53,6 @@ export const NOTES_VIEW_KEY: string = 'NOTES_VIEW_KEY__' + REPO_NAME
|
|
|
53
53
|
export const EN_WORD_LIST_COMP_IS_AUTO_SHOW_CARD: string =
|
|
54
54
|
'EN_WORD_LIST_COMP_IS_AUTO_SHOW_CARD__' + REPO_NAME
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
* MarkMap 默认主题配置
|
|
58
|
-
*/
|
|
59
|
-
export const MARKMAP_THEME_KEY: string = 'MARKMAP_THEME_KEY__' + REPO_NAME
|
|
60
|
-
|
|
61
56
|
/**
|
|
62
57
|
* 侧边栏是否显示笔记编号配置
|
|
63
58
|
*/
|