@tnotesjs/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @tnotesjs/core might be problematic. Click here for more details.
- package/README.md +105 -0
- package/dist/chunk-K3X5OP3N.js +1532 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4199 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +9 -0
- package/package.json +74 -0
- package/types/config.ts +61 -0
- package/types/index.ts +11 -0
- package/types/note.ts +33 -0
- package/vitepress/assets/icons/icon__check.svg +3 -0
- package/vitepress/assets/icons/icon__clipboard.svg +8 -0
- package/vitepress/assets/icons/icon__close.svg +1 -0
- package/vitepress/assets/icons/icon__collapse.svg +1 -0
- package/vitepress/assets/icons/icon__confirm.svg +1 -0
- package/vitepress/assets/icons/icon__copy.svg +4 -0
- package/vitepress/assets/icons/icon__focus.svg +1 -0
- package/vitepress/assets/icons/icon__fold.svg +3 -0
- package/vitepress/assets/icons/icon__folder.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -0
- package/vitepress/assets/icons/icon__github.svg +4 -0
- package/vitepress/assets/icons/icon__mindmap.svg +1 -0
- package/vitepress/assets/icons/icon__next.svg +1 -0
- package/vitepress/assets/icons/icon__number_gray.svg +1 -0
- package/vitepress/assets/icons/icon__number_purple.svg +1 -0
- package/vitepress/assets/icons/icon__prev.svg +1 -0
- package/vitepress/assets/icons/icon__restore.svg +1 -0
- package/vitepress/assets/icons/icon__rotate.svg +4 -0
- package/vitepress/assets/icons/icon__search.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -0
- package/vitepress/assets/icons/icon__totop.svg +6 -0
- package/vitepress/assets/icons/icon__vscode.svg +6 -0
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -0
- package/vitepress/assets/icons/index.ts +38 -0
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -0
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -0
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -0
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -0
- package/vitepress/components/Discussions/Discussions.module.scss +32 -0
- package/vitepress/components/Discussions/Discussions.vue +211 -0
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -0
- package/vitepress/components/EnWordList/EnWordList.vue +543 -0
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -0
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -0
- package/vitepress/components/Footprints/Footprints.module.scss +93 -0
- package/vitepress/components/Footprints/Footprints.vue +377 -0
- package/vitepress/components/Layout/AboutModal.module.scss +233 -0
- package/vitepress/components/Layout/AboutModal.vue +105 -0
- package/vitepress/components/Layout/AboutPanel.vue +266 -0
- package/vitepress/components/Layout/ContentCollapse.vue +603 -0
- package/vitepress/components/Layout/CustomSidebar.vue +605 -0
- package/vitepress/components/Layout/DocBeforeControls.vue +139 -0
- package/vitepress/components/Layout/DocFooter.vue +225 -0
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -0
- package/vitepress/components/Layout/ImagePreview.vue +281 -0
- package/vitepress/components/Layout/Layout.module.scss +661 -0
- package/vitepress/components/Layout/Layout.vue +542 -0
- package/vitepress/components/Layout/NoteStatus.vue +140 -0
- package/vitepress/components/Layout/SidebarItems.vue +263 -0
- package/vitepress/components/Layout/SidebarNavBefore.vue +92 -0
- package/vitepress/components/Layout/Swiper.vue +167 -0
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -0
- package/vitepress/components/Layout/ToggleFullContent.vue +34 -0
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -0
- package/vitepress/components/Layout/ToggleSidebar.vue +35 -0
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -0
- package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -0
- package/vitepress/components/Layout/composables/useNoteSave.ts +173 -0
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -0
- package/vitepress/components/Layout/composables/useRedirect.ts +110 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -0
- package/vitepress/components/Layout/homeReadme.data.ts +124 -0
- package/vitepress/components/LoadingPage/LoadingPage.vue +192 -0
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -0
- package/vitepress/components/MarkMap/MarkMap.vue +404 -0
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -0
- package/vitepress/components/Mermaid/Mermaid.vue +364 -0
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -0
- package/vitepress/components/NotesTable/NotesTable.vue +98 -0
- package/vitepress/components/NotesTable/README.md +67 -0
- package/vitepress/components/Settings/Settings.module.scss +433 -0
- package/vitepress/components/Settings/Settings.vue +306 -0
- package/vitepress/components/SidebarCard/MindMapView.vue +483 -0
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -0
- package/vitepress/components/SidebarCard/SidebarCard.vue +948 -0
- package/vitepress/components/Tooltip/Tooltip.vue +70 -0
- package/vitepress/components/constants.ts +91 -0
- package/vitepress/components/notesConfig.data.ts +73 -0
- package/vitepress/components/sidebar.data.ts +59 -0
- package/vitepress/components/tnotes-config.data.ts +21 -0
- package/vitepress/components/utils.ts +26 -0
- package/vitepress/config/index.ts +126 -0
- package/vitepress/configs/constants.ts +26 -0
- package/vitepress/configs/head.config.ts +25 -0
- package/vitepress/configs/index.ts +9 -0
- package/vitepress/configs/markdown-it.d.ts +23 -0
- package/vitepress/configs/markdown.config.ts +366 -0
- package/vitepress/configs/theme.config.ts +108 -0
- package/vitepress/plugins/buildProgressPlugin.ts +390 -0
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -0
- package/vitepress/plugins/renameNotePlugin.ts +60 -0
- package/vitepress/plugins/updateConfigPlugin.ts +63 -0
- package/vitepress/theme/index.ts +95 -0
- package/vitepress/theme/styles/base.scss +50 -0
- package/vitepress/theme/styles/components/404.scss +31 -0
- package/vitepress/theme/styles/components/collapse.scss +175 -0
- package/vitepress/theme/styles/components/markmap.scss +101 -0
- package/vitepress/theme/styles/components/swiper.scss +255 -0
- package/vitepress/theme/styles/index.scss +25 -0
- package/vitepress/theme/styles/layout.scss +62 -0
- package/vitepress/theme/styles/utilities.scss +39 -0
- package/vitepress/theme/styles/vitepress-override.scss +25 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Layout>
|
|
3
|
+
<template v-if="showNotFound" #not-found>
|
|
4
|
+
<div class="not-found-container">
|
|
5
|
+
<h1>404</h1>
|
|
6
|
+
<p>Page not found</p>
|
|
7
|
+
<!-- <div class="debug-info">
|
|
8
|
+
<p>Current path: {{ decodedCurrentPath }}</p>
|
|
9
|
+
<p>Matched ID: {{ matchedId }}</p>
|
|
10
|
+
<p>Redirect path: {{ redirectPath }}</p>
|
|
11
|
+
</div> -->layout
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
<template #doc-top>
|
|
15
|
+
<!-- <pre>vscodesNoteDir: {{ vscodeNotesDir }}</pre> -->
|
|
16
|
+
<!-- <pre>vpData.page.value: {{ vpData.page.value }}</pre>
|
|
17
|
+
<pre>currentNoteConfig: {{ currentNoteConfig }}</pre> -->
|
|
18
|
+
<!-- <button @click="copyRawFile" title="Copy raw file">raw</button> -->
|
|
19
|
+
<!-- <pre>{{ tocData }}</pre> -->
|
|
20
|
+
<ImagePreview />
|
|
21
|
+
<Swiper />
|
|
22
|
+
<ContentCollapse />
|
|
23
|
+
</template>
|
|
24
|
+
<!-- <template #doc-bottom>doc-bottom</template> -->
|
|
25
|
+
<template #doc-before>
|
|
26
|
+
<DocBeforeControls
|
|
27
|
+
:is-full-content-mode="isFullContentMode"
|
|
28
|
+
:vscode-notes-dir="vscodeNotesDir"
|
|
29
|
+
:is-home-readme="isHomeReadme"
|
|
30
|
+
:current-note-id="currentNoteId"
|
|
31
|
+
:created_at="created_at"
|
|
32
|
+
:updated_at="updated_at"
|
|
33
|
+
:home-readme-created-at="homeReadmeCreatedAt"
|
|
34
|
+
:home-readme-updated-at="homeReadmeUpdatedAt"
|
|
35
|
+
:time-modal-open="timeModalOpen"
|
|
36
|
+
:all-collapsed="allCollapsed"
|
|
37
|
+
@open-time-modal="openTimeModal"
|
|
38
|
+
@toggle-all-collapse="toggleAllCollapse"
|
|
39
|
+
/>
|
|
40
|
+
<!-- 笔记状态标题 -->
|
|
41
|
+
<NoteStatus
|
|
42
|
+
:note-config="currentNoteConfig"
|
|
43
|
+
:is-notes-page="isNotesPage"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<AboutModal
|
|
47
|
+
v-model="timeModalOpen"
|
|
48
|
+
@close="onTimeModalClose"
|
|
49
|
+
:title="modalTitle"
|
|
50
|
+
>
|
|
51
|
+
<template #title>
|
|
52
|
+
{{ modalTitle }}
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<AboutPanel
|
|
56
|
+
:is-home-readme="isHomeReadme"
|
|
57
|
+
:current-note-id="currentNoteId"
|
|
58
|
+
:is-dev="isDev"
|
|
59
|
+
v-model:editable-note-title="editableNoteTitle"
|
|
60
|
+
v-model:editable-description="editableDescription"
|
|
61
|
+
v-model:editable-note-status="editableNoteStatus"
|
|
62
|
+
v-model:editable-discussions-enabled="editableDiscussionsEnabled"
|
|
63
|
+
v-model:title-error="titleError"
|
|
64
|
+
:modal-created-at="modalCreatedAt"
|
|
65
|
+
:modal-updated-at="modalUpdatedAt"
|
|
66
|
+
:modal-github-url="modalGithubUrl"
|
|
67
|
+
:modal-github-page-url="modalGithubPageUrl"
|
|
68
|
+
:completion-percentage="completionPercentage"
|
|
69
|
+
:done-notes-len="doneNotesLen"
|
|
70
|
+
:total-notes-len="totalNotesLen"
|
|
71
|
+
@title-input="onTitleInput"
|
|
72
|
+
@title-blur="onTitleBlur"
|
|
73
|
+
@description-input="onDescriptionInput"
|
|
74
|
+
@config-change="onConfigChange"
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<!-- 操作按钮(仅开发环境且非首页显示) -->
|
|
78
|
+
<template #footer v-if="isDev && !isHomeReadme">
|
|
79
|
+
<div :class="$style.actionBar">
|
|
80
|
+
<button
|
|
81
|
+
:class="[
|
|
82
|
+
$style.saveButton,
|
|
83
|
+
{ [$style.disabled]: !hasConfigChanges },
|
|
84
|
+
]"
|
|
85
|
+
@click="saveNoteConfig"
|
|
86
|
+
:disabled="!hasConfigChanges || isSaving"
|
|
87
|
+
type="button"
|
|
88
|
+
>
|
|
89
|
+
{{ saveButtonText }}
|
|
90
|
+
</button>
|
|
91
|
+
<button
|
|
92
|
+
v-if="hasConfigChanges"
|
|
93
|
+
@click="resetNoteConfig"
|
|
94
|
+
:class="$style.resetButton"
|
|
95
|
+
type="button"
|
|
96
|
+
>
|
|
97
|
+
重置
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- 保存进度提示 -->
|
|
102
|
+
<Transition name="toast">
|
|
103
|
+
<div v-if="isSaving && savingMessage" :class="$style.loadingToast">
|
|
104
|
+
<div :class="$style.loadingSpinner"></div>
|
|
105
|
+
<span>{{ savingMessage }}</span>
|
|
106
|
+
</div>
|
|
107
|
+
</Transition>
|
|
108
|
+
|
|
109
|
+
<!-- 保存成功提示 -->
|
|
110
|
+
<Transition name="toast">
|
|
111
|
+
<div v-if="showSuccessToast" :class="$style.toast">✓ 保存成功</div>
|
|
112
|
+
</Transition>
|
|
113
|
+
</template>
|
|
114
|
+
</AboutModal>
|
|
115
|
+
</template>
|
|
116
|
+
<template #doc-footer-before>
|
|
117
|
+
<!-- <div class="footer-time-info">
|
|
118
|
+
<p title="首次提交时间">首次提交时间:{{ formatDate(created_at) }}</p>
|
|
119
|
+
<p title="最近提交时间">最近提交时间:{{ formatDate(updated_at) }}</p>
|
|
120
|
+
</div> -->
|
|
121
|
+
</template>
|
|
122
|
+
<template #doc-after>
|
|
123
|
+
<!-- 自定义 DocFooter -->
|
|
124
|
+
<DocFooter />
|
|
125
|
+
<!-- {{ REPO_NAME + '.' + currentNoteId }} -->
|
|
126
|
+
<Discussions
|
|
127
|
+
v-if="isDiscussionsVisible"
|
|
128
|
+
:id="currentNoteConfig.id"
|
|
129
|
+
:note-number="currentNoteId || ''"
|
|
130
|
+
:note-title="currentNoteTitle"
|
|
131
|
+
/>
|
|
132
|
+
</template>
|
|
133
|
+
<!-- <template #doc-bottom>
|
|
134
|
+
<Discussions id="TNotes.template.0003" />
|
|
135
|
+
</template> -->
|
|
136
|
+
|
|
137
|
+
<template #aside-top>
|
|
138
|
+
<!-- aside-top -->
|
|
139
|
+
<!-- {{ vpData.page.value.title }} -->
|
|
140
|
+
</template>
|
|
141
|
+
<!-- <template #aside-outline-before>
|
|
142
|
+
<span
|
|
143
|
+
@click="scrollToTop"
|
|
144
|
+
style="cursor: pointer; height: 1em; width: 1em"
|
|
145
|
+
title="回到顶部"
|
|
146
|
+
>
|
|
147
|
+
<img :src="icon__totop" alt="to top" />
|
|
148
|
+
</span>
|
|
149
|
+
</template> -->
|
|
150
|
+
|
|
151
|
+
<!-- 使用 sidebar-nav-before 插槽插入控制按钮 -->
|
|
152
|
+
<template #sidebar-nav-before>
|
|
153
|
+
<SidebarNavBefore
|
|
154
|
+
:is-expanded="allSidebarExpanded"
|
|
155
|
+
:show-note-id="showNoteId"
|
|
156
|
+
@toggle-expand="toggleSidebarSections"
|
|
157
|
+
@toggle-note-id="toggleNoteId"
|
|
158
|
+
@focus-current="focusCurrentNote"
|
|
159
|
+
/>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<!-- 使用 sidebar-nav-after 插槽插入自定义 Sidebar -->
|
|
163
|
+
<template #sidebar-nav-after>
|
|
164
|
+
<CustomSidebar ref="customSidebarRef" />
|
|
165
|
+
</template>
|
|
166
|
+
<!-- <template #sidebar-nav-after>sidebar-nav-after</template> -->
|
|
167
|
+
|
|
168
|
+
<!-- <template #aside-outline-after>aside-outline-after</template> -->
|
|
169
|
+
<!-- <template #aside-bottom>aside-bottom</template> -->
|
|
170
|
+
<!-- <template #aside-ads-before>aside-ads-before</template> -->
|
|
171
|
+
<!-- <template #aside-ads-after>aside-ads-after</template> -->
|
|
172
|
+
<!-- <template #layout-top>layout-top</template> -->
|
|
173
|
+
<!-- <template #layout-bottom>layout-bottom</template> -->
|
|
174
|
+
<!-- <template #nav-bar-title-before>nav-bar-title-before</template> -->
|
|
175
|
+
<!-- <template #nav-bar-title-after>nav-bar-title-after</template> -->
|
|
176
|
+
<!-- <template #nav-bar-content-before>nav-bar-content-before</template> -->
|
|
177
|
+
<!-- <template #nav-bar-content-after>nav-bar-content-after</template> -->
|
|
178
|
+
|
|
179
|
+
<!-- !NOTE 不清楚下面的插槽所对应的位置 -->
|
|
180
|
+
<!-- <template #nav-screen-content-before>nav-screen-content-before</template> -->
|
|
181
|
+
<!-- <template #nav-screen-content-after>nav-screen-content-after</template> -->
|
|
182
|
+
</Layout>
|
|
183
|
+
</template>
|
|
184
|
+
|
|
185
|
+
<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
|
+
import { useData, useRoute, useRouter } from "vitepress";
|
|
199
|
+
import DefaultTheme from "vitepress/theme";
|
|
200
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
201
|
+
|
|
202
|
+
import { data as allNotesConfig } from "../notesConfig.data.ts";
|
|
203
|
+
import { data as readmeData } from "./homeReadme.data.ts";
|
|
204
|
+
import { SIDEBAR_SHOW_NOTE_ID_KEY } from "../constants";
|
|
205
|
+
|
|
206
|
+
// Composables
|
|
207
|
+
import { useRedirect } from "./composables/useRedirect";
|
|
208
|
+
import { useNoteConfig } from "./composables/useNoteConfig";
|
|
209
|
+
import { useNoteValidation } from "./composables/useNoteValidation";
|
|
210
|
+
import { useNoteSave } from "./composables/useNoteSave";
|
|
211
|
+
import { useCollapseControl } from "./composables/useCollapseControl";
|
|
212
|
+
import { useVSCodeIntegration } from "./composables/useVSCodeIntegration";
|
|
213
|
+
import { useCodeBlockFullscreen } from "../CodeBlockFullscreen";
|
|
214
|
+
|
|
215
|
+
const { Layout } = DefaultTheme;
|
|
216
|
+
const vpData = useData();
|
|
217
|
+
const router = useRouter();
|
|
218
|
+
const route = useRoute();
|
|
219
|
+
|
|
220
|
+
// 启用代码块全屏功能
|
|
221
|
+
useCodeBlockFullscreen();
|
|
222
|
+
|
|
223
|
+
// 自定义侧边栏引用
|
|
224
|
+
const customSidebarRef = ref(null);
|
|
225
|
+
const showNoteId = ref(false);
|
|
226
|
+
|
|
227
|
+
// 计算是否有展开的一级章节
|
|
228
|
+
const allSidebarExpanded = computed(() => {
|
|
229
|
+
if (!customSidebarRef.value) return false;
|
|
230
|
+
return customSidebarRef.value.hasAnyFirstLevelExpanded();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 初始化笔记编号显示状态
|
|
234
|
+
if (typeof window !== "undefined") {
|
|
235
|
+
const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY);
|
|
236
|
+
showNoteId.value = savedShowNoteId === "true";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 切换侧边栏展开/折叠状态(智能切换)
|
|
240
|
+
function toggleSidebarSections() {
|
|
241
|
+
if (customSidebarRef.value) {
|
|
242
|
+
customSidebarRef.value.toggleExpandCollapse();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 切换笔记编号显示状态
|
|
247
|
+
function toggleNoteId() {
|
|
248
|
+
showNoteId.value = !showNoteId.value;
|
|
249
|
+
if (typeof window !== "undefined") {
|
|
250
|
+
localStorage.setItem(SIDEBAR_SHOW_NOTE_ID_KEY, showNoteId.value.toString());
|
|
251
|
+
// 刷新页面以应用变化
|
|
252
|
+
window.location.reload();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 聚焦到当前笔记
|
|
257
|
+
function focusCurrentNote() {
|
|
258
|
+
if (customSidebarRef.value) {
|
|
259
|
+
customSidebarRef.value.focusCurrentNote();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 提取当前笔记的 ID(前 4 个数字)
|
|
264
|
+
const currentNoteId = computed(() => {
|
|
265
|
+
const relativePath = vpData.page.value.relativePath;
|
|
266
|
+
// relativePath 格式: notes/0001. 标题/README.md
|
|
267
|
+
const match = relativePath.match(/notes\/(\d{4})/);
|
|
268
|
+
const id = match ? match[1] : null;
|
|
269
|
+
|
|
270
|
+
return id;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 判断是否是笔记页面(notes 目录下)
|
|
274
|
+
const isNotesPage = computed(() => {
|
|
275
|
+
return vpData.page.value.relativePath.startsWith("notes/");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// 提取当前笔记的标题(从 relativePath)
|
|
279
|
+
const currentNoteTitle = computed(() => {
|
|
280
|
+
const relativePath = vpData.page.value.relativePath;
|
|
281
|
+
// relativePath 格式: notes/0001. 标题/README.md
|
|
282
|
+
const match = relativePath.match(/notes\/\d{4}\.\s+([^/]+)\//);
|
|
283
|
+
const title = match ? match[1] : "";
|
|
284
|
+
|
|
285
|
+
return title;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// 根据当前笔记 ID 获取配置数据
|
|
289
|
+
const currentNoteConfig = computed(() => {
|
|
290
|
+
return currentNoteId.value && allNotesConfig[currentNoteId.value]
|
|
291
|
+
? allNotesConfig[currentNoteId.value]
|
|
292
|
+
: {
|
|
293
|
+
bilibili: [],
|
|
294
|
+
done: false,
|
|
295
|
+
enableDiscussions: false,
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const isDiscussionsVisible = computed(
|
|
300
|
+
() => currentNoteConfig.value.enableDiscussions,
|
|
301
|
+
);
|
|
302
|
+
const updated_at = computed(() => currentNoteConfig.value.updated_at);
|
|
303
|
+
const created_at = computed(() => currentNoteConfig.value.created_at);
|
|
304
|
+
|
|
305
|
+
// 判断是否为首页 README.md
|
|
306
|
+
const isHomeReadme = computed(() => vpData.page.value.filePath === "README.md");
|
|
307
|
+
const doneNotesLen = computed(() => readmeData?.doneNotesLen || 0);
|
|
308
|
+
const totalNotesLen = computed(() => readmeData?.totalNotesLen || 0);
|
|
309
|
+
|
|
310
|
+
// 完成进度百分比
|
|
311
|
+
const completionPercentage = computed(() => {
|
|
312
|
+
if (!totalNotesLen.value || totalNotesLen.value === 0) return null;
|
|
313
|
+
return Math.round((doneNotesLen.value / totalNotesLen.value) * 100);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// 首页 README.md 的时间戳
|
|
317
|
+
const homeReadmeCreatedAt = computed(() => readmeData?.created_at);
|
|
318
|
+
const homeReadmeUpdatedAt = computed(() => readmeData?.updated_at);
|
|
319
|
+
|
|
320
|
+
// 计算当前笔记的 GitHub URL
|
|
321
|
+
const currentNoteGithubUrl = computed(() => {
|
|
322
|
+
if (!currentNoteId.value) return "";
|
|
323
|
+
|
|
324
|
+
// 从 relativePath 提取笔记路径
|
|
325
|
+
// 格式如: notes/0001. xxx/README.md
|
|
326
|
+
const relativePath = vpData.page.value.relativePath;
|
|
327
|
+
const match = relativePath.match(/notes\/(\d{4}\.[^/]+)/);
|
|
328
|
+
|
|
329
|
+
if (!match) return "";
|
|
330
|
+
|
|
331
|
+
const notePath = match[0]; // notes/0001. xxx
|
|
332
|
+
const repoName = vpData.site.value.title.toLowerCase(); // TNotes.introduction
|
|
333
|
+
|
|
334
|
+
return `https://github.com/tnotesjs/${repoName}/tree/main/${notePath}`;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// #region - Composables
|
|
338
|
+
// 404 重定向
|
|
339
|
+
const { showNotFound, decodedCurrentPath, initRedirectCheck } =
|
|
340
|
+
useRedirect(allNotesConfig);
|
|
341
|
+
|
|
342
|
+
// modal 控制
|
|
343
|
+
const timeModalOpen = ref(false);
|
|
344
|
+
|
|
345
|
+
// 笔记配置管理
|
|
346
|
+
const {
|
|
347
|
+
editableNoteStatus,
|
|
348
|
+
editableDiscussionsEnabled,
|
|
349
|
+
editableNoteTitle,
|
|
350
|
+
editableDescription,
|
|
351
|
+
titleError,
|
|
352
|
+
hasConfigChanges,
|
|
353
|
+
resetNoteConfig,
|
|
354
|
+
updateOriginalValues,
|
|
355
|
+
} = useNoteConfig(
|
|
356
|
+
currentNoteId,
|
|
357
|
+
currentNoteConfig,
|
|
358
|
+
currentNoteTitle,
|
|
359
|
+
timeModalOpen,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// 标题验证
|
|
363
|
+
const { onTitleInput: validateTitleInput, onTitleBlur: validateTitleBlur } =
|
|
364
|
+
useNoteValidation();
|
|
365
|
+
|
|
366
|
+
// 保存逻辑
|
|
367
|
+
const {
|
|
368
|
+
isSaving,
|
|
369
|
+
showSuccessToast,
|
|
370
|
+
savingMessage,
|
|
371
|
+
saveButtonText,
|
|
372
|
+
saveNoteConfig,
|
|
373
|
+
} = useNoteSave(
|
|
374
|
+
currentNoteId,
|
|
375
|
+
computed(() => {
|
|
376
|
+
if (typeof window === "undefined") return false;
|
|
377
|
+
return (
|
|
378
|
+
window.location.hostname === "localhost" ||
|
|
379
|
+
window.location.hostname === "127.0.0.1"
|
|
380
|
+
);
|
|
381
|
+
}),
|
|
382
|
+
hasConfigChanges,
|
|
383
|
+
titleError,
|
|
384
|
+
editableNoteTitle,
|
|
385
|
+
computed(() => currentNoteTitle.value),
|
|
386
|
+
editableNoteStatus,
|
|
387
|
+
computed(() => currentNoteConfig.value.done || false),
|
|
388
|
+
editableDiscussionsEnabled,
|
|
389
|
+
computed(() => currentNoteConfig.value.enableDiscussions || false),
|
|
390
|
+
editableDescription,
|
|
391
|
+
computed(() => currentNoteConfig.value.description || ""),
|
|
392
|
+
allNotesConfig,
|
|
393
|
+
updateOriginalValues,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// 折叠控制
|
|
397
|
+
const { allCollapsed, toggleAllCollapse } = useCollapseControl();
|
|
398
|
+
|
|
399
|
+
// VSCode 集成
|
|
400
|
+
const { vscodeNotesDir, updateVscodeNoteDir, interceptHomeReadmeLinks } =
|
|
401
|
+
useVSCodeIntegration();
|
|
402
|
+
|
|
403
|
+
// 判断是否为开发环境
|
|
404
|
+
const isDev = computed(() => {
|
|
405
|
+
if (typeof window === "undefined") return false;
|
|
406
|
+
return (
|
|
407
|
+
window.location.hostname === "localhost" ||
|
|
408
|
+
window.location.hostname === "127.0.0.1"
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// #endregion
|
|
413
|
+
|
|
414
|
+
// modal 标题
|
|
415
|
+
const modalTitle = computed(() => {
|
|
416
|
+
return isHomeReadme.value ? "关于这个知识库" : "关于这篇笔记";
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// modal 中显示的 GitHub 链接
|
|
420
|
+
const modalGithubUrl = computed(() => {
|
|
421
|
+
if (isHomeReadme.value) {
|
|
422
|
+
const repoName = vpData.site.value.title.toLowerCase();
|
|
423
|
+
return `https://github.com/tnotesjs/${repoName}`;
|
|
424
|
+
}
|
|
425
|
+
return currentNoteGithubUrl.value;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// modal 中显示的 GitHub Page 链接
|
|
429
|
+
const modalGithubPageUrl = computed(() => {
|
|
430
|
+
if (isHomeReadme.value) {
|
|
431
|
+
const repoName = vpData.site.value.title; // 保持原始大小写
|
|
432
|
+
return `https://tnotesjs.github.io/${repoName}/`;
|
|
433
|
+
}
|
|
434
|
+
// 笔记页面的 GitHub Page 链接
|
|
435
|
+
if (currentNoteId.value && currentNoteTitle.value) {
|
|
436
|
+
const repoName = vpData.site.value.title; // 保持原始大小写
|
|
437
|
+
const encodedTitle = encodeURIComponent(currentNoteTitle.value);
|
|
438
|
+
return `https://tnotesjs.github.io/${repoName}/notes/${currentNoteId.value}.%20${encodedTitle}/README`;
|
|
439
|
+
}
|
|
440
|
+
return "";
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// modal 中显示的创建时间
|
|
444
|
+
const modalCreatedAt = computed(() => {
|
|
445
|
+
return isHomeReadme.value ? homeReadmeCreatedAt.value : created_at.value;
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// modal 中显示的更新时间
|
|
449
|
+
const modalUpdatedAt = computed(() => {
|
|
450
|
+
return isHomeReadme.value ? homeReadmeUpdatedAt.value : updated_at.value;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
function openTimeModal() {
|
|
454
|
+
timeModalOpen.value = true;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function onTimeModalClose() {
|
|
458
|
+
timeModalOpen.value = false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 配置变更时的回调
|
|
462
|
+
function onConfigChange() {
|
|
463
|
+
// 配置变更时不需要做额外操作,只需要触发 hasConfigChanges 计算
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 标题输入事件
|
|
467
|
+
function onTitleInput() {
|
|
468
|
+
validateTitleInput(editableNoteTitle, titleError);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 标题失焦事件
|
|
472
|
+
function onTitleBlur() {
|
|
473
|
+
validateTitleBlur(editableNoteTitle, titleError);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 简介输入事件
|
|
477
|
+
function onDescriptionInput() {
|
|
478
|
+
// 简介没有特殊验证,只需要触发变更检测
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// #region - 全屏状态检测
|
|
482
|
+
const isFullContentMode = ref(false);
|
|
483
|
+
|
|
484
|
+
function checkFullContentMode() {
|
|
485
|
+
if (typeof document === "undefined") return;
|
|
486
|
+
|
|
487
|
+
const vpApp = document.querySelector(".VPContent");
|
|
488
|
+
if (vpApp) {
|
|
489
|
+
const sidebar = document.querySelector(".VPSidebar");
|
|
490
|
+
if (sidebar) {
|
|
491
|
+
const sidebarDisplay = window.getComputedStyle(sidebar).display;
|
|
492
|
+
isFullContentMode.value = sidebarDisplay === "none";
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// #endregion
|
|
497
|
+
|
|
498
|
+
// 生命周期钩子
|
|
499
|
+
onMounted(() => {
|
|
500
|
+
updateVscodeNoteDir();
|
|
501
|
+
interceptHomeReadmeLinks(isHomeReadme, router);
|
|
502
|
+
initRedirectCheck();
|
|
503
|
+
|
|
504
|
+
if (typeof window !== "undefined") {
|
|
505
|
+
checkFullContentMode();
|
|
506
|
+
window.addEventListener("resize", checkFullContentMode);
|
|
507
|
+
|
|
508
|
+
// 监听 VitePress 的侧边栏切换事件
|
|
509
|
+
const observer = new MutationObserver(checkFullContentMode);
|
|
510
|
+
const vpLayout = document.querySelector(".Layout");
|
|
511
|
+
if (vpLayout) {
|
|
512
|
+
observer.observe(vpLayout, {
|
|
513
|
+
attributes: true,
|
|
514
|
+
childList: true,
|
|
515
|
+
subtree: true,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// 监听路由变化
|
|
522
|
+
watch(
|
|
523
|
+
() => vpData.page.value.relativePath,
|
|
524
|
+
() => {
|
|
525
|
+
updateVscodeNoteDir();
|
|
526
|
+
interceptHomeReadmeLinks(isHomeReadme, router);
|
|
527
|
+
},
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
watch(
|
|
531
|
+
() => route.path,
|
|
532
|
+
() => {
|
|
533
|
+
setTimeout(checkFullContentMode, 100);
|
|
534
|
+
},
|
|
535
|
+
);
|
|
536
|
+
</script>
|
|
537
|
+
|
|
538
|
+
<style>
|
|
539
|
+
@import "../CodeBlockFullscreen/styles.css";
|
|
540
|
+
</style>
|
|
541
|
+
|
|
542
|
+
<style module src="./Layout.module.scss" scoped></style>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<h1 v-if="shouldShow" class="note-status-title">
|
|
3
|
+
<span v-if="statusEmoji" class="status-emoji">{{ statusEmoji }}</span>
|
|
4
|
+
<Tooltip v-if="githubLink" text="在 GitHub 中打开">
|
|
5
|
+
<a
|
|
6
|
+
:href="githubLink"
|
|
7
|
+
class="note-title-link"
|
|
8
|
+
target="_blank"
|
|
9
|
+
rel="noopener noreferrer"
|
|
10
|
+
>
|
|
11
|
+
<span class="note-title">{{ noteTitle }}</span>
|
|
12
|
+
</a>
|
|
13
|
+
</Tooltip>
|
|
14
|
+
<span v-else class="note-title">{{ noteTitle }}</span>
|
|
15
|
+
</h1>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { computed, toRefs, ref, onMounted, nextTick } from 'vue'
|
|
20
|
+
import { useData } from 'vitepress'
|
|
21
|
+
import Tooltip from '../Tooltip/Tooltip.vue'
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
noteConfig: {
|
|
25
|
+
type: Object,
|
|
26
|
+
default: () => ({}),
|
|
27
|
+
},
|
|
28
|
+
isNotesPage: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const { noteConfig, isNotesPage } = toRefs(props)
|
|
35
|
+
const { page } = useData()
|
|
36
|
+
|
|
37
|
+
// GitHub 链接(从原始 h1 中提取)
|
|
38
|
+
const githubLink = ref('')
|
|
39
|
+
|
|
40
|
+
// 是否应该显示组件
|
|
41
|
+
const shouldShow = computed(() => {
|
|
42
|
+
return isNotesPage.value && noteTitle.value
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// 获取笔记标题(从 page.title 获取)
|
|
46
|
+
const noteTitle = computed(() => {
|
|
47
|
+
return page.value?.title || ''
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// 计算状态 emoji
|
|
51
|
+
const statusEmoji = computed(() => {
|
|
52
|
+
const config = noteConfig.value
|
|
53
|
+
|
|
54
|
+
if (!config) return ''
|
|
55
|
+
|
|
56
|
+
// done 为 false 表示待完成
|
|
57
|
+
if (config.done === false) {
|
|
58
|
+
return '⏰'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// done 为 true 表示已完成
|
|
62
|
+
if (config.done === true) {
|
|
63
|
+
// return '✅'
|
|
64
|
+
// 已完成取消标记
|
|
65
|
+
return ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 默认未完成
|
|
69
|
+
return '⏰'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// 从原始 h1 中提取 GitHub 链接
|
|
73
|
+
function extractGithubLink() {
|
|
74
|
+
nextTick(() => {
|
|
75
|
+
// 查找被隐藏的原始 h1
|
|
76
|
+
const originalH1 = document.querySelector('.vp-doc h1:first-of-type')
|
|
77
|
+
if (originalH1) {
|
|
78
|
+
// 查找 h1 中的链接
|
|
79
|
+
const link = originalH1.querySelector('a')
|
|
80
|
+
if (link) {
|
|
81
|
+
githubLink.value = link.href
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 组件挂载时提取链接
|
|
88
|
+
onMounted(() => {
|
|
89
|
+
extractGithubLink()
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style scoped>
|
|
94
|
+
.note-status-title {
|
|
95
|
+
margin: 32px 0 0;
|
|
96
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
97
|
+
padding-top: 24px;
|
|
98
|
+
letter-spacing: -0.02em;
|
|
99
|
+
line-height: 40px;
|
|
100
|
+
font-size: 32px;
|
|
101
|
+
font-weight: 600;
|
|
102
|
+
text-align: center;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.status-emoji {
|
|
106
|
+
margin-right: 8px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.note-title-link {
|
|
110
|
+
text-decoration: none;
|
|
111
|
+
color: var(--vp-c-brand-1);
|
|
112
|
+
transition: color 0.25s;
|
|
113
|
+
position: relative;
|
|
114
|
+
display: inline-block;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.note-title-link:hover {
|
|
118
|
+
color: var(--vp-c-brand-1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.note-title {
|
|
122
|
+
color: inherit;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* 响应式调整 */
|
|
126
|
+
@media (max-width: 768px) {
|
|
127
|
+
.note-status-title {
|
|
128
|
+
font-size: 28px;
|
|
129
|
+
line-height: 36px;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
|
|
134
|
+
<!-- 全局样式:隐藏 Markdown 渲染的原始 h1 -->
|
|
135
|
+
<style>
|
|
136
|
+
/* 只在笔记页面隐藏第一个 h1(Markdown 渲染的) */
|
|
137
|
+
.vp-doc h1:first-of-type {
|
|
138
|
+
display: none !important;
|
|
139
|
+
}
|
|
140
|
+
</style>
|