@tnotesjs/core 0.2.2 → 0.3.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 +2 -2
- package/package.json +1 -1
- 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/MarkMap/MarkMap.vue +6 -13
- 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/theme/styles/doc-layout.scss +4 -4
- package/services/timestamp/index.ts +0 -7
- package/services/timestamp/service.ts +0 -465
|
@@ -12,17 +12,19 @@ import { onContentUpdated } from 'vitepress'
|
|
|
12
12
|
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
13
13
|
|
|
14
14
|
import { icon__fullscreen, icon__fullscreen_exit, icon__confirm } from '../../assets/icons'
|
|
15
|
-
import { MARKMAP_THEME_KEY
|
|
15
|
+
import { MARKMAP_THEME_KEY } from '../constants'
|
|
16
16
|
|
|
17
17
|
// doc: https://github.com/markmap/markmap/blob/205367a24603dc187f67da1658940c6cade20dce/packages/markmap-view/src/constants.ts#L15
|
|
18
18
|
|
|
19
|
+
const DEFAULT_EXPAND_LEVEL = 3
|
|
20
|
+
|
|
19
21
|
const props = defineProps({
|
|
20
22
|
content: { type: String, default: '' },
|
|
21
23
|
duration: { type: Number, default: 100 },
|
|
22
24
|
spacingVertical: { type: Number, default: 10 },
|
|
23
25
|
spacingHorizontal: { type: Number, default: 20 },
|
|
24
26
|
nodeMinHeight: { type: Number, default: 24 },
|
|
25
|
-
initialExpandLevel: { type: Number, default:
|
|
27
|
+
initialExpandLevel: { type: Number, default: DEFAULT_EXPAND_LEVEL },
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
const svgRef = ref<SVGSVGElement | null>(null)
|
|
@@ -36,15 +38,6 @@ let darkClassObserver: MutationObserver | null = null
|
|
|
36
38
|
|
|
37
39
|
const isMarkmapDark = ref(false)
|
|
38
40
|
|
|
39
|
-
// 从 localStorage 读取配置,如果没有则使用 props 或默认值
|
|
40
|
-
const getInitialExpandLevel = () => {
|
|
41
|
-
if (typeof window !== 'undefined') {
|
|
42
|
-
const saved = localStorage.getItem(MARKMAP_EXPAND_LEVEL_KEY)
|
|
43
|
-
if (saved) return parseInt(saved)
|
|
44
|
-
}
|
|
45
|
-
return props.initialExpandLevel
|
|
46
|
-
}
|
|
47
|
-
|
|
48
41
|
const getThemeColorFn = () => {
|
|
49
42
|
if (typeof window !== 'undefined') {
|
|
50
43
|
if (document.documentElement.classList.contains('markmap-dark')) {
|
|
@@ -63,8 +56,8 @@ const getThemeColorFn = () => {
|
|
|
63
56
|
return scaleOrdinal(schemePastel2)
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
//
|
|
67
|
-
const expandLevel = ref(
|
|
59
|
+
// 可由 props 初始化,也可以通过工具栏改动
|
|
60
|
+
const expandLevel = ref(props.initialExpandLevel)
|
|
68
61
|
const transformer = new Transformer()
|
|
69
62
|
const isFullscreen = ref(false)
|
|
70
63
|
|
|
@@ -10,33 +10,60 @@ vitepress/components/Settings/Settings.vue
|
|
|
10
10
|
<div class="settingItem">
|
|
11
11
|
<div class="settingMeta">
|
|
12
12
|
<div class="labelLine">
|
|
13
|
-
<span class="itemName"
|
|
13
|
+
<span class="itemName">本地 IDE 打开</span>
|
|
14
14
|
<span class="infoWrap">
|
|
15
15
|
<span class="infoIcon">?</span>
|
|
16
16
|
<span class="tooltip">
|
|
17
|
-
适用于 PC
|
|
17
|
+
适用于 PC 桌面环境。配置本地知识库 notes 目录路径,并选择 VS Code 或 Cursor,即可从页面快速打开知识库或笔记。
|
|
18
18
|
</span>
|
|
19
19
|
</span>
|
|
20
20
|
</div>
|
|
21
21
|
<span v-if="path" class="statusText">已配置</span>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
|
-
<div class="
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
<div class="fieldStack">
|
|
25
|
+
<div class="controlRow">
|
|
26
|
+
<span class="controlLabel">IDE</span>
|
|
27
|
+
<div class="segmented segmented-two">
|
|
28
|
+
<label
|
|
29
|
+
v-for="option in localIdeOptions"
|
|
30
|
+
:key="option.value"
|
|
31
|
+
:class="[
|
|
32
|
+
'segment',
|
|
33
|
+
{ activeSegment: localIde === option.value },
|
|
34
|
+
]"
|
|
35
|
+
>
|
|
36
|
+
<input
|
|
37
|
+
v-model="localIde"
|
|
38
|
+
type="radio"
|
|
39
|
+
name="local-ide"
|
|
40
|
+
:value="option.value"
|
|
41
|
+
/>
|
|
42
|
+
<span>{{ option.label }}</span>
|
|
43
|
+
</label>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<label class="field">
|
|
48
|
+
<span class="controlLabel">知识库路径</span>
|
|
49
|
+
<div class="inputGroup">
|
|
50
|
+
<input
|
|
51
|
+
v-model="path"
|
|
52
|
+
type="text"
|
|
53
|
+
placeholder="/Users/username/notes"
|
|
54
|
+
class="input pathInput"
|
|
55
|
+
/>
|
|
56
|
+
<button
|
|
57
|
+
v-if="path"
|
|
58
|
+
@click="clearPath"
|
|
59
|
+
class="clearBtn"
|
|
60
|
+
title="清空"
|
|
61
|
+
type="button"
|
|
62
|
+
>
|
|
63
|
+
×
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</label>
|
|
40
67
|
</div>
|
|
41
68
|
</div>
|
|
42
69
|
|
|
@@ -128,36 +155,6 @@ vitepress/components/Settings/Settings.vue
|
|
|
128
155
|
</div>
|
|
129
156
|
</div>
|
|
130
157
|
</div>
|
|
131
|
-
|
|
132
|
-
<div class="settingItem">
|
|
133
|
-
<div class="settingMeta">
|
|
134
|
-
<div class="labelLine">
|
|
135
|
-
<span class="itemName">笔记内 MarkMap</span>
|
|
136
|
-
<span class="infoWrap">
|
|
137
|
-
<span class="infoIcon">?</span>
|
|
138
|
-
<span class="tooltip">
|
|
139
|
-
配置笔记中 markmap 代码块的初始展开层级。
|
|
140
|
-
</span>
|
|
141
|
-
</span>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
<div class="inlineGrid">
|
|
146
|
-
<label class="field">
|
|
147
|
-
<span class="controlLabel">展开层级</span>
|
|
148
|
-
<div class="inputWithUnit">
|
|
149
|
-
<input
|
|
150
|
-
v-model.number="markmapExpandLevel"
|
|
151
|
-
type="number"
|
|
152
|
-
min="1"
|
|
153
|
-
max="100"
|
|
154
|
-
class="input"
|
|
155
|
-
/>
|
|
156
|
-
<span class="unit">层</span>
|
|
157
|
-
</div>
|
|
158
|
-
</label>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
158
|
</section>
|
|
162
159
|
|
|
163
160
|
<div class="actionBar">
|
|
@@ -185,12 +182,18 @@ import { ref, computed, onMounted } from 'vue'
|
|
|
185
182
|
|
|
186
183
|
import {
|
|
187
184
|
NOTES_DIR_KEY,
|
|
188
|
-
|
|
185
|
+
LOCAL_IDE_KEY,
|
|
189
186
|
SIDEBAR_SHOW_NOTE_ID_KEY,
|
|
190
187
|
SIDEBAR_DONE_PREFIX_KEY,
|
|
191
188
|
SIDEBAR_UNDONE_PREFIX_KEY,
|
|
192
189
|
CONTENT_WIDTH_MODE_KEY,
|
|
193
190
|
} from '../constants'
|
|
191
|
+
import { useLocalIde } from '../composables/useLocalIde'
|
|
192
|
+
import {
|
|
193
|
+
DEFAULT_LOCAL_IDE,
|
|
194
|
+
normalizeLocalIde,
|
|
195
|
+
type LocalIdeId,
|
|
196
|
+
} from '../utils/vscodePaths'
|
|
194
197
|
// @ts-expect-error - VitePress Data Loader
|
|
195
198
|
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
196
199
|
|
|
@@ -200,6 +203,8 @@ const DEFAULT_CONTENT_WIDTH_MODE: ContentWidthMode = 'wide'
|
|
|
200
203
|
const DEFAULT_DONE_PREFIX = '✅'
|
|
201
204
|
const DEFAULT_UNDONE_PREFIX = '⏰'
|
|
202
205
|
|
|
206
|
+
const { setLocalIde } = useLocalIde()
|
|
207
|
+
|
|
203
208
|
const contentWidthModeOptions: Array<{
|
|
204
209
|
label: string
|
|
205
210
|
value: ContentWidthMode
|
|
@@ -208,10 +213,15 @@ const contentWidthModeOptions: Array<{
|
|
|
208
213
|
{ label: '标准页宽', value: 'standard' },
|
|
209
214
|
]
|
|
210
215
|
|
|
216
|
+
const localIdeOptions: Array<{ label: string; value: LocalIdeId }> = [
|
|
217
|
+
{ label: 'VS Code', value: 'vscode' },
|
|
218
|
+
{ label: 'Cursor', value: 'cursor' },
|
|
219
|
+
]
|
|
220
|
+
|
|
211
221
|
const path = ref('')
|
|
212
222
|
const originalPath = ref('')
|
|
213
|
-
const
|
|
214
|
-
const
|
|
223
|
+
const localIde = ref<LocalIdeId>(DEFAULT_LOCAL_IDE)
|
|
224
|
+
const originalLocalIde = ref<LocalIdeId>(DEFAULT_LOCAL_IDE)
|
|
215
225
|
const showNoteId = ref(false)
|
|
216
226
|
const originalShowNoteId = ref(false)
|
|
217
227
|
const donePrefix = ref(DEFAULT_DONE_PREFIX)
|
|
@@ -227,7 +237,7 @@ const showSuccessToast = ref(false)
|
|
|
227
237
|
const hasChanges = computed(
|
|
228
238
|
() =>
|
|
229
239
|
path.value !== originalPath.value ||
|
|
230
|
-
|
|
240
|
+
localIde.value !== originalLocalIde.value ||
|
|
231
241
|
showNoteId.value !== originalShowNoteId.value ||
|
|
232
242
|
donePrefix.value !== originalDonePrefix.value ||
|
|
233
243
|
undonePrefix.value !== originalUndonePrefix.value ||
|
|
@@ -246,9 +256,9 @@ onMounted(() => {
|
|
|
246
256
|
path.value = savedPath
|
|
247
257
|
originalPath.value = savedPath
|
|
248
258
|
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
259
|
+
const savedLocalIde = normalizeLocalIde(localStorage.getItem(LOCAL_IDE_KEY))
|
|
260
|
+
localIde.value = savedLocalIde
|
|
261
|
+
originalLocalIde.value = savedLocalIde
|
|
252
262
|
|
|
253
263
|
const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
|
|
254
264
|
showNoteId.value =
|
|
@@ -293,17 +303,14 @@ function save() {
|
|
|
293
303
|
contentWidthMode.value !== originalContentWidthMode.value
|
|
294
304
|
|
|
295
305
|
localStorage.setItem(NOTES_DIR_KEY, path.value)
|
|
296
|
-
|
|
297
|
-
MARKMAP_EXPAND_LEVEL_KEY,
|
|
298
|
-
markmapExpandLevel.value.toString(),
|
|
299
|
-
)
|
|
306
|
+
setLocalIde(localIde.value)
|
|
300
307
|
localStorage.setItem(SIDEBAR_SHOW_NOTE_ID_KEY, showNoteId.value.toString())
|
|
301
308
|
localStorage.setItem(SIDEBAR_DONE_PREFIX_KEY, donePrefix.value)
|
|
302
309
|
localStorage.setItem(SIDEBAR_UNDONE_PREFIX_KEY, undonePrefix.value)
|
|
303
310
|
localStorage.setItem(CONTENT_WIDTH_MODE_KEY, contentWidthMode.value)
|
|
304
311
|
|
|
305
312
|
originalPath.value = path.value
|
|
306
|
-
|
|
313
|
+
originalLocalIde.value = localIde.value
|
|
307
314
|
originalShowNoteId.value = showNoteId.value
|
|
308
315
|
originalDonePrefix.value = donePrefix.value
|
|
309
316
|
originalUndonePrefix.value = undonePrefix.value
|
|
@@ -327,7 +334,7 @@ function save() {
|
|
|
327
334
|
|
|
328
335
|
function reset() {
|
|
329
336
|
path.value = originalPath.value
|
|
330
|
-
|
|
337
|
+
localIde.value = originalLocalIde.value
|
|
331
338
|
showNoteId.value = originalShowNoteId.value
|
|
332
339
|
donePrefix.value = originalDonePrefix.value
|
|
333
340
|
undonePrefix.value = originalUndonePrefix.value
|
|
@@ -400,10 +407,12 @@ function reset() {
|
|
|
400
407
|
|
|
401
408
|
.tooltip {
|
|
402
409
|
position: absolute;
|
|
403
|
-
left:
|
|
404
|
-
|
|
405
|
-
|
|
410
|
+
left: 0;
|
|
411
|
+
top: calc(100% + 8px);
|
|
412
|
+
bottom: auto;
|
|
413
|
+
z-index: 20;
|
|
406
414
|
width: 240px;
|
|
415
|
+
max-width: min(240px, calc(100vw - 48px));
|
|
407
416
|
padding: 8px 10px;
|
|
408
417
|
color: var(--vp-c-text-1);
|
|
409
418
|
background: var(--vp-c-bg-elv);
|
|
@@ -414,7 +423,7 @@ function reset() {
|
|
|
414
423
|
line-height: 18px;
|
|
415
424
|
opacity: 0;
|
|
416
425
|
pointer-events: none;
|
|
417
|
-
transform:
|
|
426
|
+
transform: translateY(-4px);
|
|
418
427
|
transition:
|
|
419
428
|
opacity 0.15s ease,
|
|
420
429
|
transform 0.15s ease;
|
|
@@ -423,7 +432,7 @@ function reset() {
|
|
|
423
432
|
.infoWrap:hover .tooltip,
|
|
424
433
|
.infoWrap:focus-within .tooltip {
|
|
425
434
|
opacity: 1;
|
|
426
|
-
transform:
|
|
435
|
+
transform: translateY(0);
|
|
427
436
|
}
|
|
428
437
|
|
|
429
438
|
.inputGroup {
|
|
@@ -699,14 +708,8 @@ function reset() {
|
|
|
699
708
|
}
|
|
700
709
|
|
|
701
710
|
.tooltip {
|
|
702
|
-
left: 0;
|
|
703
711
|
width: min(260px, calc(100vw - 48px));
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.infoWrap:hover .tooltip,
|
|
708
|
-
.infoWrap:focus-within .tooltip {
|
|
709
|
-
transform: translate(0, 0);
|
|
712
|
+
max-width: min(260px, calc(100vw - 48px));
|
|
710
713
|
}
|
|
711
714
|
|
|
712
715
|
.actionBar {
|
|
@@ -6,8 +6,8 @@ vitepress/components/SidebarCard/FolderTreeItems.vue
|
|
|
6
6
|
import {
|
|
7
7
|
icon__sidebar_collapsed,
|
|
8
8
|
icon__sidebar_opened,
|
|
9
|
-
icon__vscode,
|
|
10
9
|
} from '../../assets/icons'
|
|
10
|
+
import { useLocalIde } from '../composables/useLocalIde'
|
|
11
11
|
|
|
12
12
|
const props = defineProps({
|
|
13
13
|
nodes: {
|
|
@@ -30,6 +30,8 @@ const props = defineProps({
|
|
|
30
30
|
|
|
31
31
|
const emit = defineEmits(['toggle-node', 'open-vscode'])
|
|
32
32
|
|
|
33
|
+
const { icon: localIdeIcon, openNoteTitle } = useLocalIde()
|
|
34
|
+
|
|
33
35
|
function hasChildren(node) {
|
|
34
36
|
return node.children?.length > 0
|
|
35
37
|
}
|
|
@@ -109,21 +111,24 @@ function onOpenVscode(node, event) {
|
|
|
109
111
|
<span class="tree-title">{{ getDisplayTitle(node) }}</span>
|
|
110
112
|
</a>
|
|
111
113
|
|
|
112
|
-
<
|
|
114
|
+
<button
|
|
113
115
|
v-else-if="hasChildren(node)"
|
|
116
|
+
type="button"
|
|
114
117
|
class="tree-link tree-link-folder"
|
|
118
|
+
:title="isExpanded(node) ? '折叠分组' : '展开分组'"
|
|
119
|
+
@click="onToggle(node)"
|
|
115
120
|
>
|
|
116
121
|
<span class="tree-title">{{ getDisplayTitle(node) }}</span>
|
|
117
|
-
</
|
|
122
|
+
</button>
|
|
118
123
|
|
|
119
124
|
<button
|
|
120
125
|
v-if="node.relativePath"
|
|
121
126
|
class="vscode-article"
|
|
122
127
|
type="button"
|
|
123
|
-
title="
|
|
128
|
+
:title="openNoteTitle"
|
|
124
129
|
@click="onOpenVscode(node, $event)"
|
|
125
130
|
>
|
|
126
|
-
<img :src="
|
|
131
|
+
<img :src="localIdeIcon" :alt="openNoteTitle" />
|
|
127
132
|
</button>
|
|
128
133
|
</div>
|
|
129
134
|
|
|
@@ -225,9 +230,14 @@ export default {
|
|
|
225
230
|
}
|
|
226
231
|
|
|
227
232
|
.tree-link-folder {
|
|
228
|
-
cursor:
|
|
233
|
+
cursor: pointer;
|
|
229
234
|
font-weight: 600;
|
|
230
235
|
color: var(--vp-c-text-1);
|
|
236
|
+
border: none;
|
|
237
|
+
background: none;
|
|
238
|
+
padding: 0;
|
|
239
|
+
text-align: left;
|
|
240
|
+
font: inherit;
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
.tree-status {
|
|
@@ -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/',
|