@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.
Files changed (71) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-4KG6RXFS.js → chunk-5QN44ES5.js} +2946 -1533
  3. package/dist/{chunk-AGSL3VZE.js → chunk-5Y5IYS6C.js} +7 -0
  4. package/dist/cli/index.js +343 -40
  5. package/dist/index.js +1 -1
  6. package/dist/vitepress/config/index.js +573 -65
  7. package/package.json +4 -1
  8. package/templates/sub-repo/.gitattributes +30 -0
  9. package/templates/sub-repo/.github/workflows/deploy.yml +87 -0
  10. package/templates/sub-repo/.vitepress/config.mts +12 -0
  11. package/templates/sub-repo/.vitepress/env.d.ts +10 -0
  12. package/templates/sub-repo/.vitepress/theme/index.ts +12 -0
  13. package/templates/sub-repo/.vscode/settings.json +330 -0
  14. package/templates/sub-repo/.vscode/tnotes.code-snippets +110 -0
  15. package/templates/sub-repo/public/favicon.ico +0 -0
  16. package/templates/sub-repo/public/logo.png +0 -0
  17. package/templates/sub-repo/tsconfig.json +45 -0
  18. package/vitepress/assets/icons/icon__setting_dark.svg +4 -0
  19. package/vitepress/assets/icons/icon__setting_light.svg +4 -0
  20. package/vitepress/assets/icons/index.ts +2 -2
  21. package/vitepress/client/localSearchIndexBridge.ts +24 -0
  22. package/vitepress/components/Discussions/Discussions.vue +1 -1
  23. package/vitepress/components/Footprints/Footprints.vue +90 -25
  24. package/vitepress/components/Layout/CustomSidebar.vue +489 -117
  25. package/vitepress/components/Layout/Layout.vue +5 -0
  26. package/vitepress/components/Layout/NavBarSettingsTrigger.vue +10 -2
  27. package/vitepress/components/Layout/SidebarItems.vue +517 -342
  28. package/vitepress/components/Layout/SidebarNavBefore.vue +1 -50
  29. package/vitepress/components/Layout/SidebarResizeHandle.vue +6 -23
  30. package/vitepress/components/Layout/composables/docLayoutLogic.ts +59 -0
  31. package/vitepress/components/Layout/composables/useDocLayout.test.ts +69 -0
  32. package/vitepress/components/Layout/composables/useDocLayout.ts +128 -0
  33. package/vitepress/components/Layout/composables/useSidebarDrag.ts +514 -0
  34. package/vitepress/components/Layout/composables/useSidebarLayout.ts +25 -8
  35. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +8 -3
  36. package/vitepress/components/Layout/sidebarCollapsedState.test.ts +183 -0
  37. package/vitepress/components/Layout/sidebarCollapsedState.ts +241 -0
  38. package/vitepress/components/Layout/sidebarDragLogic.test.ts +1146 -0
  39. package/vitepress/components/Layout/sidebarDragLogic.ts +1307 -0
  40. package/vitepress/components/Layout/sidebarHitTest.test.ts +572 -0
  41. package/vitepress/components/Layout/sidebarHitTest.ts +801 -0
  42. package/vitepress/components/Layout/sidebarItemRenderLogic.test.ts +53 -0
  43. package/vitepress/components/Layout/sidebarItemRenderLogic.ts +50 -0
  44. package/vitepress/components/MarkMap/MarkMap.vue +67 -10
  45. package/vitepress/components/Settings/Settings.vue +78 -74
  46. package/vitepress/components/SidebarCard/FolderTreeItems.vue +268 -0
  47. package/vitepress/components/SidebarCard/SidebarCard.vue +125 -274
  48. package/vitepress/components/constants.ts +18 -6
  49. package/vitepress/components/sidebar.data.ts +13 -11
  50. package/vitepress/components/sidebarTreeHelpers.test.ts +142 -0
  51. package/vitepress/components/sidebarTreeHelpers.ts +168 -0
  52. package/vitepress/components/utils/vscodePaths.test.ts +41 -0
  53. package/vitepress/components/utils/vscodePaths.ts +52 -0
  54. package/vitepress/config/index.ts +2 -0
  55. package/vitepress/plugins/localSearchIndexBuilder.ts +209 -0
  56. package/vitepress/plugins/localSearchReindex.test.ts +46 -0
  57. package/vitepress/plugins/localSearchReindexLogic.ts +50 -0
  58. package/vitepress/plugins/localSearchReindexPatch.test.ts +30 -0
  59. package/vitepress/plugins/localSearchReindexPatch.ts +37 -0
  60. package/vitepress/plugins/localSearchReindexPlugin.ts +318 -0
  61. package/vitepress/plugins/sidebarStructurePlugin.ts +209 -63
  62. package/vitepress/theme/index.ts +8 -0
  63. package/vitepress/theme/styles/base.scss +0 -8
  64. package/vitepress/theme/styles/components/markmap.scss +23 -2
  65. package/vitepress/theme/styles/components/swiper.scss +1 -1
  66. package/vitepress/theme/styles/doc-layout.scss +212 -0
  67. package/vitepress/theme/styles/index.scss +3 -0
  68. package/vitepress/theme/styles/layout.scss +60 -10
  69. package/vitepress/assets/icons/icon__mindmap.svg +0 -1
  70. package/vitepress/assets/icons/icon__setting.svg +0 -1
  71. 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-4KG6RXFS.js";
