@tnotesjs/core 0.1.28 → 0.2.1
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/dist/{chunk-4KG6RXFS.js → chunk-5QN44ES5.js} +2946 -1533
- package/dist/{chunk-AGSL3VZE.js → chunk-5Y5IYS6C.js} +7 -0
- package/dist/cli/index.js +343 -40
- package/dist/index.js +1 -1
- package/dist/vitepress/config/index.js +573 -65
- package/package.json +4 -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/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
|
@@ -2,19 +2,19 @@ import {
|
|
|
2
2
|
FileWatcherService,
|
|
3
3
|
NoteIndexCache,
|
|
4
4
|
NoteService,
|
|
5
|
-
ReadmeService,
|
|
6
5
|
RenameNoteCommand,
|
|
6
|
+
TocService,
|
|
7
7
|
UpdateNoteConfigCommand,
|
|
8
8
|
generateAnchor,
|
|
9
9
|
logger
|
|
10
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-5QN44ES5.js";
|
|
11
11
|
import {
|
|
12
12
|
ConfigManager
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-5Y5IYS6C.js";
|
|
14
14
|
|
|
15
15
|
// vitepress/config/index.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import fs3 from "fs";
|
|
17
|
+
import path5 from "path";
|
|
18
18
|
import { defineConfig } from "vitepress";
|
|
19
19
|
|
|
20
20
|
// vitepress/configs/constants.ts
|
|
@@ -764,6 +764,422 @@ function getNoteByConfigIdPlugin() {
|
|
|
764
764
|
};
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
// vitepress/plugins/localSearchReindexPlugin.ts
|
|
768
|
+
import path4 from "path";
|
|
769
|
+
|
|
770
|
+
// vitepress/plugins/localSearchIndexBuilder.ts
|
|
771
|
+
import MiniSearch from "minisearch";
|
|
772
|
+
import fs2 from "fs/promises";
|
|
773
|
+
import path3 from "path";
|
|
774
|
+
import {
|
|
775
|
+
createMarkdownRenderer,
|
|
776
|
+
resolvePages
|
|
777
|
+
} from "vitepress";
|
|
778
|
+
|
|
779
|
+
// vitepress/plugins/localSearchReindexLogic.ts
|
|
780
|
+
import path2 from "path";
|
|
781
|
+
function slash(value) {
|
|
782
|
+
return value.replace(/\\/g, "/");
|
|
783
|
+
}
|
|
784
|
+
var NOTE_README_RE = /(?:^|[\\/])notes[\\/](\d{4}\.[^\\/]+)[\\/]README\.md$/i;
|
|
785
|
+
function isNoteReadmePath(filePath, ignoreDirNames = []) {
|
|
786
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
787
|
+
const match = NOTE_README_RE.exec(normalized);
|
|
788
|
+
if (!match) return false;
|
|
789
|
+
const folderName = match[1];
|
|
790
|
+
if (!folderName) return false;
|
|
791
|
+
return !ignoreDirNames.some((dir) => folderName === dir);
|
|
792
|
+
}
|
|
793
|
+
function getDocIdFromFile(input) {
|
|
794
|
+
const absoluteFile = path2.isAbsolute(input.absoluteOrRelativeFile) ? input.absoluteOrRelativeFile : path2.join(input.srcDir, input.absoluteOrRelativeFile);
|
|
795
|
+
let relFile = slash(path2.relative(input.srcDir, absoluteFile));
|
|
796
|
+
if (input.rewrites?.[relFile]) {
|
|
797
|
+
relFile = input.rewrites[relFile];
|
|
798
|
+
}
|
|
799
|
+
let id = slash(path2.posix.join(input.base, relFile));
|
|
800
|
+
id = id.replace(/(^|\/)index\.md$/, "$1");
|
|
801
|
+
id = id.replace(/\.md$/, input.cleanUrls ? "" : ".html");
|
|
802
|
+
return id;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// vitepress/plugins/localSearchIndexBuilder.ts
|
|
806
|
+
var headingRegex = /<h(\d*).*?>(.*?<a.*? href="#.*?".*?>.*?<\/a>)<\/h\1>/gi;
|
|
807
|
+
var headingContentRegex = /(.*?)<a.*? href="#(.*?)".*?>.*?<\/a>/i;
|
|
808
|
+
function slash2(value) {
|
|
809
|
+
return value.replace(/\\/g, "/");
|
|
810
|
+
}
|
|
811
|
+
function clearHtmlTags(str) {
|
|
812
|
+
return str.replace(/<[^>]*>/g, "");
|
|
813
|
+
}
|
|
814
|
+
function getSearchableText(content) {
|
|
815
|
+
return clearHtmlTags(content);
|
|
816
|
+
}
|
|
817
|
+
function* splitPageIntoSections(html) {
|
|
818
|
+
const result = html.split(headingRegex);
|
|
819
|
+
result.shift();
|
|
820
|
+
let parentTitles = [];
|
|
821
|
+
for (let i = 0; i < result.length; i += 3) {
|
|
822
|
+
const level = parseInt(result[i], 10) - 1;
|
|
823
|
+
const heading = result[i + 1];
|
|
824
|
+
const headingResult = headingContentRegex.exec(heading);
|
|
825
|
+
const title = clearHtmlTags(headingResult?.[1] ?? "").trim();
|
|
826
|
+
const anchor = headingResult?.[2] ?? "";
|
|
827
|
+
const content = result[i + 2];
|
|
828
|
+
if (!title || !content) continue;
|
|
829
|
+
let titles = parentTitles.slice(0, level);
|
|
830
|
+
titles[level] = title;
|
|
831
|
+
titles = titles.filter(Boolean);
|
|
832
|
+
yield { anchor, titles, text: getSearchableText(content) };
|
|
833
|
+
if (level === 0) {
|
|
834
|
+
parentTitles = [title];
|
|
835
|
+
} else {
|
|
836
|
+
parentTitles[level] = title;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function getLocaleForPath(site, page) {
|
|
841
|
+
const normalizedPage = slash2(page);
|
|
842
|
+
for (const [localePath, localeConfig] of Object.entries(site.locales ?? {})) {
|
|
843
|
+
const prefix = localePath.replace(/\\/g, "/");
|
|
844
|
+
if (prefix === "root") continue;
|
|
845
|
+
const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
846
|
+
if (normalizedPage.startsWith(normalizedPrefix)) {
|
|
847
|
+
return localePath;
|
|
848
|
+
}
|
|
849
|
+
if (localeConfig?.lang && normalizedPage === prefix.replace(/\/$/, "")) {
|
|
850
|
+
return localePath;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return "root";
|
|
854
|
+
}
|
|
855
|
+
async function createLocalSearchMarkdownRenderer(siteConfig) {
|
|
856
|
+
return createMarkdownRenderer(
|
|
857
|
+
siteConfig.srcDir,
|
|
858
|
+
siteConfig.markdown,
|
|
859
|
+
siteConfig.site.base,
|
|
860
|
+
siteConfig.logger
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
async function renderPageHtml(siteConfig, md, absoluteFile) {
|
|
864
|
+
if (!await fs2.stat(absoluteFile).catch(() => null)) return "";
|
|
865
|
+
const srcDir = siteConfig.srcDir;
|
|
866
|
+
const relativePath = slash2(path3.relative(srcDir, absoluteFile));
|
|
867
|
+
const env = {
|
|
868
|
+
path: absoluteFile,
|
|
869
|
+
relativePath,
|
|
870
|
+
cleanUrls: siteConfig.cleanUrls ?? false,
|
|
871
|
+
frontmatter: {}
|
|
872
|
+
};
|
|
873
|
+
const options = siteConfig.site.themeConfig?.search?.options ?? {};
|
|
874
|
+
const mdRaw = await fs2.readFile(absoluteFile, "utf-8");
|
|
875
|
+
if (options._render) {
|
|
876
|
+
return await options._render(mdRaw, env, md);
|
|
877
|
+
}
|
|
878
|
+
const html = md.render(mdRaw, env);
|
|
879
|
+
return env.frontmatter?.search === false ? "" : html;
|
|
880
|
+
}
|
|
881
|
+
async function indexPage(siteConfig, md, page, index) {
|
|
882
|
+
const absoluteFile = path3.join(siteConfig.srcDir, page);
|
|
883
|
+
const fileId = getDocIdFromFile({
|
|
884
|
+
srcDir: siteConfig.srcDir,
|
|
885
|
+
base: siteConfig.site.base,
|
|
886
|
+
cleanUrls: siteConfig.cleanUrls ?? false,
|
|
887
|
+
rewrites: siteConfig.rewrites.map,
|
|
888
|
+
absoluteOrRelativeFile: absoluteFile
|
|
889
|
+
});
|
|
890
|
+
const html = await renderPageHtml(siteConfig, md, absoluteFile);
|
|
891
|
+
const sections = splitPageIntoSections(html);
|
|
892
|
+
for (const section of sections) {
|
|
893
|
+
if (!section || !(section.text || section.titles)) break;
|
|
894
|
+
const { anchor, text, titles } = section;
|
|
895
|
+
const id = anchor ? [fileId, anchor].join("#") : fileId;
|
|
896
|
+
if (index.has(id)) index.discard(id);
|
|
897
|
+
index.add({
|
|
898
|
+
id,
|
|
899
|
+
text,
|
|
900
|
+
title: titles[titles.length - 1],
|
|
901
|
+
titles: titles.slice(0, -1)
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
async function buildLocalSearchIndexSnapshot(siteConfig, md) {
|
|
906
|
+
const { pages } = await resolvePages(
|
|
907
|
+
siteConfig.srcDir,
|
|
908
|
+
siteConfig.userConfig,
|
|
909
|
+
siteConfig.logger
|
|
910
|
+
);
|
|
911
|
+
const options = siteConfig.site.themeConfig?.search?.options ?? {};
|
|
912
|
+
const indexByLocale = /* @__PURE__ */ new Map();
|
|
913
|
+
for (const page of pages) {
|
|
914
|
+
const locale = getLocaleForPath(siteConfig.site, page);
|
|
915
|
+
let index = indexByLocale.get(locale);
|
|
916
|
+
if (!index) {
|
|
917
|
+
index = new MiniSearch({
|
|
918
|
+
fields: ["title", "titles", "text"],
|
|
919
|
+
storeFields: ["title", "titles"],
|
|
920
|
+
...options.miniSearch?.options
|
|
921
|
+
});
|
|
922
|
+
indexByLocale.set(locale, index);
|
|
923
|
+
}
|
|
924
|
+
await indexPage(siteConfig, md, page, index);
|
|
925
|
+
}
|
|
926
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const [locale, index] of indexByLocale) {
|
|
928
|
+
snapshot.set(locale, JSON.stringify(JSON.stringify(index)));
|
|
929
|
+
}
|
|
930
|
+
return { snapshot, pageCount: pages.length };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// vitepress/plugins/localSearchReindexPatch.ts
|
|
934
|
+
function patchVPNavBarSearch(code) {
|
|
935
|
+
if (code.includes("localSearchIndexBridge")) return null;
|
|
936
|
+
if (!code.includes("VPLocalSearchBox")) return null;
|
|
937
|
+
let next = code.replace(
|
|
938
|
+
/<script lang="ts" setup>\r?\n/,
|
|
939
|
+
`<script lang="ts" setup>
|
|
940
|
+
import { searchBoxRemountKey } from '@tnotesjs/core/vitepress/client/localSearchIndexBridge'
|
|
941
|
+
`
|
|
942
|
+
);
|
|
943
|
+
next = next.replace(
|
|
944
|
+
/<VPLocalSearchBox\r?\n\s+v-if="showSearch"\r?\n\s+@close="showSearch = false"\r?\n\s+\/>/,
|
|
945
|
+
`<VPLocalSearchBox
|
|
946
|
+
v-if="showSearch"
|
|
947
|
+
:key="searchBoxRemountKey"
|
|
948
|
+
@close="showSearch = false"
|
|
949
|
+
/>`
|
|
950
|
+
);
|
|
951
|
+
return next === code ? null : next;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// vitepress/plugins/localSearchReindexPlugin.ts
|
|
955
|
+
var LOCAL_SEARCH_INDEX_REQUEST_PATH = "/@localSearchIndex";
|
|
956
|
+
var LOCAL_SEARCH_INDEX_ID = "@localSearchIndex";
|
|
957
|
+
var SEARCH_REINDEX_HTTP_PATH = "/__tnotes_search_reindex";
|
|
958
|
+
var SEARCH_INDEX_HMR_EVENT = "tnotes:search-index-updated";
|
|
959
|
+
var REBUILD_DEBOUNCE_MS = 400;
|
|
960
|
+
var VP_NAV_BAR_SEARCH = /vitepress[/\\]dist[/\\]client[/\\]theme-default[/\\]components[/\\]VPNavBarSearch\.vue$/;
|
|
961
|
+
var reindexController = null;
|
|
962
|
+
var indexSnapshot = null;
|
|
963
|
+
var snapshotRevision = 0;
|
|
964
|
+
var DEBUG = process.env.TNOTES_DEBUG_SEARCH_REINDEX === "1";
|
|
965
|
+
function debugLog(message, detail) {
|
|
966
|
+
if (!DEBUG) return;
|
|
967
|
+
if (detail === void 0) {
|
|
968
|
+
console.log(`[tnotes:search-reindex] ${message}`);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
console.log(`[tnotes:search-reindex] ${message}`, detail);
|
|
972
|
+
}
|
|
973
|
+
function logInfo(message) {
|
|
974
|
+
console.warn(`[tnotes:search-reindex] ${message}`);
|
|
975
|
+
}
|
|
976
|
+
function normalizeId(id) {
|
|
977
|
+
return id.replace(/\\/g, "/");
|
|
978
|
+
}
|
|
979
|
+
function getVitePressSiteConfig(server) {
|
|
980
|
+
return server.config.vitepress ?? null;
|
|
981
|
+
}
|
|
982
|
+
function isLocalSearchEnabled(server) {
|
|
983
|
+
return server.config.plugins.some(
|
|
984
|
+
(entry) => entry.name === "vitepress:local-search" && typeof entry.load === "function"
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
function normalizeLocalSearchModuleId(id) {
|
|
988
|
+
return id.split("?")[0];
|
|
989
|
+
}
|
|
990
|
+
function getLocaleFromIndexModuleId(id) {
|
|
991
|
+
const normalized = normalizeLocalSearchModuleId(id);
|
|
992
|
+
if (normalized === LOCAL_SEARCH_INDEX_REQUEST_PATH) return null;
|
|
993
|
+
if (!normalized.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) return null;
|
|
994
|
+
return normalized.slice(LOCAL_SEARCH_INDEX_REQUEST_PATH.length) || null;
|
|
995
|
+
}
|
|
996
|
+
function getSearchIndexModuleIds(snapshot) {
|
|
997
|
+
const ids = [LOCAL_SEARCH_INDEX_REQUEST_PATH];
|
|
998
|
+
for (const locale of snapshot.keys()) {
|
|
999
|
+
ids.push(`${LOCAL_SEARCH_INDEX_REQUEST_PATH}${locale}`);
|
|
1000
|
+
}
|
|
1001
|
+
return ids;
|
|
1002
|
+
}
|
|
1003
|
+
function notifySearchIndexUpdated(server, snapshot, revision) {
|
|
1004
|
+
const moduleIds = getSearchIndexModuleIds(snapshot);
|
|
1005
|
+
const updates = [];
|
|
1006
|
+
for (const moduleId of moduleIds) {
|
|
1007
|
+
server.moduleGraph.onFileChange(moduleId);
|
|
1008
|
+
const mod = server.moduleGraph.getModuleById(moduleId);
|
|
1009
|
+
if (mod) {
|
|
1010
|
+
server.moduleGraph.invalidateModule(mod);
|
|
1011
|
+
updates.push({
|
|
1012
|
+
acceptedPath: mod.url,
|
|
1013
|
+
path: mod.url,
|
|
1014
|
+
timestamp: Date.now(),
|
|
1015
|
+
type: "js-update"
|
|
1016
|
+
});
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
debugLog(`HMR module not loaded yet: ${moduleId}`);
|
|
1020
|
+
}
|
|
1021
|
+
if (updates.length > 0) {
|
|
1022
|
+
server.ws.send({ type: "update", updates });
|
|
1023
|
+
debugLog("HMR pushed", updates.map((item) => item.path));
|
|
1024
|
+
} else {
|
|
1025
|
+
debugLog("HMR skipped: no loaded search index modules");
|
|
1026
|
+
}
|
|
1027
|
+
server.ws.send({
|
|
1028
|
+
type: "custom",
|
|
1029
|
+
event: SEARCH_INDEX_HMR_EVENT,
|
|
1030
|
+
data: { revision }
|
|
1031
|
+
});
|
|
1032
|
+
debugLog("custom HMR event sent", { revision });
|
|
1033
|
+
}
|
|
1034
|
+
function renderSnapshotLoad(id, snapshot) {
|
|
1035
|
+
const normalizedId = normalizeLocalSearchModuleId(id);
|
|
1036
|
+
if (normalizedId === LOCAL_SEARCH_INDEX_REQUEST_PATH) {
|
|
1037
|
+
const records = [];
|
|
1038
|
+
for (const locale2 of snapshot.keys()) {
|
|
1039
|
+
records.push(
|
|
1040
|
+
`${JSON.stringify(locale2)}: () => import('${LOCAL_SEARCH_INDEX_ID}${locale2}')`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
return `export default {${records.join(",")}}`;
|
|
1044
|
+
}
|
|
1045
|
+
const locale = getLocaleFromIndexModuleId(id);
|
|
1046
|
+
if (locale !== null) {
|
|
1047
|
+
const payload = snapshot.get(locale) ?? JSON.stringify({});
|
|
1048
|
+
return `export default ${payload}`;
|
|
1049
|
+
}
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
function createDebouncer(delayMs) {
|
|
1053
|
+
let timer = null;
|
|
1054
|
+
let pendingReason = "";
|
|
1055
|
+
return (reason, task) => {
|
|
1056
|
+
pendingReason = reason;
|
|
1057
|
+
if (timer) clearTimeout(timer);
|
|
1058
|
+
timer = setTimeout(async () => {
|
|
1059
|
+
timer = null;
|
|
1060
|
+
const reasonToRun = pendingReason;
|
|
1061
|
+
pendingReason = "";
|
|
1062
|
+
try {
|
|
1063
|
+
await task();
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
console.error(
|
|
1066
|
+
`[tnotes:search-reindex] rebuild failed (${reasonToRun})`,
|
|
1067
|
+
error
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}, delayMs);
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function matchesSearchReindexPath(url) {
|
|
1074
|
+
return url === SEARCH_REINDEX_HTTP_PATH || url.endsWith(SEARCH_REINDEX_HTTP_PATH);
|
|
1075
|
+
}
|
|
1076
|
+
function scheduleNoteSearchReindex(reason = "external") {
|
|
1077
|
+
if (!reindexController) {
|
|
1078
|
+
logInfo(`schedule ignored (plugin not ready): ${reason}`);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
reindexController.schedule(reason);
|
|
1082
|
+
}
|
|
1083
|
+
function localSearchReindexPlugin() {
|
|
1084
|
+
return {
|
|
1085
|
+
name: "tnotes-local-search-reindex",
|
|
1086
|
+
apply: "serve",
|
|
1087
|
+
enforce: "pre",
|
|
1088
|
+
resolveId(source) {
|
|
1089
|
+
if (!source.startsWith(LOCAL_SEARCH_INDEX_ID)) return null;
|
|
1090
|
+
const clean = source.split("?")[0];
|
|
1091
|
+
if (clean === source) return null;
|
|
1092
|
+
return `/${clean}`;
|
|
1093
|
+
},
|
|
1094
|
+
transform(code, id) {
|
|
1095
|
+
const normalizedId = normalizeId(id);
|
|
1096
|
+
if (VP_NAV_BAR_SEARCH.test(normalizedId)) {
|
|
1097
|
+
return patchVPNavBarSearch(code);
|
|
1098
|
+
}
|
|
1099
|
+
return null;
|
|
1100
|
+
},
|
|
1101
|
+
load(id) {
|
|
1102
|
+
if (!indexSnapshot || !id.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
return renderSnapshotLoad(id, indexSnapshot);
|
|
1106
|
+
},
|
|
1107
|
+
configureServer(server) {
|
|
1108
|
+
server.middlewares.use((req, res, next) => {
|
|
1109
|
+
const url = req.url ?? "";
|
|
1110
|
+
if (!matchesSearchReindexPath(url) || req.method !== "POST") {
|
|
1111
|
+
return next();
|
|
1112
|
+
}
|
|
1113
|
+
let body = "";
|
|
1114
|
+
req.on("data", (chunk) => {
|
|
1115
|
+
body += chunk.toString();
|
|
1116
|
+
});
|
|
1117
|
+
req.on("end", () => {
|
|
1118
|
+
try {
|
|
1119
|
+
const data = JSON.parse(body || "{}");
|
|
1120
|
+
scheduleNoteSearchReindex(data.reason || "bridge:http");
|
|
1121
|
+
res.statusCode = 204;
|
|
1122
|
+
res.end();
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
res.statusCode = 400;
|
|
1125
|
+
res.end(
|
|
1126
|
+
error instanceof Error ? error.message : "Bad search reindex payload"
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
return async () => {
|
|
1132
|
+
indexSnapshot = null;
|
|
1133
|
+
snapshotRevision = 0;
|
|
1134
|
+
if (!isLocalSearchEnabled(server)) {
|
|
1135
|
+
reindexController = null;
|
|
1136
|
+
logInfo("vitepress:local-search not found, reindex disabled");
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const siteConfig = getVitePressSiteConfig(server);
|
|
1140
|
+
if (!siteConfig) {
|
|
1141
|
+
reindexController = null;
|
|
1142
|
+
logInfo("server.config.vitepress missing, reindex disabled");
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
ConfigManager.init({ rootPath: process.cwd() });
|
|
1146
|
+
const ignoreDirNames = ConfigManager.getInstance().getAll().ignore_dirs ?? [];
|
|
1147
|
+
const md = await createLocalSearchMarkdownRenderer(siteConfig);
|
|
1148
|
+
const debouncedRebuild = createDebouncer(REBUILD_DEBOUNCE_MS);
|
|
1149
|
+
async function rebuildFullIndex(reason) {
|
|
1150
|
+
const startedAt = Date.now();
|
|
1151
|
+
snapshotRevision += 1;
|
|
1152
|
+
debugLog(`rebuild start (${reason}) rev=${snapshotRevision}`);
|
|
1153
|
+
const { snapshot, pageCount } = await buildLocalSearchIndexSnapshot(
|
|
1154
|
+
siteConfig,
|
|
1155
|
+
md
|
|
1156
|
+
);
|
|
1157
|
+
indexSnapshot = snapshot;
|
|
1158
|
+
notifySearchIndexUpdated(server, indexSnapshot, snapshotRevision);
|
|
1159
|
+
logInfo(
|
|
1160
|
+
`rebuild done (${reason}) rev=${snapshotRevision} pages=${pageCount} locales=${indexSnapshot.size} elapsed=${Date.now() - startedAt}ms`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
reindexController = {
|
|
1164
|
+
schedule(reason) {
|
|
1165
|
+
debugLog(`scheduled (${reason})`);
|
|
1166
|
+
debouncedRebuild(reason, () => rebuildFullIndex(reason));
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
const onWatcherEvent = (absoluteFile, event) => {
|
|
1170
|
+
if (!isNoteReadmePath(absoluteFile, ignoreDirNames)) return;
|
|
1171
|
+
scheduleNoteSearchReindex(
|
|
1172
|
+
`watcher:${event}:${path4.basename(absoluteFile)}`
|
|
1173
|
+
);
|
|
1174
|
+
};
|
|
1175
|
+
server.watcher.on("add", (file) => onWatcherEvent(file, "add"));
|
|
1176
|
+
server.watcher.on("change", (file) => onWatcherEvent(file, "change"));
|
|
1177
|
+
server.watcher.on("unlink", (file) => onWatcherEvent(file, "unlink"));
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
767
1183
|
// vitepress/plugins/renameNotePlugin.ts
|
|
768
1184
|
function renameNotePlugin() {
|
|
769
1185
|
const renameCommand = new RenameNoteCommand();
|
|
@@ -836,12 +1252,6 @@ function sendJson(res, statusCode, payload) {
|
|
|
836
1252
|
function normalizePathname(url) {
|
|
837
1253
|
return (url || "").split("?")[0];
|
|
838
1254
|
}
|
|
839
|
-
function normalizeGroupPath(value) {
|
|
840
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
841
|
-
throw new Error("Missing groupPath");
|
|
842
|
-
}
|
|
843
|
-
return value.map((item) => String(item));
|
|
844
|
-
}
|
|
845
1255
|
function normalizeCount(value) {
|
|
846
1256
|
const count = Number(value ?? 1);
|
|
847
1257
|
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
@@ -856,6 +1266,15 @@ function getCreatedNotePayload(notes) {
|
|
|
856
1266
|
link: `/notes/${encodeURIComponent(note.dirName)}/README`
|
|
857
1267
|
}));
|
|
858
1268
|
}
|
|
1269
|
+
function buildNoteReadmeLink(note) {
|
|
1270
|
+
return `/notes/${encodeURIComponent(note.dirName)}/README`;
|
|
1271
|
+
}
|
|
1272
|
+
function resolveDeleteRedirectUrl(noteService, previousNoteIndex) {
|
|
1273
|
+
if (!previousNoteIndex) return "/";
|
|
1274
|
+
const previousNote = noteService.getNoteByIndex(previousNoteIndex);
|
|
1275
|
+
if (!previousNote) return "/";
|
|
1276
|
+
return buildNoteReadmeLink(previousNote);
|
|
1277
|
+
}
|
|
859
1278
|
async function withSuspendedWatcher(task) {
|
|
860
1279
|
const watcher = FileWatcherService.getInstance();
|
|
861
1280
|
try {
|
|
@@ -867,7 +1286,7 @@ async function withSuspendedWatcher(task) {
|
|
|
867
1286
|
}
|
|
868
1287
|
function sidebarStructurePlugin() {
|
|
869
1288
|
const noteService = NoteService.getInstance();
|
|
870
|
-
const
|
|
1289
|
+
const tocService = TocService.getInstance();
|
|
871
1290
|
async function createNotes(count) {
|
|
872
1291
|
const notes = [];
|
|
873
1292
|
const usedIndexes = /* @__PURE__ */ new Set();
|
|
@@ -890,17 +1309,21 @@ function sidebarStructurePlugin() {
|
|
|
890
1309
|
}
|
|
891
1310
|
return notes;
|
|
892
1311
|
}
|
|
1312
|
+
async function refreshTocAndSidebar() {
|
|
1313
|
+
await tocService.regenerateSidebar();
|
|
1314
|
+
}
|
|
893
1315
|
return {
|
|
894
1316
|
name: "tnotes-sidebar-structure",
|
|
895
1317
|
configureServer(server) {
|
|
896
1318
|
server.middlewares.use(async (req, res, next) => {
|
|
897
1319
|
const pathname = normalizePathname(req.url);
|
|
898
1320
|
const handledPaths = /* @__PURE__ */ new Set([
|
|
899
|
-
"/__tnotes_sidebar_rename_group",
|
|
900
|
-
"/__tnotes_sidebar_delete_group",
|
|
901
1321
|
"/__tnotes_sidebar_create_note",
|
|
902
1322
|
"/__tnotes_sidebar_create_notes",
|
|
1323
|
+
"/__tnotes_sidebar_create_folder",
|
|
903
1324
|
"/__tnotes_sidebar_delete_note",
|
|
1325
|
+
"/__tnotes_sidebar_delete_entry",
|
|
1326
|
+
"/__tnotes_sidebar_rename_folder",
|
|
904
1327
|
"/__tnotes_sidebar_reorder"
|
|
905
1328
|
]);
|
|
906
1329
|
if (!handledPaths.has(pathname)) {
|
|
@@ -914,92 +1337,176 @@ function sidebarStructurePlugin() {
|
|
|
914
1337
|
try {
|
|
915
1338
|
const data = await readJsonBody(req);
|
|
916
1339
|
const result = await withSuspendedWatcher(async () => {
|
|
917
|
-
if (pathname === "/
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
await
|
|
921
|
-
await
|
|
1340
|
+
if (pathname === "/__tnotes_sidebar_delete_note") {
|
|
1341
|
+
const noteIndex = String(data.noteIndex || "");
|
|
1342
|
+
if (!noteIndex) throw new Error("Missing noteIndex");
|
|
1343
|
+
const previousNoteIndex = await tocService.getPreviousNoteIndexBeforeDelete(noteIndex);
|
|
1344
|
+
await tocService.deleteNoteFromToc(noteIndex);
|
|
1345
|
+
await noteService.deleteNote(noteIndex);
|
|
1346
|
+
await refreshTocAndSidebar();
|
|
1347
|
+
scheduleNoteSearchReindex("api:delete-note");
|
|
922
1348
|
return {
|
|
923
1349
|
success: true,
|
|
924
1350
|
sidebarChanged: true,
|
|
925
|
-
|
|
1351
|
+
redirectUrl: resolveDeleteRedirectUrl(
|
|
1352
|
+
noteService,
|
|
1353
|
+
previousNoteIndex
|
|
1354
|
+
),
|
|
1355
|
+
redirectNoteIndex: previousNoteIndex,
|
|
1356
|
+
deletedNoteIndexes: [noteIndex],
|
|
1357
|
+
message: "\u7B14\u8BB0\u5DF2\u5220\u9664"
|
|
926
1358
|
};
|
|
927
1359
|
}
|
|
928
|
-
if (pathname === "/
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
);
|
|
933
|
-
for (const noteIndex of noteIndexes) {
|
|
934
|
-
await noteService.deleteNote(noteIndex);
|
|
1360
|
+
if (pathname === "/__tnotes_sidebar_delete_entry") {
|
|
1361
|
+
const tocLineIndex = Number(data.tocLineIndex);
|
|
1362
|
+
if (!Number.isInteger(tocLineIndex) || tocLineIndex < 0) {
|
|
1363
|
+
throw new Error("Missing tocLineIndex");
|
|
935
1364
|
}
|
|
936
|
-
|
|
1365
|
+
const currentNoteIndex = String(data.currentNoteIndex || "");
|
|
1366
|
+
const previousNoteIndex = await tocService.getPreviousNoteIndexBeforeEntryDelete(
|
|
1367
|
+
tocLineIndex
|
|
1368
|
+
);
|
|
1369
|
+
const inSubtree = currentNoteIndex && await tocService.isNoteInTocEntrySubtree(
|
|
1370
|
+
tocLineIndex,
|
|
1371
|
+
currentNoteIndex
|
|
1372
|
+
);
|
|
1373
|
+
const deletedNoteIndexes = await tocService.deleteTocEntryCascade(tocLineIndex);
|
|
1374
|
+
await refreshTocAndSidebar();
|
|
1375
|
+
scheduleNoteSearchReindex("api:delete-entry");
|
|
937
1376
|
return {
|
|
938
1377
|
success: true,
|
|
939
1378
|
sidebarChanged: true,
|
|
940
|
-
redirectUrl:
|
|
941
|
-
|
|
942
|
-
|
|
1379
|
+
redirectUrl: inSubtree ? resolveDeleteRedirectUrl(noteService, previousNoteIndex) : void 0,
|
|
1380
|
+
redirectNoteIndex: inSubtree ? previousNoteIndex : null,
|
|
1381
|
+
deletedNoteIndexes,
|
|
1382
|
+
message: "\u5DF2\u5220\u9664"
|
|
943
1383
|
};
|
|
944
1384
|
}
|
|
945
|
-
if (pathname === "/
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1385
|
+
if (pathname === "/__tnotes_sidebar_rename_folder") {
|
|
1386
|
+
const tocLineIndex = Number(data.tocLineIndex);
|
|
1387
|
+
const newTitle = String(data.newTitle || "");
|
|
1388
|
+
if (!Number.isInteger(tocLineIndex) || tocLineIndex < 0) {
|
|
1389
|
+
throw new Error("Missing tocLineIndex");
|
|
1390
|
+
}
|
|
1391
|
+
await tocService.renameFolderInToc(tocLineIndex, newTitle);
|
|
1392
|
+
await refreshTocAndSidebar();
|
|
951
1393
|
return {
|
|
952
1394
|
success: true,
|
|
953
1395
|
sidebarChanged: true,
|
|
954
|
-
|
|
955
|
-
deletedNoteIndexes: [noteIndex],
|
|
956
|
-
message: "\u7B14\u8BB0\u5DF2\u5220\u9664"
|
|
1396
|
+
message: "\u76EE\u5F55\u5DF2\u91CD\u547D\u540D"
|
|
957
1397
|
};
|
|
958
1398
|
}
|
|
959
1399
|
if (pathname === "/__tnotes_sidebar_reorder") {
|
|
960
|
-
if (data.
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1400
|
+
if (data.node_uuid && data.action === "prependChild" && !data.target_uuid) {
|
|
1401
|
+
await tocService.prependToRootByNodeId(String(data.node_uuid));
|
|
1402
|
+
await refreshTocAndSidebar();
|
|
1403
|
+
return {
|
|
1404
|
+
success: true,
|
|
1405
|
+
sidebarChanged: true,
|
|
1406
|
+
message: "\u6392\u5E8F\u5DF2\u66F4\u65B0"
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
if (data.node_uuid && data.target_uuid && (data.action === "moveAfter" || data.action === "prependChild")) {
|
|
1410
|
+
if (data.action === "moveAfter") {
|
|
1411
|
+
await tocService.moveAfterByNodeId(
|
|
1412
|
+
String(data.node_uuid),
|
|
1413
|
+
String(data.target_uuid)
|
|
1414
|
+
);
|
|
967
1415
|
} else {
|
|
968
|
-
await
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1416
|
+
await tocService.prependChildByNodeId(
|
|
1417
|
+
String(data.node_uuid),
|
|
1418
|
+
String(data.target_uuid)
|
|
1419
|
+
);
|
|
972
1420
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1421
|
+
await refreshTocAndSidebar();
|
|
1422
|
+
return {
|
|
1423
|
+
success: true,
|
|
1424
|
+
sidebarChanged: true,
|
|
1425
|
+
message: "\u6392\u5E8F\u5DF2\u66F4\u65B0"
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const dragTocLineIndex = Number(data.dragTocLineIndex);
|
|
1429
|
+
if (!Number.isInteger(dragTocLineIndex) || dragTocLineIndex < 0) {
|
|
1430
|
+
throw new Error("Missing dragTocLineIndex");
|
|
1431
|
+
}
|
|
1432
|
+
const placement = data.targetType === "group" || data.placement === "inside" ? "inside" : data.placement === "after" ? "after" : "before";
|
|
1433
|
+
if (data.targetTocLineIndex !== void 0 && data.targetTocLineIndex !== null) {
|
|
1434
|
+
await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
|
|
1435
|
+
targetTocLineIndex: Number(data.targetTocLineIndex),
|
|
1436
|
+
placement
|
|
1437
|
+
});
|
|
1438
|
+
} else if (Array.isArray(data.targetFolderPath) && data.targetFolderPath.length > 0) {
|
|
1439
|
+
await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
|
|
1440
|
+
targetType: "folder",
|
|
1441
|
+
targetFolderPath: data.targetFolderPath.map(String),
|
|
1442
|
+
placement
|
|
1443
|
+
});
|
|
979
1444
|
} else {
|
|
980
|
-
|
|
1445
|
+
const targetNoteIndex = String(
|
|
1446
|
+
data.targetNoteIndex || data.targetGroupNoteIndex || ""
|
|
1447
|
+
);
|
|
1448
|
+
if (!targetNoteIndex) throw new Error("Missing targetNoteIndex");
|
|
1449
|
+
await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
|
|
1450
|
+
targetType: "note",
|
|
1451
|
+
targetNoteIndex,
|
|
1452
|
+
placement
|
|
1453
|
+
});
|
|
981
1454
|
}
|
|
982
|
-
await
|
|
1455
|
+
await refreshTocAndSidebar();
|
|
983
1456
|
return {
|
|
984
1457
|
success: true,
|
|
985
1458
|
sidebarChanged: true,
|
|
986
1459
|
message: "\u6392\u5E8F\u5DF2\u66F4\u65B0"
|
|
987
1460
|
};
|
|
988
1461
|
}
|
|
1462
|
+
if (pathname === "/__tnotes_sidebar_create_folder") {
|
|
1463
|
+
const parentTocLineIndex = Number(data.parentTocLineIndex);
|
|
1464
|
+
const title = String(data.title || "");
|
|
1465
|
+
if (!Number.isInteger(parentTocLineIndex) || parentTocLineIndex < 0) {
|
|
1466
|
+
throw new Error("Missing parentTocLineIndex");
|
|
1467
|
+
}
|
|
1468
|
+
await tocService.insertFolderUnderParent(
|
|
1469
|
+
parentTocLineIndex,
|
|
1470
|
+
title
|
|
1471
|
+
);
|
|
1472
|
+
await refreshTocAndSidebar();
|
|
1473
|
+
return {
|
|
1474
|
+
success: true,
|
|
1475
|
+
sidebarChanged: true,
|
|
1476
|
+
message: "\u76EE\u5F55\u5DF2\u521B\u5EFA"
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
989
1479
|
const count = normalizeCount(data.count);
|
|
990
1480
|
const notes = await createNotes(count);
|
|
991
1481
|
if (data.targetNoteIndex) {
|
|
992
1482
|
const placement = data.placement === "after" ? "after" : "before";
|
|
993
|
-
await
|
|
1483
|
+
await tocService.insertNotesAroundNote(
|
|
994
1484
|
String(data.targetNoteIndex),
|
|
995
1485
|
notes,
|
|
996
1486
|
placement
|
|
997
1487
|
);
|
|
1488
|
+
} else if (data.parentTocLineIndex !== void 0 && data.parentTocLineIndex !== null) {
|
|
1489
|
+
await tocService.insertNotesUnderTocLine(
|
|
1490
|
+
Number(data.parentTocLineIndex),
|
|
1491
|
+
notes
|
|
1492
|
+
);
|
|
1493
|
+
} else if (Array.isArray(data.parentFolderPath) && data.parentFolderPath.length > 0) {
|
|
1494
|
+
await tocService.insertNotesUnderFolder(
|
|
1495
|
+
data.parentFolderPath.map(String),
|
|
1496
|
+
notes
|
|
1497
|
+
);
|
|
1498
|
+
} else if (data.parentNoteIndex) {
|
|
1499
|
+
await tocService.insertNotesUnderParent(
|
|
1500
|
+
String(data.parentNoteIndex),
|
|
1501
|
+
notes
|
|
1502
|
+
);
|
|
998
1503
|
} else {
|
|
999
|
-
const
|
|
1000
|
-
|
|
1504
|
+
for (const note of notes) {
|
|
1505
|
+
await tocService.appendNoteToToc(note.index);
|
|
1506
|
+
}
|
|
1001
1507
|
}
|
|
1002
|
-
await
|
|
1508
|
+
await refreshTocAndSidebar();
|
|
1509
|
+
scheduleNoteSearchReindex("api:create-notes");
|
|
1003
1510
|
return {
|
|
1004
1511
|
success: true,
|
|
1005
1512
|
sidebarChanged: true,
|
|
@@ -1105,6 +1612,7 @@ function defineNotesConfig(overrides = {}) {
|
|
|
1105
1612
|
sidebarStructurePlugin(),
|
|
1106
1613
|
getNoteByConfigIdPlugin(),
|
|
1107
1614
|
fileWatcherBridgePlugin(),
|
|
1615
|
+
localSearchReindexPlugin(),
|
|
1108
1616
|
...overrideVite?.plugins || []
|
|
1109
1617
|
],
|
|
1110
1618
|
server: {
|
|
@@ -1153,9 +1661,9 @@ function defineNotesConfig(overrides = {}) {
|
|
|
1153
1661
|
},
|
|
1154
1662
|
transformPageData(pageData, ctx) {
|
|
1155
1663
|
if (/^notes\/\d{4}/.test(pageData.relativePath)) {
|
|
1156
|
-
const fullPath =
|
|
1664
|
+
const fullPath = path5.resolve(rootPath, pageData.relativePath);
|
|
1157
1665
|
try {
|
|
1158
|
-
const raw =
|
|
1666
|
+
const raw = fs3.readFileSync(fullPath, "utf-8");
|
|
1159
1667
|
pageData.frontmatter.rawContent = Buffer.from(raw, "utf-8").toString(
|
|
1160
1668
|
"base64"
|
|
1161
1669
|
);
|