@tnotesjs/core 0.0.7 → 0.1.14
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 +6 -3
- package/dist/{chunk-JDVN2QYW.js → chunk-FVKQPBPP.js} +643 -546
- package/dist/{chunk-NASIL5FY.js → chunk-SQVHJR2Z.js} +0 -4
- package/dist/cli/index.js +126 -126
- package/dist/index.js +1 -1
- package/dist/vitepress/config/index.js +229 -208
- package/package.json +16 -3
- package/types/shims.d.ts +3 -4
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +2 -1
- package/vitepress/components/CodeBlockFullscreen/index.ts +2 -1
- package/vitepress/components/Discussions/Discussions.vue +3 -2
- package/vitepress/components/EnWordList/EnWordList.vue +2 -1
- package/vitepress/components/Layout/AboutModal.vue +1 -1
- package/vitepress/components/Layout/AboutPanel.vue +8 -3
- package/vitepress/components/Layout/ContentCollapse.vue +2 -1
- package/vitepress/components/Layout/CustomSidebar.vue +7 -4
- package/vitepress/components/Layout/DocBeforeControls.vue +4 -4
- package/vitepress/components/Layout/DocFooter.vue +2 -1
- package/vitepress/components/Layout/ImagePreview.vue +2 -1
- package/vitepress/components/Layout/Layout.vue +38 -20
- package/vitepress/components/Layout/NoteStatus.vue +2 -1
- package/vitepress/components/Layout/SidebarItems.vue +7 -2
- package/vitepress/components/Layout/SidebarNavBefore.vue +22 -0
- package/vitepress/components/Layout/ToggleFullContent.vue +2 -1
- package/vitepress/components/Layout/ToggleSidebar.vue +2 -1
- package/vitepress/components/Layout/composables/useCollapseControl.ts +1 -1
- package/vitepress/components/Layout/composables/useNoteConfig.ts +2 -4
- package/vitepress/components/Layout/composables/useNoteSave.ts +23 -18
- package/vitepress/components/Layout/composables/useRedirect.ts +8 -5
- package/vitepress/components/Layout/composables/useRenameOverlay.ts +33 -0
- package/vitepress/components/Layout/composables/useRenameRedirect.ts +59 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +7 -4
- package/vitepress/components/Layout/homeReadme.data.ts +1 -1
- package/vitepress/components/LoadingPage/LoadingPage.vue +84 -192
- package/vitepress/components/MarkMap/MarkMap.vue +5 -4
- package/vitepress/components/Mermaid/Mermaid.vue +4 -2
- package/vitepress/components/NotesTable/NotesTable.vue +2 -1
- package/vitepress/components/Settings/Settings.vue +2 -1
- package/vitepress/components/Settings/SettingsDialog.vue +243 -0
- package/vitepress/components/Settings/composables/useSettingsDialog.ts +35 -0
- package/vitepress/components/SidebarCard/MindMapView.vue +10 -9
- package/vitepress/components/SidebarCard/SidebarCard.vue +12 -14
- package/vitepress/config/index.ts +11 -4
- package/vitepress/configs/head.config.ts +2 -1
- package/vitepress/configs/markdown-it.d.ts +6 -3
- package/vitepress/configs/markdown.config.ts +6 -5
- package/vitepress/configs/theme.config.ts +1 -1
- package/vitepress/plugins/buildProgressPlugin.ts +2 -38
- package/vitepress/plugins/fileWatcherBridgePlugin.ts +49 -0
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +3 -2
- package/vitepress/plugins/renameNotePlugin.ts +9 -1
- package/vitepress/plugins/updateConfigPlugin.ts +2 -1
- package/vitepress/theme/index.ts +45 -3
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<script setup lang="ts">
|
|
18
|
-
import { ref, onMounted, watch, computed } from 'vue'
|
|
19
18
|
import { useRoute, useData } from 'vitepress'
|
|
19
|
+
import { ref, onMounted, watch, computed } from 'vue'
|
|
20
|
+
|
|
20
21
|
import SidebarItems from './SidebarItems.vue'
|
|
22
|
+
import { SIDEBAR_SHOW_NOTE_ID_KEY, SIDEBAR_MAX_DEPTH_KEY } from '../constants'
|
|
21
23
|
// @ts-expect-error - VitePress Data Loader
|
|
22
24
|
import { data as sidebarConfig } from '../sidebar.data'
|
|
23
25
|
// @ts-expect-error - VitePress Data Loader
|
|
24
26
|
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
25
|
-
import { SIDEBAR_SHOW_NOTE_ID_KEY, SIDEBAR_MAX_DEPTH_KEY } from '../constants'
|
|
26
27
|
|
|
27
28
|
// 支持递归的侧边栏项类型
|
|
28
29
|
interface SidebarItem {
|
|
@@ -192,7 +193,8 @@ function expandParentGroup(element: HTMLElement) {
|
|
|
192
193
|
|
|
193
194
|
// 向上遍历,收集所有父级 group 的标题文本
|
|
194
195
|
while (currentElement) {
|
|
195
|
-
const groupElement =
|
|
196
|
+
const groupElement: HTMLElement | null =
|
|
197
|
+
currentElement.closest<HTMLElement>('.group')
|
|
196
198
|
if (!groupElement) break
|
|
197
199
|
|
|
198
200
|
const groupTitle = groupElement.querySelector('.group-title span')
|
|
@@ -205,7 +207,8 @@ function expandParentGroup(element: HTMLElement) {
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
// 继续向上查找
|
|
208
|
-
currentElement =
|
|
210
|
+
currentElement =
|
|
211
|
+
groupElement.parentElement?.closest<HTMLElement>('.group') || null
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
console.log(
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
:class="$style.aboutIconButton"
|
|
75
75
|
@click="$emit('open-time-modal')"
|
|
76
76
|
aria-haspopup="dialog"
|
|
77
|
-
:aria-expanded="timeModalOpen
|
|
77
|
+
:aria-expanded="timeModalOpen"
|
|
78
78
|
:title="isHomeReadme ? '关于这个知识库' : '关于这篇笔记'"
|
|
79
79
|
type="button"
|
|
80
80
|
>
|
|
@@ -86,11 +86,11 @@
|
|
|
86
86
|
</template>
|
|
87
87
|
|
|
88
88
|
<script setup lang="ts">
|
|
89
|
-
import { ref } from 'vue'
|
|
90
89
|
import { useData } from 'vitepress'
|
|
91
|
-
import
|
|
92
|
-
import ToggleFullContent from './ToggleFullContent.vue'
|
|
90
|
+
import { ref } from 'vue'
|
|
93
91
|
|
|
92
|
+
import ToggleFullContent from './ToggleFullContent.vue'
|
|
93
|
+
import ToggleSidebar from './ToggleSidebar.vue'
|
|
94
94
|
import {
|
|
95
95
|
icon__github,
|
|
96
96
|
icon__vscode,
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
</template>
|
|
26
26
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
|
-
import { computed, ref, onMounted } from 'vue'
|
|
29
28
|
import { useRoute, useData } from 'vitepress'
|
|
29
|
+
import { computed, ref, onMounted } from 'vue'
|
|
30
|
+
|
|
30
31
|
import { SIDEBAR_SHOW_NOTE_ID_KEY } from '../constants'
|
|
31
32
|
// @ts-expect-error - VitePress Data Loader
|
|
32
33
|
import { data as sidebarConfig } from '../sidebar.data'
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
</template>
|
|
57
57
|
|
|
58
58
|
<script setup>
|
|
59
|
+
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
60
|
+
|
|
59
61
|
import {
|
|
60
62
|
icon__close,
|
|
61
63
|
icon__next,
|
|
@@ -65,7 +67,6 @@ import {
|
|
|
65
67
|
icon__zoom_out,
|
|
66
68
|
} from '../../assets/icons'
|
|
67
69
|
|
|
68
|
-
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
69
70
|
|
|
70
71
|
const preview = ref({
|
|
71
72
|
visible: false,
|
|
@@ -156,6 +156,7 @@
|
|
|
156
156
|
@toggle-expand="toggleSidebarSections"
|
|
157
157
|
@toggle-note-id="toggleNoteId"
|
|
158
158
|
@focus-current="focusCurrentNote"
|
|
159
|
+
@open-settings="openSettings"
|
|
159
160
|
/>
|
|
160
161
|
</template>
|
|
161
162
|
|
|
@@ -180,37 +181,48 @@
|
|
|
180
181
|
<!-- <template #nav-screen-content-before>nav-screen-content-before</template> -->
|
|
181
182
|
<!-- <template #nav-screen-content-after>nav-screen-content-after</template> -->
|
|
182
183
|
</Layout>
|
|
184
|
+
|
|
185
|
+
<!-- 全局重命名遮罩:关于面板保存或文件系统重命名时由 useRenameOverlay 控制 -->
|
|
186
|
+
<LoadingPage
|
|
187
|
+
:visible="renameOverlayState.visible"
|
|
188
|
+
:message="renameOverlayState.message"
|
|
189
|
+
:tip="renameOverlayState.tip"
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
<!-- 全局设置 Dialog:由 useSettingsDialog 控制显隐 -->
|
|
193
|
+
<SettingsDialog />
|
|
183
194
|
</template>
|
|
184
195
|
|
|
185
196
|
<script setup>
|
|
186
|
-
import Discussions from "../Discussions/Discussions.vue";
|
|
187
|
-
import ImagePreview from "./ImagePreview.vue";
|
|
188
|
-
import Swiper from "./Swiper.vue";
|
|
189
|
-
import ContentCollapse from "./ContentCollapse.vue";
|
|
190
|
-
import AboutModal from "./AboutModal.vue";
|
|
191
|
-
import AboutPanel from "./AboutPanel.vue";
|
|
192
|
-
import DocBeforeControls from "./DocBeforeControls.vue";
|
|
193
|
-
import CustomSidebar from "./CustomSidebar.vue";
|
|
194
|
-
import SidebarNavBefore from "./SidebarNavBefore.vue";
|
|
195
|
-
import DocFooter from "./DocFooter.vue";
|
|
196
|
-
import NoteStatus from "./NoteStatus.vue";
|
|
197
|
-
|
|
198
197
|
import { useData, useRoute, useRouter } from "vitepress";
|
|
199
198
|
import DefaultTheme from "vitepress/theme";
|
|
200
199
|
import { computed, onMounted, ref, watch } from "vue";
|
|
201
200
|
|
|
202
|
-
import {
|
|
203
|
-
import { data as readmeData } from "./homeReadme.data.ts";
|
|
201
|
+
import { useCodeBlockFullscreen } from "../CodeBlockFullscreen";
|
|
204
202
|
import { SIDEBAR_SHOW_NOTE_ID_KEY } from "../constants";
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
import
|
|
203
|
+
import { data as allNotesConfig } from "../notesConfig.data.ts";
|
|
204
|
+
import AboutModal from "./AboutModal.vue";
|
|
205
|
+
import AboutPanel from "./AboutPanel.vue";
|
|
206
|
+
import Discussions from "../Discussions/Discussions.vue";
|
|
207
|
+
import LoadingPage from "../LoadingPage/LoadingPage.vue";
|
|
208
|
+
import { useCollapseControl } from "./composables/useCollapseControl";
|
|
208
209
|
import { useNoteConfig } from "./composables/useNoteConfig";
|
|
209
|
-
import { useNoteValidation } from "./composables/useNoteValidation";
|
|
210
210
|
import { useNoteSave } from "./composables/useNoteSave";
|
|
211
|
-
import {
|
|
211
|
+
import { useNoteValidation } from "./composables/useNoteValidation";
|
|
212
|
+
import { useRedirect } from "./composables/useRedirect";
|
|
213
|
+
import { useRenameOverlay } from "./composables/useRenameOverlay";
|
|
212
214
|
import { useVSCodeIntegration } from "./composables/useVSCodeIntegration";
|
|
213
|
-
import
|
|
215
|
+
import ContentCollapse from "./ContentCollapse.vue";
|
|
216
|
+
import CustomSidebar from "./CustomSidebar.vue";
|
|
217
|
+
import DocBeforeControls from "./DocBeforeControls.vue";
|
|
218
|
+
import DocFooter from "./DocFooter.vue";
|
|
219
|
+
import { data as readmeData } from "./homeReadme.data.ts";
|
|
220
|
+
import ImagePreview from "./ImagePreview.vue";
|
|
221
|
+
import NoteStatus from "./NoteStatus.vue";
|
|
222
|
+
import SidebarNavBefore from "./SidebarNavBefore.vue";
|
|
223
|
+
import Swiper from "./Swiper.vue";
|
|
224
|
+
import { useSettingsDialog } from "../Settings/composables/useSettingsDialog";
|
|
225
|
+
import SettingsDialog from "../Settings/SettingsDialog.vue";
|
|
214
226
|
|
|
215
227
|
const { Layout } = DefaultTheme;
|
|
216
228
|
const vpData = useData();
|
|
@@ -220,6 +232,12 @@ const route = useRoute();
|
|
|
220
232
|
// 启用代码块全屏功能
|
|
221
233
|
useCodeBlockFullscreen();
|
|
222
234
|
|
|
235
|
+
// 全局重命名遮罩状态(由 useRenameRedirect 控制 show/hide)
|
|
236
|
+
const { state: renameOverlayState } = useRenameOverlay();
|
|
237
|
+
|
|
238
|
+
// 全局设置 Dialog 控制
|
|
239
|
+
const { open: openSettings } = useSettingsDialog();
|
|
240
|
+
|
|
223
241
|
// 自定义侧边栏引用
|
|
224
242
|
const customSidebarRef = ref(null);
|
|
225
243
|
const showNoteId = ref(false);
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
|
-
import { computed, toRefs, ref, onMounted, nextTick } from 'vue'
|
|
20
19
|
import { useData } from 'vitepress'
|
|
20
|
+
import { computed, toRefs, ref, onMounted, nextTick } from 'vue'
|
|
21
|
+
|
|
21
22
|
import Tooltip from '../Tooltip/Tooltip.vue'
|
|
22
23
|
|
|
23
24
|
const props = defineProps({
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<div v-show="!item.collapsed" class="group-items">
|
|
27
27
|
<SidebarItems
|
|
28
|
-
:items="item
|
|
28
|
+
:items="getChildItems(item)"
|
|
29
29
|
:depth="depth + 1"
|
|
30
30
|
:max-depth="maxDepth"
|
|
31
31
|
:show-note-id="showNoteId"
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
</template>
|
|
56
56
|
|
|
57
57
|
<script setup lang="ts">
|
|
58
|
-
import { computed } from 'vue'
|
|
58
|
+
import { computed } from 'vue'
|
|
59
|
+
|
|
59
60
|
import { icon__sidebar_opened, icon__sidebar_collapsed } from '../../assets/icons'
|
|
60
61
|
|
|
61
62
|
interface SidebarItem {
|
|
@@ -92,6 +93,10 @@ function hasChildren(item: SidebarItem): boolean {
|
|
|
92
93
|
return !!(item.items && item.items.length > 0)
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
function getChildItems(item: SidebarItem): SidebarItem[] {
|
|
97
|
+
return item.items ?? []
|
|
98
|
+
}
|
|
99
|
+
|
|
95
100
|
// 获取项的唯一 key
|
|
96
101
|
function getItemKey(item: SidebarItem): string {
|
|
97
102
|
return item.link || item.text
|
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
>
|
|
31
31
|
<img :src="icon__focus" class="toggle-icon" alt="聚焦当前笔记" />
|
|
32
32
|
</button>
|
|
33
|
+
|
|
34
|
+
<!-- 设置按钮 -->
|
|
35
|
+
<button
|
|
36
|
+
class="toggle-btn settings-btn"
|
|
37
|
+
@click="$emit('open-settings')"
|
|
38
|
+
title="打开设置"
|
|
39
|
+
aria-label="打开设置"
|
|
40
|
+
>
|
|
41
|
+
<span class="settings-icon" aria-hidden="true">⚙️</span>
|
|
42
|
+
</button>
|
|
33
43
|
</div>
|
|
34
44
|
</template>
|
|
35
45
|
|
|
@@ -45,6 +55,7 @@ defineEmits<{
|
|
|
45
55
|
'toggle-expand': []
|
|
46
56
|
'toggle-note-id': []
|
|
47
57
|
'focus-current': []
|
|
58
|
+
'open-settings': []
|
|
48
59
|
}>()
|
|
49
60
|
</script>
|
|
50
61
|
|
|
@@ -89,4 +100,15 @@ defineEmits<{
|
|
|
89
100
|
.toggle-btn:hover .toggle-icon {
|
|
90
101
|
transform: scale(1.1);
|
|
91
102
|
}
|
|
103
|
+
|
|
104
|
+
.settings-icon {
|
|
105
|
+
font-size: 18px;
|
|
106
|
+
line-height: 1;
|
|
107
|
+
display: inline-block;
|
|
108
|
+
transition: transform 0.2s ease;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.toggle-btn:hover .settings-icon {
|
|
112
|
+
transform: scale(1.1);
|
|
113
|
+
}
|
|
92
114
|
</style>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<img @click="toggle" :class="$style.contentToggleBtn" :aria-pressed="full
|
|
2
|
+
<img @click="toggle" :class="$style.contentToggleBtn" :aria-pressed="full" :title="full ? '退出全屏内容区' : '全屏显示内容区'" :src="full ? icon__fullscreen_exit : icon__fullscreen" alt="" />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts">
|
|
6
6
|
import { onMounted, ref } from 'vue'
|
|
7
|
+
|
|
7
8
|
import { icon__fullscreen, icon__fullscreen_exit } from '../../assets/icons'
|
|
8
9
|
|
|
9
10
|
const KEY = 'vp:content:fullscreen'
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<!-- .vitepress\components\Layout\ToggleSidebar.vue -->
|
|
2
2
|
<template>
|
|
3
|
-
<img @click="toggle" :class="$style.sidebarToggleBtn" :aria-pressed="hidden
|
|
3
|
+
<img @click="toggle" :class="$style.sidebarToggleBtn" :aria-pressed="hidden" :title="hidden ? '显示侧边栏' : '隐藏侧边栏'" :src="hidden ? icon__next : icon__prev" alt="" />
|
|
4
4
|
</template>
|
|
5
5
|
|
|
6
6
|
<script setup lang="ts">
|
|
7
7
|
import { onMounted, ref } from 'vue'
|
|
8
|
+
|
|
8
9
|
import { icon__next, icon__prev } from '../../assets/icons'
|
|
9
10
|
|
|
10
11
|
const KEY = 'vp:sidebar:hidden'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ref, computed, watch } from 'vue'
|
|
2
|
-
|
|
3
|
-
import { useData } from 'vitepress'
|
|
2
|
+
|
|
4
3
|
import type { NoteConfig } from '../../../../types'
|
|
4
|
+
import type { Ref } from 'vue'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 管理笔记配置状态
|
|
@@ -13,8 +13,6 @@ export function useNoteConfig(
|
|
|
13
13
|
currentNoteTitle: Ref<string>,
|
|
14
14
|
timeModalOpen: Ref<boolean>
|
|
15
15
|
) {
|
|
16
|
-
const vpData = useData()
|
|
17
|
-
|
|
18
16
|
// 可编辑的配置项
|
|
19
17
|
const editableNoteStatus = ref(false)
|
|
20
18
|
const editableDiscussionsEnabled = ref(false)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { ref, computed } from 'vue'
|
|
2
|
-
import type { Ref, ComputedRef } from 'vue'
|
|
3
1
|
import { useData } from 'vitepress'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { redirectAfterRename } from './useRenameRedirect'
|
|
5
|
+
|
|
4
6
|
import type { NoteConfig } from '../../../../types'
|
|
7
|
+
import type { Ref, ComputedRef } from 'vue'
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* 处理笔记配置的保存和重置逻辑
|
|
@@ -53,6 +56,8 @@ export function useNoteSave(
|
|
|
53
56
|
isSaving.value = true
|
|
54
57
|
savingMessage.value = '正在保存配置...'
|
|
55
58
|
|
|
59
|
+
let renameNewUrl: string | null = null
|
|
60
|
+
|
|
56
61
|
try {
|
|
57
62
|
// 如果标题有变化,先重命名文件夹
|
|
58
63
|
if (titleChanged) {
|
|
@@ -74,10 +79,17 @@ export function useNoteSave(
|
|
|
74
79
|
throw new Error(`重命名失败: ${error}`)
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
//
|
|
78
|
-
const result = await renameResponse.json()
|
|
79
|
-
|
|
82
|
+
// 后端已经完成所有更新(含文件系统、根 README、sidebar)
|
|
83
|
+
const result = (await renameResponse.json()) as {
|
|
84
|
+
success: boolean
|
|
85
|
+
newUrl?: string
|
|
86
|
+
}
|
|
80
87
|
|
|
88
|
+
if (!result?.newUrl) {
|
|
89
|
+
throw new Error('重命名响应缺少 newUrl 字段')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
renameNewUrl = result.newUrl
|
|
81
93
|
savingMessage.value = '文件已同步,准备跳转...'
|
|
82
94
|
}
|
|
83
95
|
|
|
@@ -131,21 +143,14 @@ export function useNoteSave(
|
|
|
131
143
|
showSuccessToast.value = false
|
|
132
144
|
}, 3000)
|
|
133
145
|
|
|
134
|
-
//
|
|
135
|
-
if (titleChanged) {
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!configId) {
|
|
140
|
-
throw new Error('无法获取笔记的 configId')
|
|
141
|
-
}
|
|
146
|
+
// 如果标题改变了, 显示遮罩并整页跳到新 URL
|
|
147
|
+
if (titleChanged && renameNewUrl) {
|
|
148
|
+
// 提前结束「保存中」状态,避免遮罩退出后按钮文案残留
|
|
149
|
+
isSaving.value = false
|
|
150
|
+
savingMessage.value = ''
|
|
142
151
|
|
|
143
|
-
// 跳转到loading页,传递 configId 参数
|
|
144
152
|
const base = vpData.site.value.base || '/'
|
|
145
|
-
|
|
146
|
-
configId
|
|
147
|
-
)}`
|
|
148
|
-
window.location.href = loadingUrl
|
|
153
|
+
await redirectAfterRename(renameNewUrl, { base })
|
|
149
154
|
}
|
|
150
155
|
} catch (error) {
|
|
151
156
|
console.error('保存配置失败:', error)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ref, watch } from 'vue'
|
|
2
1
|
import { useRoute, useData } from 'vitepress'
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
3
|
|
|
4
4
|
import type { NoteConfig } from '../../../../types'
|
|
5
5
|
|
|
@@ -32,11 +32,14 @@ export function useRedirect(allNotesConfig: Record<string, NoteConfig & { redire
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
if (match) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const matchedNoteId = match[1]
|
|
36
|
+
if (!matchedNoteId) return false
|
|
37
|
+
|
|
38
|
+
matchedId.value = matchedNoteId
|
|
39
|
+
const targetNote = allNotesConfig[matchedNoteId]
|
|
40
|
+
redirectPath.value = targetNote?.redirect ?? ''
|
|
38
41
|
|
|
39
|
-
if (targetNote
|
|
42
|
+
if (targetNote?.redirect) {
|
|
40
43
|
const base = vpData.site.value.base
|
|
41
44
|
// 构建目标路径(包含基础路径)
|
|
42
45
|
const targetPath = `${base}${targetNote.redirect}`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局「重命名遮罩」状态。
|
|
3
|
+
*
|
|
4
|
+
* Layout 顶层渲染 `<LoadingPage />`,由本 store 控制 visible/message/tip,
|
|
5
|
+
* `useRenameRedirect` 调用 show/hide 即可。
|
|
6
|
+
*/
|
|
7
|
+
import { reactive } from 'vue'
|
|
8
|
+
|
|
9
|
+
export interface RenameOverlayState {
|
|
10
|
+
visible: boolean
|
|
11
|
+
message: string
|
|
12
|
+
tip: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const state = reactive<RenameOverlayState>({
|
|
16
|
+
visible: false,
|
|
17
|
+
message: '正在处理...',
|
|
18
|
+
tip: '',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export function useRenameOverlay() {
|
|
22
|
+
function show(payload: { message?: string; tip?: string } = {}) {
|
|
23
|
+
state.message = payload.message ?? '正在处理...'
|
|
24
|
+
state.tip = payload.tip ?? ''
|
|
25
|
+
state.visible = true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hide() {
|
|
29
|
+
state.visible = false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { state, show, hide }
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 重命名后跳转工具:显示遮罩 → 等 Vite HMR 重建路由 → 整页跳到新 URL。
|
|
3
|
+
*
|
|
4
|
+
* 关于面板保存与文件系统直接重命名两条入口共用此函数,确保行为一致。
|
|
5
|
+
*/
|
|
6
|
+
import { useRenameOverlay } from './useRenameOverlay'
|
|
7
|
+
|
|
8
|
+
export interface RedirectAfterRenameOptions {
|
|
9
|
+
/** 主标题,默认「正在跳转到新地址...」 */
|
|
10
|
+
message?: string
|
|
11
|
+
/** 副提示,默认「请稍候」 */
|
|
12
|
+
tip?: string
|
|
13
|
+
/** 等待 HMR 的毫秒数,默认 200ms(经验值,给 Vite 重算路由表留时间) */
|
|
14
|
+
delayMs?: number
|
|
15
|
+
/** 站点 base,例如 `/TNotes.introduction/`;不传则使用 `import.meta.env.BASE_URL` 或 `/` */
|
|
16
|
+
base?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 根据后端返回的 `newUrl`(形如 `/notes/xxx/README`)跳转到最终地址。
|
|
21
|
+
*
|
|
22
|
+
* 注意:使用 `window.location.replace` 整页跳,避免 SPA 路由表滞后导致 404。
|
|
23
|
+
*/
|
|
24
|
+
export async function redirectAfterRename(
|
|
25
|
+
newUrl: string,
|
|
26
|
+
opts: RedirectAfterRenameOptions = {},
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const { show } = useRenameOverlay()
|
|
29
|
+
|
|
30
|
+
show({
|
|
31
|
+
message: opts.message ?? '正在跳转到新地址...',
|
|
32
|
+
tip: opts.tip ?? '请稍候',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const base =
|
|
36
|
+
opts.base ??
|
|
37
|
+
(typeof import.meta !== 'undefined' ? import.meta.env?.BASE_URL : '/') ??
|
|
38
|
+
'/'
|
|
39
|
+
|
|
40
|
+
const normalizedBase = base.endsWith('/') ? base : `${base}/`
|
|
41
|
+
const normalizedPath = newUrl.replace(/^\/+/, '')
|
|
42
|
+
const finalUrl = normalizedBase + normalizedPath
|
|
43
|
+
|
|
44
|
+
// 关键:先用 replaceState 把地址栏同步切到新 URL。
|
|
45
|
+
// 文件系统重命名场景下,Vite chokidar 会几乎同时触发 full-reload,
|
|
46
|
+
// 如果只用 setTimeout + location.replace,会被 full-reload 抢先以旧 URL 重载页面。
|
|
47
|
+
// 先 replaceState 后,无论谁先重载,浏览器都从新 URL 拉页面。
|
|
48
|
+
try {
|
|
49
|
+
window.history.replaceState(null, '', finalUrl)
|
|
50
|
+
} catch {
|
|
51
|
+
// 跨域等异常下退回 location.replace
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, opts.delayMs ?? 200))
|
|
55
|
+
|
|
56
|
+
// 再做一次显式跳转,确保 SPA 状态完全重置。
|
|
57
|
+
// 此时 location.href 已是 finalUrl,等价于 reload。
|
|
58
|
+
window.location.replace(finalUrl)
|
|
59
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { ref } from 'vue'
|
|
2
|
-
import type { Ref, ComputedRef } from 'vue'
|
|
3
1
|
import { useData } from 'vitepress'
|
|
4
|
-
import
|
|
5
|
-
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { NOTES_DIR_KEY } from '../../constants'
|
|
5
|
+
|
|
6
|
+
import type { Router } from 'vitepress'
|
|
7
|
+
import type { Ref, ComputedRef } from 'vue'
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* VSCode 集成和 GitHub 链接拦截
|