@tnotesjs/core 0.1.28 → 0.2.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 +2 -1
- package/package.json +3 -1
- package/templates/sub-repo/.gitattributes +30 -0
- package/templates/sub-repo/.github/workflows/deploy.yml +87 -0
- package/templates/sub-repo/.vitepress/config.mts +12 -0
- package/templates/sub-repo/.vitepress/env.d.ts +10 -0
- package/templates/sub-repo/.vitepress/theme/index.ts +12 -0
- package/templates/sub-repo/.vscode/settings.json +330 -0
- package/templates/sub-repo/.vscode/tnotes.code-snippets +110 -0
- package/templates/sub-repo/public/favicon.ico +0 -0
- package/templates/sub-repo/public/logo.png +0 -0
- package/templates/sub-repo/tsconfig.json +45 -0
- package/vitepress/assets/icons/icon__setting_dark.svg +4 -0
- package/vitepress/assets/icons/icon__setting_light.svg +4 -0
- package/vitepress/assets/icons/index.ts +2 -2
- package/vitepress/client/localSearchIndexBridge.ts +24 -0
- package/vitepress/components/Discussions/Discussions.vue +1 -1
- package/vitepress/components/Footprints/Footprints.vue +90 -25
- package/vitepress/components/Layout/CustomSidebar.vue +489 -117
- package/vitepress/components/Layout/Layout.vue +5 -0
- package/vitepress/components/Layout/NavBarSettingsTrigger.vue +10 -2
- package/vitepress/components/Layout/SidebarItems.vue +517 -342
- package/vitepress/components/Layout/SidebarNavBefore.vue +1 -50
- package/vitepress/components/Layout/SidebarResizeHandle.vue +6 -23
- package/vitepress/components/Layout/composables/docLayoutLogic.ts +59 -0
- package/vitepress/components/Layout/composables/useDocLayout.test.ts +69 -0
- package/vitepress/components/Layout/composables/useDocLayout.ts +128 -0
- package/vitepress/components/Layout/composables/useSidebarDrag.ts +514 -0
- package/vitepress/components/Layout/composables/useSidebarLayout.ts +25 -8
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +8 -3
- package/vitepress/components/Layout/sidebarCollapsedState.test.ts +183 -0
- package/vitepress/components/Layout/sidebarCollapsedState.ts +241 -0
- package/vitepress/components/Layout/sidebarDragLogic.test.ts +1146 -0
- package/vitepress/components/Layout/sidebarDragLogic.ts +1307 -0
- package/vitepress/components/Layout/sidebarHitTest.test.ts +572 -0
- package/vitepress/components/Layout/sidebarHitTest.ts +801 -0
- package/vitepress/components/Layout/sidebarItemRenderLogic.test.ts +53 -0
- package/vitepress/components/Layout/sidebarItemRenderLogic.ts +50 -0
- package/vitepress/components/MarkMap/MarkMap.vue +67 -10
- package/vitepress/components/Settings/Settings.vue +78 -74
- package/vitepress/components/SidebarCard/FolderTreeItems.vue +268 -0
- package/vitepress/components/SidebarCard/SidebarCard.vue +125 -274
- package/vitepress/components/constants.ts +18 -6
- package/vitepress/components/sidebar.data.ts +13 -11
- package/vitepress/components/sidebarTreeHelpers.test.ts +142 -0
- package/vitepress/components/sidebarTreeHelpers.ts +168 -0
- package/vitepress/components/utils/vscodePaths.test.ts +41 -0
- package/vitepress/components/utils/vscodePaths.ts +52 -0
- package/vitepress/config/index.ts +2 -0
- package/vitepress/plugins/localSearchIndexBuilder.ts +209 -0
- package/vitepress/plugins/localSearchReindex.test.ts +46 -0
- package/vitepress/plugins/localSearchReindexLogic.ts +50 -0
- package/vitepress/plugins/localSearchReindexPatch.test.ts +30 -0
- package/vitepress/plugins/localSearchReindexPatch.ts +37 -0
- package/vitepress/plugins/localSearchReindexPlugin.ts +318 -0
- package/vitepress/plugins/sidebarStructurePlugin.ts +209 -63
- package/vitepress/theme/index.ts +8 -0
- package/vitepress/theme/styles/base.scss +0 -8
- package/vitepress/theme/styles/components/markmap.scss +23 -2
- package/vitepress/theme/styles/components/swiper.scss +1 -1
- package/vitepress/theme/styles/doc-layout.scss +212 -0
- package/vitepress/theme/styles/index.scss +3 -0
- package/vitepress/theme/styles/layout.scss +60 -10
- package/dist/chunk-4KG6RXFS.js +0 -4637
- package/dist/chunk-AGSL3VZE.js +0 -193
- package/dist/cli/index.js +0 -683
- package/dist/index.js +0 -8
- package/dist/vitepress/config/index.js +0 -1178
- package/vitepress/assets/icons/icon__mindmap.svg +0 -1
- package/vitepress/assets/icons/icon__setting.svg +0 -1
- package/vitepress/components/SidebarCard/MindMapView.vue +0 -488
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localSearchReindexLogic.ts
|
|
3
|
+
*
|
|
4
|
+
* 纯函数:笔记 README 路径判定(可单测)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
|
|
9
|
+
function slash(value: string): string {
|
|
10
|
+
return value.replace(/\\/g, '/')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NOTE_README_RE =
|
|
14
|
+
/(?:^|[\\/])notes[\\/](\d{4}\.[^\\/]+)[\\/]README\.md$/i
|
|
15
|
+
|
|
16
|
+
export function isNoteReadmePath(
|
|
17
|
+
filePath: string,
|
|
18
|
+
ignoreDirNames: string[] = [],
|
|
19
|
+
): boolean {
|
|
20
|
+
const normalized = filePath.replace(/\\/g, '/')
|
|
21
|
+
const match = NOTE_README_RE.exec(normalized)
|
|
22
|
+
if (!match) return false
|
|
23
|
+
|
|
24
|
+
const folderName = match[1]
|
|
25
|
+
if (!folderName) return false
|
|
26
|
+
|
|
27
|
+
return !ignoreDirNames.some((dir) => folderName === dir)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getDocIdFromFile(input: {
|
|
31
|
+
srcDir: string
|
|
32
|
+
base: string
|
|
33
|
+
cleanUrls: boolean
|
|
34
|
+
rewrites?: Record<string, string>
|
|
35
|
+
absoluteOrRelativeFile: string
|
|
36
|
+
}): string {
|
|
37
|
+
const absoluteFile = path.isAbsolute(input.absoluteOrRelativeFile)
|
|
38
|
+
? input.absoluteOrRelativeFile
|
|
39
|
+
: path.join(input.srcDir, input.absoluteOrRelativeFile)
|
|
40
|
+
|
|
41
|
+
let relFile = slash(path.relative(input.srcDir, absoluteFile))
|
|
42
|
+
if (input.rewrites?.[relFile]) {
|
|
43
|
+
relFile = input.rewrites[relFile]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let id = slash(path.posix.join(input.base, relFile))
|
|
47
|
+
id = id.replace(/(^|\/)index\.md$/, '$1')
|
|
48
|
+
id = id.replace(/\.md$/, input.cleanUrls ? '' : '.html')
|
|
49
|
+
return id
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localSearchReindexPatch.test.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { describe, expect, it } from 'vitest'
|
|
9
|
+
|
|
10
|
+
import { patchVPNavBarSearch } from './localSearchReindexPatch'
|
|
11
|
+
|
|
12
|
+
describe('patchVPNavBarSearch', () => {
|
|
13
|
+
it('patches vitepress VPNavBarSearch with remount key', () => {
|
|
14
|
+
const vpRoot = path.dirname(
|
|
15
|
+
fileURLToPath(import.meta.resolve('vitepress/package.json')),
|
|
16
|
+
)
|
|
17
|
+
const source = fs.readFileSync(
|
|
18
|
+
path.join(
|
|
19
|
+
vpRoot,
|
|
20
|
+
'dist/client/theme-default/components/VPNavBarSearch.vue',
|
|
21
|
+
),
|
|
22
|
+
'utf8',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const patched = patchVPNavBarSearch(source)
|
|
26
|
+
expect(patched).not.toBeNull()
|
|
27
|
+
expect(patched).toContain('localSearchIndexBridge')
|
|
28
|
+
expect(patched).toContain(':key="searchBoxRemountKey"')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localSearchReindexPatch.ts
|
|
3
|
+
*
|
|
4
|
+
* VitePress Local Search 组件 dev patch(可单测)
|
|
5
|
+
*
|
|
6
|
+
* 只 patch VPNavBarSearch(:key remount),不修改 VPLocalSearchBox 索引逻辑。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function patchVPLocalSearchBox(_code: string): string | null {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function patchVPNavBarSearch(code: string): string | null {
|
|
14
|
+
if (code.includes('localSearchIndexBridge')) return null
|
|
15
|
+
if (!code.includes('VPLocalSearchBox')) return null
|
|
16
|
+
|
|
17
|
+
let next = code.replace(
|
|
18
|
+
/<script lang="ts" setup>\r?\n/,
|
|
19
|
+
`<script lang="ts" setup>
|
|
20
|
+
import { searchBoxRemountKey } from '@tnotesjs/core/vitepress/client/localSearchIndexBridge'
|
|
21
|
+
`,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
next = next.replace(
|
|
25
|
+
/<VPLocalSearchBox\r?\n\s+v-if="showSearch"\r?\n\s+@close="showSearch = false"\r?\n\s+\/>/,
|
|
26
|
+
`<VPLocalSearchBox
|
|
27
|
+
v-if="showSearch"
|
|
28
|
+
:key="searchBoxRemountKey"
|
|
29
|
+
@close="showSearch = false"
|
|
30
|
+
/>`,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return next === code ? null : next
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @internal test alias */
|
|
37
|
+
export const patchVPLocalSearchBoxForTest = patchVPLocalSearchBox
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localSearchReindexPlugin.ts
|
|
3
|
+
*
|
|
4
|
+
* dev 下监听笔记 README 变更,debounced 全量重建 Local Search 索引并 HMR 推送。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
buildLocalSearchIndexSnapshot,
|
|
13
|
+
createLocalSearchMarkdownRenderer,
|
|
14
|
+
type LocalSearchIndexSnapshot,
|
|
15
|
+
} from './localSearchIndexBuilder'
|
|
16
|
+
import { isNoteReadmePath } from './localSearchReindexLogic'
|
|
17
|
+
import {
|
|
18
|
+
patchVPNavBarSearch,
|
|
19
|
+
} from './localSearchReindexPatch'
|
|
20
|
+
import { ConfigManager } from '../../config/ConfigManager'
|
|
21
|
+
|
|
22
|
+
import type { PluginOption, ViteDevServer } from 'vite'
|
|
23
|
+
import type { SiteConfig } from 'vitepress'
|
|
24
|
+
|
|
25
|
+
const LOCAL_SEARCH_INDEX_REQUEST_PATH = '/@localSearchIndex'
|
|
26
|
+
const LOCAL_SEARCH_INDEX_ID = '@localSearchIndex'
|
|
27
|
+
const SEARCH_REINDEX_HTTP_PATH = '/__tnotes_search_reindex'
|
|
28
|
+
const SEARCH_INDEX_HMR_EVENT = 'tnotes:search-index-updated'
|
|
29
|
+
const REBUILD_DEBOUNCE_MS = 400
|
|
30
|
+
|
|
31
|
+
const VP_NAV_BAR_SEARCH =
|
|
32
|
+
/vitepress[/\\]dist[/\\]client[/\\]theme-default[/\\]components[/\\]VPNavBarSearch\.vue$/
|
|
33
|
+
|
|
34
|
+
type ReindexController = {
|
|
35
|
+
schedule: (reason: string) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ViteServerConfigWithVp = ViteDevServer['config'] & {
|
|
39
|
+
vitepress?: SiteConfig
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let reindexController: ReindexController | null = null
|
|
43
|
+
let indexSnapshot: LocalSearchIndexSnapshot | null = null
|
|
44
|
+
let snapshotRevision = 0
|
|
45
|
+
|
|
46
|
+
const DEBUG = process.env.TNOTES_DEBUG_SEARCH_REINDEX === '1'
|
|
47
|
+
|
|
48
|
+
function debugLog(message: string, detail?: unknown): void {
|
|
49
|
+
if (!DEBUG) return
|
|
50
|
+
if (detail === undefined) {
|
|
51
|
+
console.log(`[tnotes:search-reindex] ${message}`)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
console.log(`[tnotes:search-reindex] ${message}`, detail)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function logInfo(message: string): void {
|
|
58
|
+
console.warn(`[tnotes:search-reindex] ${message}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeId(id: string): string {
|
|
62
|
+
return id.replace(/\\/g, '/')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getVitePressSiteConfig(server: ViteDevServer): SiteConfig | null {
|
|
66
|
+
return (server.config as ViteServerConfigWithVp).vitepress ?? null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isLocalSearchEnabled(server: ViteDevServer): boolean {
|
|
70
|
+
return server.config.plugins.some(
|
|
71
|
+
(entry) =>
|
|
72
|
+
entry.name === 'vitepress:local-search' && typeof entry.load === 'function',
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeLocalSearchModuleId(id: string): string {
|
|
77
|
+
return id.split('?')[0]!
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getLocaleFromIndexModuleId(id: string): string | null {
|
|
81
|
+
const normalized = normalizeLocalSearchModuleId(id)
|
|
82
|
+
if (normalized === LOCAL_SEARCH_INDEX_REQUEST_PATH) return null
|
|
83
|
+
if (!normalized.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) return null
|
|
84
|
+
return normalized.slice(LOCAL_SEARCH_INDEX_REQUEST_PATH.length) || null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getSearchIndexModuleIds(snapshot: LocalSearchIndexSnapshot): string[] {
|
|
88
|
+
const ids = [LOCAL_SEARCH_INDEX_REQUEST_PATH]
|
|
89
|
+
for (const locale of snapshot.keys()) {
|
|
90
|
+
ids.push(`${LOCAL_SEARCH_INDEX_REQUEST_PATH}${locale}`)
|
|
91
|
+
}
|
|
92
|
+
return ids
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function notifySearchIndexUpdated(
|
|
96
|
+
server: ViteDevServer,
|
|
97
|
+
snapshot: LocalSearchIndexSnapshot,
|
|
98
|
+
revision: number,
|
|
99
|
+
): void {
|
|
100
|
+
const moduleIds = getSearchIndexModuleIds(snapshot)
|
|
101
|
+
const updates: Array<{
|
|
102
|
+
acceptedPath: string
|
|
103
|
+
path: string
|
|
104
|
+
timestamp: number
|
|
105
|
+
type: 'js-update'
|
|
106
|
+
}> = []
|
|
107
|
+
|
|
108
|
+
for (const moduleId of moduleIds) {
|
|
109
|
+
server.moduleGraph.onFileChange(moduleId)
|
|
110
|
+
const mod = server.moduleGraph.getModuleById(moduleId)
|
|
111
|
+
if (mod) {
|
|
112
|
+
server.moduleGraph.invalidateModule(mod)
|
|
113
|
+
updates.push({
|
|
114
|
+
acceptedPath: mod.url,
|
|
115
|
+
path: mod.url,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
type: 'js-update',
|
|
118
|
+
})
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
debugLog(`HMR module not loaded yet: ${moduleId}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (updates.length > 0) {
|
|
125
|
+
server.ws.send({ type: 'update', updates })
|
|
126
|
+
debugLog('HMR pushed', updates.map((item) => item.path))
|
|
127
|
+
} else {
|
|
128
|
+
debugLog('HMR skipped: no loaded search index modules')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
server.ws.send({
|
|
132
|
+
type: 'custom',
|
|
133
|
+
event: SEARCH_INDEX_HMR_EVENT,
|
|
134
|
+
data: { revision },
|
|
135
|
+
})
|
|
136
|
+
debugLog('custom HMR event sent', { revision })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderSnapshotLoad(
|
|
140
|
+
id: string,
|
|
141
|
+
snapshot: LocalSearchIndexSnapshot,
|
|
142
|
+
): string | null {
|
|
143
|
+
const normalizedId = normalizeLocalSearchModuleId(id)
|
|
144
|
+
|
|
145
|
+
if (normalizedId === LOCAL_SEARCH_INDEX_REQUEST_PATH) {
|
|
146
|
+
const records: string[] = []
|
|
147
|
+
for (const locale of snapshot.keys()) {
|
|
148
|
+
records.push(
|
|
149
|
+
`${JSON.stringify(locale)}: () => import('${LOCAL_SEARCH_INDEX_ID}${locale}')`,
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
return `export default {${records.join(',')}}`
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const locale = getLocaleFromIndexModuleId(id)
|
|
156
|
+
if (locale !== null) {
|
|
157
|
+
const payload = snapshot.get(locale) ?? JSON.stringify({})
|
|
158
|
+
return `export default ${payload}`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createDebouncer(delayMs: number) {
|
|
165
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
166
|
+
let pendingReason = ''
|
|
167
|
+
|
|
168
|
+
return (reason: string, task: () => Promise<void>) => {
|
|
169
|
+
pendingReason = reason
|
|
170
|
+
if (timer) clearTimeout(timer)
|
|
171
|
+
timer = setTimeout(async () => {
|
|
172
|
+
timer = null
|
|
173
|
+
const reasonToRun = pendingReason
|
|
174
|
+
pendingReason = ''
|
|
175
|
+
try {
|
|
176
|
+
await task()
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(
|
|
179
|
+
`[tnotes:search-reindex] rebuild failed (${reasonToRun})`,
|
|
180
|
+
error,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}, delayMs)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function matchesSearchReindexPath(url: string): boolean {
|
|
188
|
+
return url === SEARCH_REINDEX_HTTP_PATH || url.endsWith(SEARCH_REINDEX_HTTP_PATH)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 由 sidebar API 等同进程模块调用,调度 debounced 全量索引重建。
|
|
193
|
+
*/
|
|
194
|
+
export function scheduleNoteSearchReindex(reason = 'external'): void {
|
|
195
|
+
if (!reindexController) {
|
|
196
|
+
logInfo(`schedule ignored (plugin not ready): ${reason}`)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
reindexController.schedule(reason)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function localSearchReindexPlugin(): PluginOption {
|
|
203
|
+
return {
|
|
204
|
+
name: 'tnotes-local-search-reindex',
|
|
205
|
+
apply: 'serve',
|
|
206
|
+
enforce: 'pre',
|
|
207
|
+
|
|
208
|
+
resolveId(source) {
|
|
209
|
+
if (!source.startsWith(LOCAL_SEARCH_INDEX_ID)) return null
|
|
210
|
+
const clean = source.split('?')[0]!
|
|
211
|
+
if (clean === source) return null
|
|
212
|
+
return `/${clean}`
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
transform(code, id) {
|
|
216
|
+
const normalizedId = normalizeId(id)
|
|
217
|
+
if (VP_NAV_BAR_SEARCH.test(normalizedId)) {
|
|
218
|
+
return patchVPNavBarSearch(code)
|
|
219
|
+
}
|
|
220
|
+
return null
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
load(id) {
|
|
224
|
+
if (!indexSnapshot || !id.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
return renderSnapshotLoad(id, indexSnapshot)
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
configureServer(server) {
|
|
231
|
+
server.middlewares.use((req, res, next) => {
|
|
232
|
+
const url = req.url ?? ''
|
|
233
|
+
if (!matchesSearchReindexPath(url) || req.method !== 'POST') {
|
|
234
|
+
return next()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let body = ''
|
|
238
|
+
req.on('data', (chunk) => {
|
|
239
|
+
body += chunk.toString()
|
|
240
|
+
})
|
|
241
|
+
req.on('end', () => {
|
|
242
|
+
try {
|
|
243
|
+
const data = JSON.parse(body || '{}') as { reason?: string }
|
|
244
|
+
scheduleNoteSearchReindex(data.reason || 'bridge:http')
|
|
245
|
+
res.statusCode = 204
|
|
246
|
+
res.end()
|
|
247
|
+
} catch (error) {
|
|
248
|
+
res.statusCode = 400
|
|
249
|
+
res.end(
|
|
250
|
+
error instanceof Error ? error.message : 'Bad search reindex payload',
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return async () => {
|
|
257
|
+
indexSnapshot = null
|
|
258
|
+
snapshotRevision = 0
|
|
259
|
+
|
|
260
|
+
if (!isLocalSearchEnabled(server)) {
|
|
261
|
+
reindexController = null
|
|
262
|
+
logInfo('vitepress:local-search not found, reindex disabled')
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const siteConfig = getVitePressSiteConfig(server)
|
|
267
|
+
if (!siteConfig) {
|
|
268
|
+
reindexController = null
|
|
269
|
+
logInfo('server.config.vitepress missing, reindex disabled')
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
ConfigManager.init({ rootPath: process.cwd() })
|
|
274
|
+
const ignoreDirNames =
|
|
275
|
+
ConfigManager.getInstance().getAll().ignore_dirs ?? []
|
|
276
|
+
|
|
277
|
+
const md = await createLocalSearchMarkdownRenderer(siteConfig)
|
|
278
|
+
const debouncedRebuild = createDebouncer(REBUILD_DEBOUNCE_MS)
|
|
279
|
+
|
|
280
|
+
async function rebuildFullIndex(reason: string): Promise<void> {
|
|
281
|
+
const startedAt = Date.now()
|
|
282
|
+
snapshotRevision += 1
|
|
283
|
+
debugLog(`rebuild start (${reason}) rev=${snapshotRevision}`)
|
|
284
|
+
const { snapshot, pageCount } = await buildLocalSearchIndexSnapshot(
|
|
285
|
+
siteConfig,
|
|
286
|
+
md,
|
|
287
|
+
)
|
|
288
|
+
indexSnapshot = snapshot
|
|
289
|
+
notifySearchIndexUpdated(server, indexSnapshot, snapshotRevision)
|
|
290
|
+
logInfo(
|
|
291
|
+
`rebuild done (${reason}) rev=${snapshotRevision} pages=${pageCount} locales=${indexSnapshot.size} elapsed=${Date.now() - startedAt}ms`,
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
reindexController = {
|
|
296
|
+
schedule(reason: string) {
|
|
297
|
+
debugLog(`scheduled (${reason})`)
|
|
298
|
+
debouncedRebuild(reason, () => rebuildFullIndex(reason))
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const onWatcherEvent = (
|
|
303
|
+
absoluteFile: string,
|
|
304
|
+
event: 'add' | 'change' | 'unlink',
|
|
305
|
+
) => {
|
|
306
|
+
if (!isNoteReadmePath(absoluteFile, ignoreDirNames)) return
|
|
307
|
+
scheduleNoteSearchReindex(
|
|
308
|
+
`watcher:${event}:${path.basename(absoluteFile)}`,
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
server.watcher.on('add', (file) => onWatcherEvent(file, 'add'))
|
|
313
|
+
server.watcher.on('change', (file) => onWatcherEvent(file, 'change'))
|
|
314
|
+
server.watcher.on('unlink', (file) => onWatcherEvent(file, 'unlink'))
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
}
|