10
+ } from "../../chunk-5QN44ES5.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "../../chunk-AGSL3VZE.js";
13
+ } from "../../chunk-5Y5IYS6C.js";
14
14
 
15
15
  // vitepress/config/index.ts
16
- import fs2 from "fs";
17
- import path2 from "path";
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 readmeService = ReadmeService.getInstance();
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 === "/__tnotes_sidebar_rename_group") {
918
- const groupPath = normalizeGroupPath(data.groupPath);
919
- const newTitle = String(data.newTitle || "");
920
- await readmeService.renameGroupInReadme(groupPath, newTitle);
921
- await readmeService.refreshHomeReadmeAndSidebar();
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
- message: "\u76EE\u5F55\u5DF2\u91CD\u547D\u540D"
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 === "/__tnotes_sidebar_delete_group") {
929
- const groupPath = normalizeGroupPath(data.groupPath);
930
- const noteIndexes = await readmeService.deleteGroupFromReadme(
931
- groupPath
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
- await readmeService.refreshHomeReadmeAndSidebar();
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
- deletedNoteIndexes: noteIndexes,
942
- message: "\u76EE\u5F55\u5DF2\u5220\u9664"
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 === "/__tnotes_sidebar_delete_note") {
946
- const noteIndex = String(data.noteIndex || "");
947
- if (!noteIndex) throw new Error("Missing noteIndex");
948
- await readmeService.deleteNoteFromReadme(noteIndex);
949
- await noteService.deleteNote(noteIndex);
950
- await readmeService.refreshHomeReadmeAndSidebar();
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
- redirectUrl: "/",
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.dragType === "note") {
961
- const noteIndex = String(data.noteIndex || "");
962
- if (!noteIndex) throw new Error("Missing noteIndex");
963
- if (data.targetType === "group") {
964
- await readmeService.moveNoteInReadme(noteIndex, {
965
- targetGroupPath: normalizeGroupPath(data.targetGroupPath)
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 readmeService.moveNoteInReadme(noteIndex, {
969
- targetNoteIndex: String(data.targetNoteIndex || ""),
970
- placement: data.placement === "after" ? "after" : "before"
971
- });
1416
+ await tocService.prependChildByNodeId(
1417
+ String(data.node_uuid),
1418
+ String(data.target_uuid)
1419
+ );
972
1420
  }
973
- } else if (data.dragType === "group") {
974
- await readmeService.moveGroupInReadme(
975
- normalizeGroupPath(data.groupPath),
976
- normalizeGroupPath(data.targetGroupPath),
977
- data.placement === "after" ? "after" : "before"
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
- throw new Error("Missing dragType");
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 readmeService.refreshHomeReadmeAndSidebar();
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 readmeService.insertNotesAroundNote(
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 groupPath = normalizeGroupPath(data.groupPath);
1000
- await readmeService.insertNotesAtGroupStart(groupPath, notes);
1504
+ for (const note of notes) {
1505
+ await tocService.appendNoteToToc(note.index);
1506
+ }
1001
1507
  }
1002
- await readmeService.refreshHomeReadmeAndSidebar();
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 = path2.resolve(rootPath, pageData.relativePath);
1664
+ const fullPath = path5.resolve(rootPath, pageData.relativePath);
1157
1665
  try {
1158
- const raw = fs2.readFileSync(fullPath, "utf-8");
1666
+ const raw = fs3.readFileSync(fullPath, "utf-8");
1159
1667
  pageData.frontmatter.rawContent = Buffer.from(raw, "utf-8").toString(
1160
1668
  "base64"
1161
1669
  );