@tnotesjs/core 0.0.6 → 0.0.8
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 +87 -84
- package/dist/{chunk-JDVN2QYW.js → chunk-JUMVJKWV.js} +615 -614
- package/dist/cli/index.js +125 -125
- package/dist/vitepress/config/index.js +184 -210
- package/package.json +15 -3
- package/types/config.ts +61 -61
- package/types/index.ts +11 -11
- package/types/note.ts +33 -33
- package/types/shims.d.ts +3 -4
- package/vitepress/assets/icons/icon__check.svg +3 -3
- package/vitepress/assets/icons/icon__clipboard.svg +7 -7
- package/vitepress/assets/icons/icon__close.svg +1 -1
- package/vitepress/assets/icons/icon__collapse.svg +1 -1
- package/vitepress/assets/icons/icon__confirm.svg +1 -1
- package/vitepress/assets/icons/icon__copy.svg +4 -4
- package/vitepress/assets/icons/icon__focus.svg +1 -1
- package/vitepress/assets/icons/icon__fold.svg +3 -3
- package/vitepress/assets/icons/icon__folder.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
- package/vitepress/assets/icons/icon__github.svg +3 -3
- package/vitepress/assets/icons/icon__mindmap.svg +1 -1
- package/vitepress/assets/icons/icon__next.svg +1 -1
- package/vitepress/assets/icons/icon__number_gray.svg +1 -1
- package/vitepress/assets/icons/icon__number_purple.svg +1 -1
- package/vitepress/assets/icons/icon__prev.svg +1 -1
- package/vitepress/assets/icons/icon__restore.svg +1 -1
- package/vitepress/assets/icons/icon__rotate.svg +4 -4
- package/vitepress/assets/icons/icon__search.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
- package/vitepress/assets/icons/icon__totop.svg +5 -5
- package/vitepress/assets/icons/icon__vscode.svg +5 -5
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
- package/vitepress/assets/icons/index.ts +38 -38
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -372
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -114
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -64
- package/vitepress/components/Discussions/Discussions.module.scss +32 -32
- package/vitepress/components/Discussions/Discussions.vue +211 -210
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
- package/vitepress/components/EnWordList/EnWordList.vue +543 -542
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
- package/vitepress/components/Footprints/Footprints.module.scss +93 -93
- package/vitepress/components/Footprints/Footprints.vue +377 -377
- package/vitepress/components/Layout/AboutModal.module.scss +233 -233
- package/vitepress/components/Layout/AboutModal.vue +105 -105
- package/vitepress/components/Layout/AboutPanel.vue +270 -265
- package/vitepress/components/Layout/ContentCollapse.vue +603 -602
- package/vitepress/components/Layout/CustomSidebar.vue +608 -605
- package/vitepress/components/Layout/DocBeforeControls.vue +149 -139
- package/vitepress/components/Layout/DocFooter.vue +233 -232
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
- package/vitepress/components/Layout/ImagePreview.vue +281 -280
- package/vitepress/components/Layout/Layout.module.scss +661 -661
- package/vitepress/components/Layout/Layout.vue +539 -542
- package/vitepress/components/Layout/NoteStatus.vue +140 -139
- package/vitepress/components/Layout/SidebarItems.vue +267 -262
- package/vitepress/components/Layout/SidebarNavBefore.vue +92 -92
- package/vitepress/components/Layout/Swiper.vue +167 -167
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
- package/vitepress/components/Layout/ToggleFullContent.vue +35 -34
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
- package/vitepress/components/Layout/ToggleSidebar.vue +36 -35
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
- package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -121
- package/vitepress/components/Layout/composables/useNoteSave.ts +173 -172
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
- package/vitepress/components/Layout/composables/useRedirect.ts +113 -110
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +86 -83
- package/vitepress/components/Layout/homeReadme.data.ts +124 -124
- package/vitepress/components/LoadingPage/LoadingPage.vue +192 -192
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
- package/vitepress/components/MarkMap/MarkMap.vue +405 -404
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
- package/vitepress/components/Mermaid/Mermaid.vue +365 -363
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
- package/vitepress/components/NotesTable/NotesTable.vue +98 -97
- package/vitepress/components/NotesTable/README.md +67 -67
- package/vitepress/components/Settings/Settings.module.scss +433 -433
- package/vitepress/components/Settings/Settings.vue +306 -305
- package/vitepress/components/SidebarCard/MindMapView.vue +484 -483
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
- package/vitepress/components/SidebarCard/SidebarCard.vue +946 -948
- package/vitepress/components/Tooltip/Tooltip.vue +70 -70
- package/vitepress/components/constants.ts +91 -91
- package/vitepress/components/notesConfig.data.ts +73 -73
- package/vitepress/components/sidebar.data.ts +59 -59
- package/vitepress/components/tnotes-config.data.ts +21 -21
- package/vitepress/components/utils.ts +26 -26
- package/vitepress/config/index.ts +160 -146
- package/vitepress/configs/constants.ts +26 -26
- package/vitepress/configs/head.config.ts +25 -24
- package/vitepress/configs/index.ts +9 -9
- package/vitepress/configs/markdown-it.d.ts +23 -20
- package/vitepress/configs/markdown.config.ts +366 -365
- package/vitepress/configs/theme.config.ts +108 -108
- package/vitepress/plugins/buildProgressPlugin.ts +434 -470
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -106
- package/vitepress/plugins/renameNotePlugin.ts +60 -59
- package/vitepress/plugins/updateConfigPlugin.ts +63 -62
- package/vitepress/theme/index.ts +95 -93
- package/vitepress/theme/styles/base.scss +50 -50
- package/vitepress/theme/styles/components/404.scss +31 -31
- package/vitepress/theme/styles/components/collapse.scss +175 -175
- package/vitepress/theme/styles/components/markmap.scss +101 -101
- package/vitepress/theme/styles/components/swiper.scss +255 -255
- package/vitepress/theme/styles/index.scss +25 -25
- package/vitepress/theme/styles/layout.scss +62 -62
- package/vitepress/theme/styles/utilities.scss +39 -39
- package/vitepress/theme/styles/vitepress-override.scss +25 -25
|
@@ -3,40 +3,6 @@ import {
|
|
|
3
3
|
getConfigManager
|
|
4
4
|
} from "./chunk-NASIL5FY.js";
|
|
5
5
|
|
|
6
|
-
// commands/models.ts
|
|
7
|
-
var COMMAND_NAMES = {
|
|
8
|
-
BUILD: "build",
|
|
9
|
-
CREATE_NOTES: "create-notes",
|
|
10
|
-
DEV: "dev",
|
|
11
|
-
FIX_TIMESTAMPS: "fix-timestamps",
|
|
12
|
-
HELP: "help",
|
|
13
|
-
PREVIEW: "preview",
|
|
14
|
-
PULL: "pull",
|
|
15
|
-
PUSH: "push",
|
|
16
|
-
RENAME_NOTE: "rename-note",
|
|
17
|
-
UPDATE: "update",
|
|
18
|
-
UPDATE_COMPLETED_COUNT: "update-completed-count",
|
|
19
|
-
UPDATE_NOTE_CONFIG: "update-note-config"
|
|
20
|
-
};
|
|
21
|
-
var COMMAND_DESCRIPTIONS = {
|
|
22
|
-
[COMMAND_NAMES.DEV]: "\u542F\u52A8\u77E5\u8BC6\u5E93\u5F00\u53D1\u670D\u52A1",
|
|
23
|
-
[COMMAND_NAMES.BUILD]: "\u6784\u5EFA\u77E5\u8BC6\u5E93",
|
|
24
|
-
[COMMAND_NAMES.PREVIEW]: "\u9884\u89C8\u6784\u5EFA\u540E\u7684\u77E5\u8BC6\u5E93",
|
|
25
|
-
[COMMAND_NAMES.UPDATE]: "\u6839\u636E\u7B14\u8BB0\u5185\u5BB9\u66F4\u65B0\u77E5\u8BC6\u5E93",
|
|
26
|
-
[COMMAND_NAMES.UPDATE_COMPLETED_COUNT]: "\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55\uFF08\u8FD1 1 \u5E74\uFF0C\u6700\u8FD1 12 \u4E2A\u6708\uFF09",
|
|
27
|
-
[COMMAND_NAMES.CREATE_NOTES]: "\u65B0\u5EFA\u7B14\u8BB0\uFF08\u652F\u6301\u6279\u91CF\u521B\u5EFA\uFF09",
|
|
28
|
-
[COMMAND_NAMES.PUSH]: "\u5C06\u77E5\u8BC6\u5E93\u63A8\u9001\u5230 GitHub",
|
|
29
|
-
[COMMAND_NAMES.PULL]: "\u5C06 GitHub \u7684\u77E5\u8BC6\u5E93\u62C9\u4E0B\u6765",
|
|
30
|
-
[COMMAND_NAMES.FIX_TIMESTAMPS]: "\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233\uFF08\u57FA\u4E8E git \u5386\u53F2\uFF09",
|
|
31
|
-
[COMMAND_NAMES.UPDATE_NOTE_CONFIG]: "\u66F4\u65B0\u7B14\u8BB0\u914D\u7F6E\u6587\u4EF6",
|
|
32
|
-
[COMMAND_NAMES.RENAME_NOTE]: "\u91CD\u547D\u540D\u7B14\u8BB0",
|
|
33
|
-
[COMMAND_NAMES.HELP]: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F"
|
|
34
|
-
};
|
|
35
|
-
var COMMAND_OPTIONS = {
|
|
36
|
-
QUIET: "quiet",
|
|
37
|
-
FORCE: "force"
|
|
38
|
-
};
|
|
39
|
-
|
|
40
6
|
// utils/errorHandler.ts
|
|
41
7
|
var TNotesError = class _TNotesError extends Error {
|
|
42
8
|
constructor(message, code = "UNKNOWN" /* UNKNOWN */, context) {
|
|
@@ -399,8 +365,8 @@ var genHierarchicalSidebar = (itemList, titles, titlesNotesCount, sidebarIsColla
|
|
|
399
365
|
};
|
|
400
366
|
|
|
401
367
|
// utils/getChangedIds.ts
|
|
402
|
-
import path from "path";
|
|
403
368
|
import { execSync } from "child_process";
|
|
369
|
+
import path from "path";
|
|
404
370
|
function getChangedIds() {
|
|
405
371
|
const changedFiles = execSync(
|
|
406
372
|
`git diff --name-only HEAD -- "notes/[0-9][0-9][0-9][0-9]*/README.md"`
|
|
@@ -419,7 +385,7 @@ function createAddNumberToTitle() {
|
|
|
419
385
|
const titleNumbers = Array(7).fill(0);
|
|
420
386
|
return function addNumberToTitle(title) {
|
|
421
387
|
const match = title.match(
|
|
422
|
-
/^(
|
|
388
|
+
/^(#+)\s*((\d+(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?)\.\s*)?(.*)/
|
|
423
389
|
);
|
|
424
390
|
const plainTitle = match ? match[9].trim() : title.trim();
|
|
425
391
|
const level = title.indexOf(" ");
|
|
@@ -1006,7 +972,7 @@ function buildNoteLink(note, repoOwner, repoName2) {
|
|
|
1006
972
|
}
|
|
1007
973
|
function updateNoteStatus(note) {
|
|
1008
974
|
let status = " ";
|
|
1009
|
-
|
|
975
|
+
const deprecatedMark = "";
|
|
1010
976
|
if (note.config) {
|
|
1011
977
|
if (note.config.done) {
|
|
1012
978
|
status = "x";
|
|
@@ -1140,6 +1106,40 @@ function validateNoteTitle(title) {
|
|
|
1140
1106
|
return { valid: true };
|
|
1141
1107
|
}
|
|
1142
1108
|
|
|
1109
|
+
// commands/models.ts
|
|
1110
|
+
var COMMAND_NAMES = {
|
|
1111
|
+
BUILD: "build",
|
|
1112
|
+
CREATE_NOTES: "create-notes",
|
|
1113
|
+
DEV: "dev",
|
|
1114
|
+
FIX_TIMESTAMPS: "fix-timestamps",
|
|
1115
|
+
HELP: "help",
|
|
1116
|
+
PREVIEW: "preview",
|
|
1117
|
+
PULL: "pull",
|
|
1118
|
+
PUSH: "push",
|
|
1119
|
+
RENAME_NOTE: "rename-note",
|
|
1120
|
+
UPDATE: "update",
|
|
1121
|
+
UPDATE_COMPLETED_COUNT: "update-completed-count",
|
|
1122
|
+
UPDATE_NOTE_CONFIG: "update-note-config"
|
|
1123
|
+
};
|
|
1124
|
+
var COMMAND_DESCRIPTIONS = {
|
|
1125
|
+
[COMMAND_NAMES.DEV]: "\u542F\u52A8\u77E5\u8BC6\u5E93\u5F00\u53D1\u670D\u52A1",
|
|
1126
|
+
[COMMAND_NAMES.BUILD]: "\u6784\u5EFA\u77E5\u8BC6\u5E93",
|
|
1127
|
+
[COMMAND_NAMES.PREVIEW]: "\u9884\u89C8\u6784\u5EFA\u540E\u7684\u77E5\u8BC6\u5E93",
|
|
1128
|
+
[COMMAND_NAMES.UPDATE]: "\u6839\u636E\u7B14\u8BB0\u5185\u5BB9\u66F4\u65B0\u77E5\u8BC6\u5E93",
|
|
1129
|
+
[COMMAND_NAMES.UPDATE_COMPLETED_COUNT]: "\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55\uFF08\u8FD1 1 \u5E74\uFF0C\u6700\u8FD1 12 \u4E2A\u6708\uFF09",
|
|
1130
|
+
[COMMAND_NAMES.CREATE_NOTES]: "\u65B0\u5EFA\u7B14\u8BB0\uFF08\u652F\u6301\u6279\u91CF\u521B\u5EFA\uFF09",
|
|
1131
|
+
[COMMAND_NAMES.PUSH]: "\u5C06\u77E5\u8BC6\u5E93\u63A8\u9001\u5230 GitHub",
|
|
1132
|
+
[COMMAND_NAMES.PULL]: "\u5C06 GitHub \u7684\u77E5\u8BC6\u5E93\u62C9\u4E0B\u6765",
|
|
1133
|
+
[COMMAND_NAMES.FIX_TIMESTAMPS]: "\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233\uFF08\u57FA\u4E8E git \u5386\u53F2\uFF09",
|
|
1134
|
+
[COMMAND_NAMES.UPDATE_NOTE_CONFIG]: "\u66F4\u65B0\u7B14\u8BB0\u914D\u7F6E\u6587\u4EF6",
|
|
1135
|
+
[COMMAND_NAMES.RENAME_NOTE]: "\u91CD\u547D\u540D\u7B14\u8BB0",
|
|
1136
|
+
[COMMAND_NAMES.HELP]: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F"
|
|
1137
|
+
};
|
|
1138
|
+
var COMMAND_OPTIONS = {
|
|
1139
|
+
QUIET: "quiet",
|
|
1140
|
+
FORCE: "force"
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
1143
|
// core/NoteIndexCache.ts
|
|
1144
1144
|
import { join as join2 } from "path";
|
|
1145
1145
|
var NoteIndexCache = class _NoteIndexCache {
|
|
@@ -1338,7 +1338,7 @@ var TocGenerator = class {
|
|
|
1338
1338
|
titles.push(numberedTitle);
|
|
1339
1339
|
lines[i] = numberedTitle;
|
|
1340
1340
|
} else if (isUnnumberedHeader) {
|
|
1341
|
-
const match = line.match(/^(
|
|
1341
|
+
const match = line.match(/^(#+)\s*(\d+(\.\d+)*\.\s*)?(.*)/);
|
|
1342
1342
|
if (match) {
|
|
1343
1343
|
const headerSymbol = match[1];
|
|
1344
1344
|
const plainTitle = match[4];
|
|
@@ -1373,7 +1373,7 @@ var TocGenerator = class {
|
|
|
1373
1373
|
});
|
|
1374
1374
|
}
|
|
1375
1375
|
if (noteConfig.yuque.length > 0) {
|
|
1376
|
-
noteConfig.yuque.forEach((slug
|
|
1376
|
+
noteConfig.yuque.forEach((slug) => {
|
|
1377
1377
|
yuqueTOCItems.push(
|
|
1378
1378
|
` - [TNotes.yuque.${repoName2.replace(
|
|
1379
1379
|
"TNotes.",
|
|
@@ -1416,9 +1416,9 @@ var TocGenerator = class {
|
|
|
1416
1416
|
* 更新首页目录
|
|
1417
1417
|
* @param lines - 首页内容行数组
|
|
1418
1418
|
* @param titles - 标题数组
|
|
1419
|
-
|
|
1419
|
+
* @param _titlesNotesCount - 每个标题下的笔记数量
|
|
1420
1420
|
*/
|
|
1421
|
-
updateHomeToc(lines, titles,
|
|
1421
|
+
updateHomeToc(lines, titles, _titlesNotesCount) {
|
|
1422
1422
|
let startLineIdx = -1, endLineIdx = -1;
|
|
1423
1423
|
lines.forEach((line, idx) => {
|
|
1424
1424
|
if (line.startsWith(NOTES_TOC_START_TAG)) startLineIdx = idx;
|
|
@@ -1740,7 +1740,6 @@ var GitManager = class {
|
|
|
1740
1740
|
didStash = await this.stash("Auto-stash before pull");
|
|
1741
1741
|
}
|
|
1742
1742
|
try {
|
|
1743
|
-
const status = await this.getStatus();
|
|
1744
1743
|
const beforeCommit = await runCommand("git rev-parse HEAD", this.dir);
|
|
1745
1744
|
this.logger.info("\u6B63\u5728\u62C9\u53D6\u8FDC\u7A0B\u66F4\u65B0...");
|
|
1746
1745
|
const cmd = `git pull ${rebase ? "--rebase" : ""} ${autostash ? "--autostash" : ""}`.trim();
|
|
@@ -2054,41 +2053,165 @@ var ProcessManager = class {
|
|
|
2054
2053
|
}
|
|
2055
2054
|
};
|
|
2056
2055
|
|
|
2057
|
-
//
|
|
2058
|
-
var
|
|
2059
|
-
constructor(
|
|
2060
|
-
this.
|
|
2061
|
-
this.logger = logger.child(name);
|
|
2056
|
+
// services/file-watcher/configChangeHandler.ts
|
|
2057
|
+
var ConfigChangeHandler = class {
|
|
2058
|
+
constructor(config2) {
|
|
2059
|
+
this.config = config2;
|
|
2062
2060
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2061
|
+
async handle(events) {
|
|
2062
|
+
if (events.length === 0) return [];
|
|
2063
|
+
const changedIndexes = [];
|
|
2064
|
+
const { state, noteService, noteIndexCache, logger: logger2 } = this.config;
|
|
2065
|
+
for (const change of events) {
|
|
2066
|
+
if (noteService.shouldIgnoreConfigChange(change.path)) {
|
|
2067
|
+
logger2.debug(`\u5FFD\u7565 API \u5199\u5165\u7684\u914D\u7F6E\u6587\u4EF6: ${change.path}`);
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
const snapshot = state.readConfigSnapshot(change.path);
|
|
2071
|
+
if (!snapshot) continue;
|
|
2072
|
+
const cached = state.getConfigSnapshot(change.path);
|
|
2073
|
+
state.setConfigSnapshot(change.path, snapshot);
|
|
2074
|
+
noteIndexCache.updateConfig(change.noteIndex, snapshot);
|
|
2075
|
+
if (!cached) continue;
|
|
2076
|
+
const statusChanged = cached.done !== snapshot.done;
|
|
2077
|
+
const otherChanged = cached.enableDiscussions !== snapshot.enableDiscussions || cached.description !== snapshot.description;
|
|
2078
|
+
if (statusChanged) {
|
|
2079
|
+
changedIndexes.push(change.noteIndex);
|
|
2080
|
+
logger2.info(`\u68C0\u6D4B\u5230\u914D\u7F6E\u72B6\u6001\u53D8\u5316: done(${cached.done}\u2192${snapshot.done})`);
|
|
2081
|
+
} else if (otherChanged) {
|
|
2082
|
+
logger2.info("\u68C0\u6D4B\u5230\u914D\u7F6E\u975E\u72B6\u6001\u5B57\u6BB5\u53D8\u5316\uFF0C\u5DF2\u5237\u65B0\u7F13\u5B58\uFF08\u65E0\u9700\u5168\u5C40\u66F4\u65B0\uFF09");
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return changedIndexes;
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
// services/file-watcher/eventScheduler.ts
|
|
2090
|
+
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
2091
|
+
var DEFAULT_BATCH_WINDOW_MS = 1e3;
|
|
2092
|
+
var DEFAULT_BATCH_THRESHOLD = 3;
|
|
2093
|
+
var DEFAULT_BATCH_BUFFER_MS = 2e3;
|
|
2094
|
+
var EventScheduler = class {
|
|
2095
|
+
constructor(config2) {
|
|
2096
|
+
this.config = config2;
|
|
2097
|
+
this.debounceMs = config2.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2098
|
+
this.batchWindowMs = config2.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS;
|
|
2099
|
+
this.batchThreshold = config2.batchThreshold ?? DEFAULT_BATCH_THRESHOLD;
|
|
2100
|
+
this.batchBufferMs = config2.batchBufferMs ?? DEFAULT_BATCH_BUFFER_MS;
|
|
2101
|
+
}
|
|
2102
|
+
/** 待处理的文件变更事件队列 */
|
|
2103
|
+
pendingEvents = /* @__PURE__ */ new Map();
|
|
2104
|
+
/** 防抖定时器 */
|
|
2105
|
+
updateTimer = null;
|
|
2106
|
+
/** 批量更新恢复定时器 */
|
|
2107
|
+
batchResumeTimer = null;
|
|
2108
|
+
/** 记录最近的变更时间戳 */
|
|
2109
|
+
recentChanges = [];
|
|
2110
|
+
/** 标记是否正在更新,避免循环触发 - 类似一把更新行为锁 */
|
|
2111
|
+
isUpdating = false;
|
|
2112
|
+
debounceMs;
|
|
2113
|
+
batchWindowMs;
|
|
2114
|
+
batchThreshold;
|
|
2115
|
+
batchBufferMs;
|
|
2116
|
+
/**
|
|
2117
|
+
* 设置更新状态锁,用于防止在执行耗时更新操作时被新的文件变更事件打断
|
|
2118
|
+
*
|
|
2119
|
+
* @param flag - true 表示正在更新(锁定),false 表示更新完成(解锁)
|
|
2120
|
+
*/
|
|
2121
|
+
setUpdating(flag) {
|
|
2122
|
+
this.isUpdating = flag;
|
|
2068
2123
|
}
|
|
2069
2124
|
/**
|
|
2070
|
-
*
|
|
2125
|
+
* 获取当前是否处于更新锁定状态
|
|
2126
|
+
*
|
|
2127
|
+
* @returns true 表示正在执行更新操作(事件处理被暂停),false 表示空闲可处理新事件
|
|
2071
2128
|
*/
|
|
2072
|
-
|
|
2073
|
-
|
|
2129
|
+
getUpdating() {
|
|
2130
|
+
return this.isUpdating;
|
|
2074
2131
|
}
|
|
2075
2132
|
/**
|
|
2076
|
-
*
|
|
2133
|
+
* 将文件变更事件加入待处理队列,并启动防抖定时器
|
|
2134
|
+
*
|
|
2135
|
+
* - 若同一文件路径的事件已存在,则忽略重复事件(去重)
|
|
2136
|
+
* - 每次新事件都会重置防抖计时器,确保在变更停止后才触发处理
|
|
2137
|
+
*
|
|
2138
|
+
* @param event 文件变更事件
|
|
2077
2139
|
*/
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2140
|
+
enqueue(event) {
|
|
2141
|
+
if (this.pendingEvents.has(event.path)) return;
|
|
2142
|
+
this.pendingEvents.set(event.path, event);
|
|
2143
|
+
if (this.updateTimer) clearTimeout(this.updateTimer);
|
|
2144
|
+
this.updateTimer = setTimeout(() => this.flush(), this.debounceMs);
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* 立即触发事件队列的处理(防抖到期或手动调用)
|
|
2148
|
+
*
|
|
2149
|
+
* - 若当前正在更新(isUpdating 为 true),则跳过以避免重复处理
|
|
2150
|
+
* - 清空待处理事件队列,并通过 onFlush 回调交由上层服务处理
|
|
2151
|
+
* - 处理开始后会锁定更新状态,防止处理过程中被新事件打断
|
|
2152
|
+
*/
|
|
2153
|
+
flush() {
|
|
2154
|
+
if (this.isUpdating) return;
|
|
2155
|
+
if (this.pendingEvents.size === 0) return;
|
|
2156
|
+
const events = Array.from(this.pendingEvents.values());
|
|
2157
|
+
this.pendingEvents.clear();
|
|
2158
|
+
this.isUpdating = true;
|
|
2159
|
+
this.config.onFlush(events);
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* 记录当前变更时间并检测是否触发批量更新模式
|
|
2163
|
+
*
|
|
2164
|
+
* - 维护一个滑动时间窗口(BATCH_UPDATE_WINDOW_MS)内的变更记录
|
|
2165
|
+
* - 若短时间内(1秒内)变更次数达到阈值(BATCH_UPDATE_THRESHOLD = 3),则判定为批量操作
|
|
2166
|
+
* - 触发批量模式后:
|
|
2167
|
+
* 1. 清空当前待处理事件队列,避免重复处理
|
|
2168
|
+
* 2. 锁定更新状态(isUpdating = true)
|
|
2169
|
+
* 3. 暂停监听服务,并在延迟(窗口 + 缓冲时间)后自动恢复
|
|
2170
|
+
*
|
|
2171
|
+
* @param now 当前时间戳(默认使用 Date.now())
|
|
2172
|
+
* @returns true 表示已触发批量更新模式,false 表示仍处于普通监听模式
|
|
2173
|
+
*/
|
|
2174
|
+
recordChangeAndDetectBatch(now = Date.now()) {
|
|
2175
|
+
this.recentChanges.push(now);
|
|
2176
|
+
this.recentChanges = this.recentChanges.filter(
|
|
2177
|
+
(t) => now - t < this.batchWindowMs
|
|
2178
|
+
);
|
|
2179
|
+
if (this.recentChanges.length < this.batchThreshold) return false;
|
|
2180
|
+
this.pendingEvents.clear();
|
|
2181
|
+
this.recentChanges = [];
|
|
2182
|
+
this.isUpdating = true;
|
|
2183
|
+
this.config.onPauseForBatch();
|
|
2184
|
+
this.batchResumeTimer = setTimeout(() => {
|
|
2185
|
+
this.batchResumeTimer = null;
|
|
2186
|
+
this.isUpdating = false;
|
|
2187
|
+
this.config.reinit();
|
|
2188
|
+
this.config.onResumeAfterBatch();
|
|
2189
|
+
}, this.batchWindowMs + this.batchBufferMs);
|
|
2190
|
+
return true;
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* 清理所有定时器,释放资源
|
|
2194
|
+
*
|
|
2195
|
+
* 在服务停止时调用,防止定时器在服务销毁后仍然触发回调
|
|
2196
|
+
*/
|
|
2197
|
+
clearTimers() {
|
|
2198
|
+
if (this.updateTimer) {
|
|
2199
|
+
clearTimeout(this.updateTimer);
|
|
2200
|
+
this.updateTimer = null;
|
|
2201
|
+
}
|
|
2202
|
+
if (this.batchResumeTimer) {
|
|
2203
|
+
clearTimeout(this.batchResumeTimer);
|
|
2204
|
+
this.batchResumeTimer = null;
|
|
2088
2205
|
}
|
|
2206
|
+
this.pendingEvents.clear();
|
|
2207
|
+
this.recentChanges = [];
|
|
2089
2208
|
}
|
|
2090
2209
|
};
|
|
2091
2210
|
|
|
2211
|
+
// services/file-watcher/folderChangeHandler.ts
|
|
2212
|
+
import { existsSync as existsSync3, promises as fsPromises } from "fs";
|
|
2213
|
+
import { join as join3 } from "path";
|
|
2214
|
+
|
|
2092
2215
|
// services/file-watcher/internal.ts
|
|
2093
2216
|
var WATCH_EVENT_TYPES = {
|
|
2094
2217
|
README: "readme",
|
|
@@ -2104,448 +2227,11 @@ async function safeExecute(label, fn, logger2) {
|
|
|
2104
2227
|
}
|
|
2105
2228
|
}
|
|
2106
2229
|
|
|
2107
|
-
// services/file-watcher/
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
var
|
|
2112
|
-
constructor(config2) {
|
|
2113
|
-
this.config = config2;
|
|
2114
|
-
}
|
|
2115
|
-
/** 文件哈希缓存 */
|
|
2116
|
-
fileHashes = /* @__PURE__ */ new Map();
|
|
2117
|
-
/** 笔记目录缓存 */
|
|
2118
|
-
noteDirCache = /* @__PURE__ */ new Set();
|
|
2119
|
-
/** 笔记配置缓存 */
|
|
2120
|
-
configCache = /* @__PURE__ */ new Map();
|
|
2121
|
-
/**
|
|
2122
|
-
* 获取指定文件的 MD5 哈希值,若文件不存在或读取失败返回 null
|
|
2123
|
-
*
|
|
2124
|
-
* @param filePath 文件路径
|
|
2125
|
-
* @returns 文件哈希
|
|
2126
|
-
*/
|
|
2127
|
-
getFileHash(filePath) {
|
|
2128
|
-
try {
|
|
2129
|
-
if (!existsSync3(filePath)) return null;
|
|
2130
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
2131
|
-
if (content.length === 0) return null;
|
|
2132
|
-
return createHash("md5").update(content).digest("hex");
|
|
2133
|
-
} catch {
|
|
2134
|
-
return null;
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* 更新文件哈希缓存,只有当文件内容发生变化时才更新并返回 true
|
|
2139
|
-
*
|
|
2140
|
-
* @param filePath 文件路径
|
|
2141
|
-
* @returns 是否发生变化
|
|
2142
|
-
*/
|
|
2143
|
-
updateFileHash(filePath) {
|
|
2144
|
-
const current = this.getFileHash(filePath);
|
|
2145
|
-
if (!current) return false;
|
|
2146
|
-
const prev = this.fileHashes.get(filePath);
|
|
2147
|
-
if (prev === current) return false;
|
|
2148
|
-
this.fileHashes.set(filePath, current);
|
|
2149
|
-
return true;
|
|
2150
|
-
}
|
|
2151
|
-
/**
|
|
2152
|
-
* 检查指定名称的笔记目录是否已存在于缓存中
|
|
2153
|
-
*
|
|
2154
|
-
* @param name 笔记目录名称
|
|
2155
|
-
* @returns 若存在则返回 true,否则返回 false
|
|
2156
|
-
*/
|
|
2157
|
-
hasNoteDir(name) {
|
|
2158
|
-
return this.noteDirCache.has(name);
|
|
2159
|
-
}
|
|
2160
|
-
/**
|
|
2161
|
-
* 将指定名称的笔记目录添加到缓存中
|
|
2162
|
-
*
|
|
2163
|
-
* @param name 笔记目录名称
|
|
2164
|
-
*/
|
|
2165
|
-
addNoteDir(name) {
|
|
2166
|
-
this.noteDirCache.add(name);
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* 从缓存中移除指定名称的笔记目录
|
|
2170
|
-
*
|
|
2171
|
-
* @param name 笔记目录名称
|
|
2172
|
-
*/
|
|
2173
|
-
deleteNoteDir(name) {
|
|
2174
|
-
this.noteDirCache.delete(name);
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
|
|
2178
|
-
*/
|
|
2179
|
-
clearAll() {
|
|
2180
|
-
this.fileHashes.clear();
|
|
2181
|
-
this.noteDirCache.clear();
|
|
2182
|
-
this.configCache.clear();
|
|
2183
|
-
}
|
|
2184
|
-
/**
|
|
2185
|
-
* 清除指定笔记目录相关的缓存数据,包括 README.md 和 .tnotes.json 的文件哈希及配置快照
|
|
2186
|
-
*
|
|
2187
|
-
* @param noteDirName 笔记目录名称
|
|
2188
|
-
*/
|
|
2189
|
-
clearNoteCaches(noteDirName) {
|
|
2190
|
-
const readmePath = join3(this.config.notesDir, noteDirName, "README.md");
|
|
2191
|
-
const configPath = join3(this.config.notesDir, noteDirName, ".tnotes.json");
|
|
2192
|
-
this.fileHashes.delete(readmePath);
|
|
2193
|
-
this.fileHashes.delete(configPath);
|
|
2194
|
-
this.configCache.delete(configPath);
|
|
2195
|
-
}
|
|
2196
|
-
/**
|
|
2197
|
-
* 获取指定配置文件路径对应的配置快照
|
|
2198
|
-
*
|
|
2199
|
-
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2200
|
-
* @returns 配置快照,若不存在则返回 undefined
|
|
2201
|
-
*/
|
|
2202
|
-
getConfigSnapshot(configPath) {
|
|
2203
|
-
return this.configCache.get(configPath);
|
|
2204
|
-
}
|
|
2205
|
-
/**
|
|
2206
|
-
* 设置指定配置文件路径的配置快照到缓存中
|
|
2207
|
-
*
|
|
2208
|
-
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2209
|
-
* @param snapshot 配置快照对象
|
|
2210
|
-
*/
|
|
2211
|
-
setConfigSnapshot(configPath, snapshot) {
|
|
2212
|
-
this.configCache.set(configPath, snapshot);
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* 读取指定配置文件的快照
|
|
2216
|
-
*
|
|
2217
|
-
* 解析 .tnotes.json 配置文件,提取 done、enableDiscussions、description 字段。
|
|
2218
|
-
*
|
|
2219
|
-
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2220
|
-
* @returns 配置快照,若文件不存在或解析失败则返回 null
|
|
2221
|
-
*/
|
|
2222
|
-
readConfigSnapshot(configPath) {
|
|
2223
|
-
try {
|
|
2224
|
-
if (!existsSync3(configPath)) return null;
|
|
2225
|
-
const content = readFileSync3(configPath, "utf-8");
|
|
2226
|
-
const config2 = JSON.parse(content);
|
|
2227
|
-
return {
|
|
2228
|
-
done: Boolean(config2.done),
|
|
2229
|
-
enableDiscussions: Boolean(config2.enableDiscussions),
|
|
2230
|
-
description: config2.description || ""
|
|
2231
|
-
};
|
|
2232
|
-
} catch (error) {
|
|
2233
|
-
this.config.logger.error(`[\u8BFB\u53D6\u914D\u7F6E\u5FEB\u7167] ${error}`);
|
|
2234
|
-
return null;
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
/**
|
|
2238
|
-
* 从磁盘初始化监听状态缓存:
|
|
2239
|
-
* 遍历笔记根目录下的所有子目录,将每个笔记目录的 README.md 和 .tnotes.json
|
|
2240
|
-
* 的哈希值及配置快照加载到缓存中。
|
|
2241
|
-
*/
|
|
2242
|
-
initializeFromDisk() {
|
|
2243
|
-
try {
|
|
2244
|
-
const noteDirs = readdirSync2(this.config.notesDir);
|
|
2245
|
-
this.clearAll();
|
|
2246
|
-
for (const noteDir of noteDirs) {
|
|
2247
|
-
const noteDirPath = join3(this.config.notesDir, noteDir);
|
|
2248
|
-
if (!statSync(noteDirPath).isDirectory()) continue;
|
|
2249
|
-
this.noteDirCache.add(noteDir);
|
|
2250
|
-
const readmePath = join3(noteDirPath, "README.md");
|
|
2251
|
-
const readmeHash = this.getFileHash(readmePath);
|
|
2252
|
-
if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
|
|
2253
|
-
const configPath = join3(noteDirPath, ".tnotes.json");
|
|
2254
|
-
const configHash = this.getFileHash(configPath);
|
|
2255
|
-
if (configHash) {
|
|
2256
|
-
this.fileHashes.set(configPath, configHash);
|
|
2257
|
-
const snapshot = this.readConfigSnapshot(configPath);
|
|
2258
|
-
if (snapshot) this.configCache.set(configPath, snapshot);
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
} catch (error) {
|
|
2262
|
-
this.config.logger.warn(
|
|
2263
|
-
`[initializeFromDisk] \u521D\u59CB\u5316\u76D1\u542C\u72B6\u6001\u5931\u8D25: ${error}`
|
|
2264
|
-
);
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
};
|
|
2268
|
-
|
|
2269
|
-
// services/file-watcher/eventScheduler.ts
|
|
2270
|
-
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
2271
|
-
var DEFAULT_BATCH_WINDOW_MS = 1e3;
|
|
2272
|
-
var DEFAULT_BATCH_THRESHOLD = 3;
|
|
2273
|
-
var DEFAULT_BATCH_BUFFER_MS = 2e3;
|
|
2274
|
-
var EventScheduler = class {
|
|
2275
|
-
constructor(config2) {
|
|
2276
|
-
this.config = config2;
|
|
2277
|
-
this.debounceMs = config2.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2278
|
-
this.batchWindowMs = config2.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS;
|
|
2279
|
-
this.batchThreshold = config2.batchThreshold ?? DEFAULT_BATCH_THRESHOLD;
|
|
2280
|
-
this.batchBufferMs = config2.batchBufferMs ?? DEFAULT_BATCH_BUFFER_MS;
|
|
2281
|
-
}
|
|
2282
|
-
/** 待处理的文件变更事件队列 */
|
|
2283
|
-
pendingEvents = /* @__PURE__ */ new Map();
|
|
2284
|
-
/** 防抖定时器 */
|
|
2285
|
-
updateTimer = null;
|
|
2286
|
-
/** 批量更新恢复定时器 */
|
|
2287
|
-
batchResumeTimer = null;
|
|
2288
|
-
/** 记录最近的变更时间戳 */
|
|
2289
|
-
recentChanges = [];
|
|
2290
|
-
/** 标记是否正在更新,避免循环触发 - 类似一把更新行为锁 */
|
|
2291
|
-
isUpdating = false;
|
|
2292
|
-
debounceMs;
|
|
2293
|
-
batchWindowMs;
|
|
2294
|
-
batchThreshold;
|
|
2295
|
-
batchBufferMs;
|
|
2296
|
-
/**
|
|
2297
|
-
* 设置更新状态锁,用于防止在执行耗时更新操作时被新的文件变更事件打断
|
|
2298
|
-
*
|
|
2299
|
-
* @param flag - true 表示正在更新(锁定),false 表示更新完成(解锁)
|
|
2300
|
-
*/
|
|
2301
|
-
setUpdating(flag) {
|
|
2302
|
-
this.isUpdating = flag;
|
|
2303
|
-
}
|
|
2304
|
-
/**
|
|
2305
|
-
* 获取当前是否处于更新锁定状态
|
|
2306
|
-
*
|
|
2307
|
-
* @returns true 表示正在执行更新操作(事件处理被暂停),false 表示空闲可处理新事件
|
|
2308
|
-
*/
|
|
2309
|
-
getUpdating() {
|
|
2310
|
-
return this.isUpdating;
|
|
2311
|
-
}
|
|
2312
|
-
/**
|
|
2313
|
-
* 将文件变更事件加入待处理队列,并启动防抖定时器
|
|
2314
|
-
*
|
|
2315
|
-
* - 若同一文件路径的事件已存在,则忽略重复事件(去重)
|
|
2316
|
-
* - 每次新事件都会重置防抖计时器,确保在变更停止后才触发处理
|
|
2317
|
-
*
|
|
2318
|
-
* @param event 文件变更事件
|
|
2319
|
-
*/
|
|
2320
|
-
enqueue(event) {
|
|
2321
|
-
if (this.pendingEvents.has(event.path)) return;
|
|
2322
|
-
this.pendingEvents.set(event.path, event);
|
|
2323
|
-
if (this.updateTimer) clearTimeout(this.updateTimer);
|
|
2324
|
-
this.updateTimer = setTimeout(() => this.flush(), this.debounceMs);
|
|
2325
|
-
}
|
|
2326
|
-
/**
|
|
2327
|
-
* 立即触发事件队列的处理(防抖到期或手动调用)
|
|
2328
|
-
*
|
|
2329
|
-
* - 若当前正在更新(isUpdating 为 true),则跳过以避免重复处理
|
|
2330
|
-
* - 清空待处理事件队列,并通过 onFlush 回调交由上层服务处理
|
|
2331
|
-
* - 处理开始后会锁定更新状态,防止处理过程中被新事件打断
|
|
2332
|
-
*/
|
|
2333
|
-
flush() {
|
|
2334
|
-
if (this.isUpdating) return;
|
|
2335
|
-
if (this.pendingEvents.size === 0) return;
|
|
2336
|
-
const events = Array.from(this.pendingEvents.values());
|
|
2337
|
-
this.pendingEvents.clear();
|
|
2338
|
-
this.isUpdating = true;
|
|
2339
|
-
this.config.onFlush(events);
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* 记录当前变更时间并检测是否触发批量更新模式
|
|
2343
|
-
*
|
|
2344
|
-
* - 维护一个滑动时间窗口(BATCH_UPDATE_WINDOW_MS)内的变更记录
|
|
2345
|
-
* - 若短时间内(1秒内)变更次数达到阈值(BATCH_UPDATE_THRESHOLD = 3),则判定为批量操作
|
|
2346
|
-
* - 触发批量模式后:
|
|
2347
|
-
* 1. 清空当前待处理事件队列,避免重复处理
|
|
2348
|
-
* 2. 锁定更新状态(isUpdating = true)
|
|
2349
|
-
* 3. 暂停监听服务,并在延迟(窗口 + 缓冲时间)后自动恢复
|
|
2350
|
-
*
|
|
2351
|
-
* @param now 当前时间戳(默认使用 Date.now())
|
|
2352
|
-
* @returns true 表示已触发批量更新模式,false 表示仍处于普通监听模式
|
|
2353
|
-
*/
|
|
2354
|
-
recordChangeAndDetectBatch(now = Date.now()) {
|
|
2355
|
-
this.recentChanges.push(now);
|
|
2356
|
-
this.recentChanges = this.recentChanges.filter(
|
|
2357
|
-
(t) => now - t < this.batchWindowMs
|
|
2358
|
-
);
|
|
2359
|
-
if (this.recentChanges.length < this.batchThreshold) return false;
|
|
2360
|
-
this.pendingEvents.clear();
|
|
2361
|
-
this.recentChanges = [];
|
|
2362
|
-
this.isUpdating = true;
|
|
2363
|
-
this.config.onPauseForBatch();
|
|
2364
|
-
this.batchResumeTimer = setTimeout(() => {
|
|
2365
|
-
this.batchResumeTimer = null;
|
|
2366
|
-
this.isUpdating = false;
|
|
2367
|
-
this.config.reinit();
|
|
2368
|
-
this.config.onResumeAfterBatch();
|
|
2369
|
-
}, this.batchWindowMs + this.batchBufferMs);
|
|
2370
|
-
return true;
|
|
2371
|
-
}
|
|
2372
|
-
/**
|
|
2373
|
-
* 清理所有定时器,释放资源
|
|
2374
|
-
*
|
|
2375
|
-
* 在服务停止时调用,防止定时器在服务销毁后仍然触发回调
|
|
2376
|
-
*/
|
|
2377
|
-
clearTimers() {
|
|
2378
|
-
if (this.updateTimer) {
|
|
2379
|
-
clearTimeout(this.updateTimer);
|
|
2380
|
-
this.updateTimer = null;
|
|
2381
|
-
}
|
|
2382
|
-
if (this.batchResumeTimer) {
|
|
2383
|
-
clearTimeout(this.batchResumeTimer);
|
|
2384
|
-
this.batchResumeTimer = null;
|
|
2385
|
-
}
|
|
2386
|
-
this.pendingEvents.clear();
|
|
2387
|
-
this.recentChanges = [];
|
|
2388
|
-
}
|
|
2389
|
-
};
|
|
2390
|
-
|
|
2391
|
-
// services/file-watcher/renameDetector.ts
|
|
2392
|
-
import { existsSync as existsSync4 } from "fs";
|
|
2393
|
-
import { join as join4 } from "path";
|
|
2394
|
-
var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
|
|
2395
|
-
var RenameDetector = class {
|
|
2396
|
-
constructor(config2) {
|
|
2397
|
-
this.config = config2;
|
|
2398
|
-
}
|
|
2399
|
-
/** 待处理的文件夹重命名 */
|
|
2400
|
-
pendingFolderRename = null;
|
|
2401
|
-
/** 文件夹重命名检测定时器 */
|
|
2402
|
-
folderRenameTimer = null;
|
|
2403
|
-
handleFsRename(folderName) {
|
|
2404
|
-
const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
|
|
2405
|
-
const folderPath = join4(notesDir, folderName);
|
|
2406
|
-
const folderExists = existsSync4(folderPath);
|
|
2407
|
-
const noteIndex = NoteManager.extractNoteIndex(folderName);
|
|
2408
|
-
if (!noteIndex) {
|
|
2409
|
-
logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
|
|
2410
|
-
return;
|
|
2411
|
-
}
|
|
2412
|
-
if (!folderExists) {
|
|
2413
|
-
if (dirCache.has(folderName)) {
|
|
2414
|
-
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u5220\u9664/\u91CD\u547D\u540D: ${folderName}`);
|
|
2415
|
-
this.pendingFolderRename = { oldName: folderName, time: Date.now() };
|
|
2416
|
-
if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer);
|
|
2417
|
-
this.folderRenameTimer = setTimeout(() => {
|
|
2418
|
-
if (this.pendingFolderRename) {
|
|
2419
|
-
logger2.warn(`\u68C0\u6D4B\u5230\u7B14\u8BB0\u5220\u9664: ${this.pendingFolderRename.oldName}`);
|
|
2420
|
-
onDelete(this.pendingFolderRename.oldName);
|
|
2421
|
-
}
|
|
2422
|
-
this.pendingFolderRename = null;
|
|
2423
|
-
this.folderRenameTimer = null;
|
|
2424
|
-
}, FOLDER_RENAME_DETECT_WINDOW_MS);
|
|
2425
|
-
}
|
|
2426
|
-
return;
|
|
2427
|
-
}
|
|
2428
|
-
if (!dirCache.has(folderName)) {
|
|
2429
|
-
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u521B\u5EFA/\u91CD\u547D\u540D: ${folderName}`);
|
|
2430
|
-
if (this.pendingFolderRename && Date.now() - this.pendingFolderRename.time < FOLDER_RENAME_DETECT_WINDOW_MS) {
|
|
2431
|
-
const oldName = this.pendingFolderRename.oldName;
|
|
2432
|
-
const oldNoteIndex = NoteManager.extractNoteIndex(oldName);
|
|
2433
|
-
if (oldNoteIndex && oldNoteIndex === noteIndex) {
|
|
2434
|
-
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u91CD\u547D\u540D: ${oldName} \u2192 ${folderName}`);
|
|
2435
|
-
if (this.folderRenameTimer) {
|
|
2436
|
-
clearTimeout(this.folderRenameTimer);
|
|
2437
|
-
this.folderRenameTimer = null;
|
|
2438
|
-
}
|
|
2439
|
-
onRename(oldName, folderName);
|
|
2440
|
-
this.pendingFolderRename = null;
|
|
2441
|
-
} else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
|
|
2442
|
-
logger2.warn(`\u7D22\u5F15\u51B2\u7A81\uFF0C\u56DE\u9000: ${oldName} -> ${folderName}`);
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
dirCache.add(folderName);
|
|
2446
|
-
if (this.pendingFolderRename) {
|
|
2447
|
-
dirCache.delete(this.pendingFolderRename.oldName);
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
clearTimers() {
|
|
2452
|
-
if (this.folderRenameTimer) {
|
|
2453
|
-
clearTimeout(this.folderRenameTimer);
|
|
2454
|
-
this.folderRenameTimer = null;
|
|
2455
|
-
}
|
|
2456
|
-
this.pendingFolderRename = null;
|
|
2457
|
-
}
|
|
2458
|
-
};
|
|
2459
|
-
|
|
2460
|
-
// services/file-watcher/configChangeHandler.ts
|
|
2461
|
-
var ConfigChangeHandler = class {
|
|
2462
|
-
constructor(config2) {
|
|
2463
|
-
this.config = config2;
|
|
2464
|
-
}
|
|
2465
|
-
async handle(events) {
|
|
2466
|
-
if (events.length === 0) return [];
|
|
2467
|
-
const changedIndexes = [];
|
|
2468
|
-
const { state, noteService, noteIndexCache, logger: logger2 } = this.config;
|
|
2469
|
-
for (const change of events) {
|
|
2470
|
-
if (noteService.shouldIgnoreConfigChange(change.path)) {
|
|
2471
|
-
logger2.debug(`\u5FFD\u7565 API \u5199\u5165\u7684\u914D\u7F6E\u6587\u4EF6: ${change.path}`);
|
|
2472
|
-
continue;
|
|
2473
|
-
}
|
|
2474
|
-
const snapshot = state.readConfigSnapshot(change.path);
|
|
2475
|
-
if (!snapshot) continue;
|
|
2476
|
-
const cached = state.getConfigSnapshot(change.path);
|
|
2477
|
-
state.setConfigSnapshot(change.path, snapshot);
|
|
2478
|
-
noteIndexCache.updateConfig(change.noteIndex, snapshot);
|
|
2479
|
-
if (!cached) continue;
|
|
2480
|
-
const statusChanged = cached.done !== snapshot.done;
|
|
2481
|
-
const otherChanged = cached.enableDiscussions !== snapshot.enableDiscussions || cached.description !== snapshot.description;
|
|
2482
|
-
if (statusChanged) {
|
|
2483
|
-
changedIndexes.push(change.noteIndex);
|
|
2484
|
-
logger2.info(`\u68C0\u6D4B\u5230\u914D\u7F6E\u72B6\u6001\u53D8\u5316: done(${cached.done}\u2192${snapshot.done})`);
|
|
2485
|
-
} else if (otherChanged) {
|
|
2486
|
-
logger2.info("\u68C0\u6D4B\u5230\u914D\u7F6E\u975E\u72B6\u6001\u5B57\u6BB5\u53D8\u5316\uFF0C\u5DF2\u5237\u65B0\u7F13\u5B58\uFF08\u65E0\u9700\u5168\u5C40\u66F4\u65B0\uFF09");
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
return changedIndexes;
|
|
2490
|
-
}
|
|
2491
|
-
};
|
|
2492
|
-
|
|
2493
|
-
// services/file-watcher/readmeChangeHandler.ts
|
|
2494
|
-
var ReadmeChangeHandler = class {
|
|
2495
|
-
constructor(config2) {
|
|
2496
|
-
this.config = config2;
|
|
2497
|
-
}
|
|
2498
|
-
async handle(events) {
|
|
2499
|
-
if (events.length === 0) return;
|
|
2500
|
-
const indexes = [...new Set(events.map((c) => c.noteIndex))];
|
|
2501
|
-
for (const noteIndex of indexes) {
|
|
2502
|
-
const noteInfo = this.config.noteService.getNoteByIndex(noteIndex);
|
|
2503
|
-
if (noteInfo) {
|
|
2504
|
-
await this.config.noteService.fixNoteTitle(noteInfo);
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
};
|
|
2509
|
-
|
|
2510
|
-
// services/file-watcher/globalUpdateCoordinator.ts
|
|
2511
|
-
var GlobalUpdateCoordinator = class {
|
|
2512
|
-
constructor(config2) {
|
|
2513
|
-
this.config = config2;
|
|
2514
|
-
}
|
|
2515
|
-
async applyConfigUpdates(changedNoteIndexes) {
|
|
2516
|
-
if (changedNoteIndexes.length === 0) return;
|
|
2517
|
-
const { readmeService, noteIndexCache, logger: logger2 } = this.config;
|
|
2518
|
-
logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...");
|
|
2519
|
-
for (const noteIndex of changedNoteIndexes) {
|
|
2520
|
-
await safeExecute(
|
|
2521
|
-
`\u589E\u91CF\u66F4\u65B0 ${noteIndex}`,
|
|
2522
|
-
async () => {
|
|
2523
|
-
const item = noteIndexCache.getByNoteIndex(noteIndex);
|
|
2524
|
-
await readmeService.updateNoteInReadme(
|
|
2525
|
-
noteIndex,
|
|
2526
|
-
item?.noteConfig || {}
|
|
2527
|
-
);
|
|
2528
|
-
logger2.info(`\u589E\u91CF\u66F4\u65B0 README \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
|
|
2529
|
-
},
|
|
2530
|
-
logger2
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
2533
|
-
await readmeService.regenerateSidebar();
|
|
2534
|
-
}
|
|
2535
|
-
async updateNoteReadmesOnly(events) {
|
|
2536
|
-
const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))];
|
|
2537
|
-
if (noteIndexesToUpdate.length === 0) return;
|
|
2538
|
-
await this.config.readmeService.updateNoteReadmesOnly(noteIndexesToUpdate);
|
|
2539
|
-
}
|
|
2540
|
-
};
|
|
2541
|
-
|
|
2542
|
-
// services/file-watcher/folderChangeHandler.ts
|
|
2543
|
-
import { existsSync as existsSync5, promises as fsPromises } from "fs";
|
|
2544
|
-
import { join as join5 } from "path";
|
|
2545
|
-
var RENAME_REVERT_DELAY_MS = 2e3;
|
|
2546
|
-
var DELETE_REINIT_DELAY_MS = 1e3;
|
|
2547
|
-
var UPDATE_UNLOCK_DELAY_MS = 500;
|
|
2548
|
-
var FolderChangeHandler = class {
|
|
2230
|
+
// services/file-watcher/folderChangeHandler.ts
|
|
2231
|
+
var RENAME_REVERT_DELAY_MS = 2e3;
|
|
2232
|
+
var DELETE_REINIT_DELAY_MS = 1e3;
|
|
2233
|
+
var UPDATE_UNLOCK_DELAY_MS = 500;
|
|
2234
|
+
var FolderChangeHandler = class {
|
|
2549
2235
|
constructor(config2) {
|
|
2550
2236
|
this.config = config2;
|
|
2551
2237
|
}
|
|
@@ -2672,104 +2358,396 @@ var FolderChangeHandler = class {
|
|
|
2672
2358
|
logger2.error(`\u672A\u627E\u5230\u65B0\u7B14\u8BB0: ${newNoteIndex}`);
|
|
2673
2359
|
}
|
|
2674
2360
|
}
|
|
2675
|
-
async revertFolderRename(oldName, newName) {
|
|
2676
|
-
const { notesDir, scheduler, watchState, logger: logger2 } = this.config;
|
|
2677
|
-
try {
|
|
2678
|
-
const oldPath =
|
|
2679
|
-
const newPath =
|
|
2680
|
-
if (
|
|
2681
|
-
scheduler.setUpdating(true);
|
|
2682
|
-
await fsPromises.rename(newPath, oldPath);
|
|
2683
|
-
logger2.warn(`\u6587\u4EF6\u5939\u5DF2\u56DE\u9000: ${newName} \u2192 ${oldName}`);
|
|
2684
|
-
this.scheduleTimer(() => {
|
|
2685
|
-
scheduler.setUpdating(false);
|
|
2686
|
-
watchState.initializeFromDisk();
|
|
2687
|
-
}, RENAME_REVERT_DELAY_MS);
|
|
2688
|
-
}
|
|
2689
|
-
} catch (error) {
|
|
2690
|
-
logger2.error(`\u56DE\u9000\u6587\u4EF6\u5939\u91CD\u547D\u540D\u5931\u8D25: ${error}`);
|
|
2691
|
-
}
|
|
2361
|
+
async revertFolderRename(oldName, newName) {
|
|
2362
|
+
const { notesDir, scheduler, watchState, logger: logger2 } = this.config;
|
|
2363
|
+
try {
|
|
2364
|
+
const oldPath = join3(notesDir, oldName);
|
|
2365
|
+
const newPath = join3(notesDir, newName);
|
|
2366
|
+
if (existsSync3(newPath)) {
|
|
2367
|
+
scheduler.setUpdating(true);
|
|
2368
|
+
await fsPromises.rename(newPath, oldPath);
|
|
2369
|
+
logger2.warn(`\u6587\u4EF6\u5939\u5DF2\u56DE\u9000: ${newName} \u2192 ${oldName}`);
|
|
2370
|
+
this.scheduleTimer(() => {
|
|
2371
|
+
scheduler.setUpdating(false);
|
|
2372
|
+
watchState.initializeFromDisk();
|
|
2373
|
+
}, RENAME_REVERT_DELAY_MS);
|
|
2374
|
+
}
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
logger2.error(`\u56DE\u9000\u6587\u4EF6\u5939\u91CD\u547D\u540D\u5931\u8D25: ${error}`);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
extractNoteIndexOrWarn(name) {
|
|
2380
|
+
const noteIndex = name.match(/^(\d{4})/)?.[1] || null;
|
|
2381
|
+
if (!noteIndex) {
|
|
2382
|
+
this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
|
|
2383
|
+
}
|
|
2384
|
+
return noteIndex;
|
|
2385
|
+
}
|
|
2386
|
+
// #endregion - 私有实现
|
|
2387
|
+
};
|
|
2388
|
+
|
|
2389
|
+
// services/file-watcher/fsWatcherAdapter.ts
|
|
2390
|
+
import { watch } from "fs";
|
|
2391
|
+
import { basename, dirname, join as join4, sep } from "path";
|
|
2392
|
+
var FsWatcherAdapter = class {
|
|
2393
|
+
constructor(config2) {
|
|
2394
|
+
this.config = config2;
|
|
2395
|
+
}
|
|
2396
|
+
/** 文件系统监听器实例 */
|
|
2397
|
+
watcher = null;
|
|
2398
|
+
start() {
|
|
2399
|
+
const { logger: logger2 } = this.config;
|
|
2400
|
+
if (this.watcher) {
|
|
2401
|
+
logger2.warn("\u6587\u4EF6\u76D1\u542C\u670D\u52A1\u5DF2\u542F\u52A8");
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
this.watcher = watch(
|
|
2405
|
+
this.config.notesDir,
|
|
2406
|
+
{ recursive: true },
|
|
2407
|
+
(eventType, filename) => this.handleFsEvent(eventType, filename)
|
|
2408
|
+
);
|
|
2409
|
+
logger2.debug(`\u6587\u4EF6\u76D1\u542C\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u76EE\u5F55\uFF1A${this.config.notesDir}`);
|
|
2410
|
+
}
|
|
2411
|
+
stop() {
|
|
2412
|
+
if (!this.watcher) return;
|
|
2413
|
+
this.watcher.close();
|
|
2414
|
+
this.watcher = null;
|
|
2415
|
+
}
|
|
2416
|
+
isWatching() {
|
|
2417
|
+
return this.watcher !== null;
|
|
2418
|
+
}
|
|
2419
|
+
handleFsEvent(eventType, filename) {
|
|
2420
|
+
const { isUpdating, onRename, onNoteEvent } = this.config;
|
|
2421
|
+
if (!filename || // 忽略无文件变更
|
|
2422
|
+
isUpdating()) {
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
if (eventType === "rename" && // 检测文件夹 rename 事件
|
|
2426
|
+
!filename.includes(sep)) {
|
|
2427
|
+
onRename(filename);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
const baseFilename = basename(filename);
|
|
2431
|
+
if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
const fullPath = join4(this.config.notesDir, filename);
|
|
2435
|
+
const event = this.buildWatchEvent(fullPath, filename);
|
|
2436
|
+
if (!event) {
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
onNoteEvent(event);
|
|
2440
|
+
}
|
|
2441
|
+
buildWatchEvent(fullPath, filename) {
|
|
2442
|
+
const noteDirName = basename(dirname(fullPath));
|
|
2443
|
+
const noteIndex = NoteManager.extractNoteIndex(noteDirName);
|
|
2444
|
+
if (!noteIndex) {
|
|
2445
|
+
NoteManager.warnInvalidNoteIndex(noteDirName);
|
|
2446
|
+
return null;
|
|
2447
|
+
}
|
|
2448
|
+
const fileType = filename.endsWith("README.md") ? WATCH_EVENT_TYPES.README : WATCH_EVENT_TYPES.CONFIG;
|
|
2449
|
+
return {
|
|
2450
|
+
path: fullPath,
|
|
2451
|
+
type: fileType,
|
|
2452
|
+
noteIndex,
|
|
2453
|
+
noteDirName,
|
|
2454
|
+
noteDirPath: dirname(fullPath)
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
// services/file-watcher/globalUpdateCoordinator.ts
|
|
2460
|
+
var GlobalUpdateCoordinator = class {
|
|
2461
|
+
constructor(config2) {
|
|
2462
|
+
this.config = config2;
|
|
2463
|
+
}
|
|
2464
|
+
async applyConfigUpdates(changedNoteIndexes) {
|
|
2465
|
+
if (changedNoteIndexes.length === 0) return;
|
|
2466
|
+
const { readmeService, noteIndexCache, logger: logger2 } = this.config;
|
|
2467
|
+
logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...");
|
|
2468
|
+
for (const noteIndex of changedNoteIndexes) {
|
|
2469
|
+
await safeExecute(
|
|
2470
|
+
`\u589E\u91CF\u66F4\u65B0 ${noteIndex}`,
|
|
2471
|
+
async () => {
|
|
2472
|
+
const item = noteIndexCache.getByNoteIndex(noteIndex);
|
|
2473
|
+
await readmeService.updateNoteInReadme(
|
|
2474
|
+
noteIndex,
|
|
2475
|
+
item?.noteConfig || {}
|
|
2476
|
+
);
|
|
2477
|
+
logger2.info(`\u589E\u91CF\u66F4\u65B0 README \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
|
|
2478
|
+
},
|
|
2479
|
+
logger2
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
await readmeService.regenerateSidebar();
|
|
2483
|
+
}
|
|
2484
|
+
async updateNoteReadmesOnly(events) {
|
|
2485
|
+
const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))];
|
|
2486
|
+
if (noteIndexesToUpdate.length === 0) return;
|
|
2487
|
+
await this.config.readmeService.updateNoteReadmesOnly(noteIndexesToUpdate);
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
|
|
2491
|
+
// services/file-watcher/readmeChangeHandler.ts
|
|
2492
|
+
var ReadmeChangeHandler = class {
|
|
2493
|
+
constructor(config2) {
|
|
2494
|
+
this.config = config2;
|
|
2495
|
+
}
|
|
2496
|
+
async handle(events) {
|
|
2497
|
+
if (events.length === 0) return;
|
|
2498
|
+
const indexes = [...new Set(events.map((c) => c.noteIndex))];
|
|
2499
|
+
for (const noteIndex of indexes) {
|
|
2500
|
+
const noteInfo = this.config.noteService.getNoteByIndex(noteIndex);
|
|
2501
|
+
if (noteInfo) {
|
|
2502
|
+
await this.config.noteService.fixNoteTitle(noteInfo);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
|
|
2508
|
+
// services/file-watcher/renameDetector.ts
|
|
2509
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2510
|
+
import { join as join5 } from "path";
|
|
2511
|
+
var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
|
|
2512
|
+
var RenameDetector = class {
|
|
2513
|
+
constructor(config2) {
|
|
2514
|
+
this.config = config2;
|
|
2515
|
+
}
|
|
2516
|
+
/** 待处理的文件夹重命名 */
|
|
2517
|
+
pendingFolderRename = null;
|
|
2518
|
+
/** 文件夹重命名检测定时器 */
|
|
2519
|
+
folderRenameTimer = null;
|
|
2520
|
+
handleFsRename(folderName) {
|
|
2521
|
+
const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
|
|
2522
|
+
const folderPath = join5(notesDir, folderName);
|
|
2523
|
+
const folderExists = existsSync4(folderPath);
|
|
2524
|
+
const noteIndex = NoteManager.extractNoteIndex(folderName);
|
|
2525
|
+
if (!noteIndex) {
|
|
2526
|
+
logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
if (!folderExists) {
|
|
2530
|
+
if (dirCache.has(folderName)) {
|
|
2531
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u5220\u9664/\u91CD\u547D\u540D: ${folderName}`);
|
|
2532
|
+
this.pendingFolderRename = { oldName: folderName, time: Date.now() };
|
|
2533
|
+
if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer);
|
|
2534
|
+
this.folderRenameTimer = setTimeout(() => {
|
|
2535
|
+
if (this.pendingFolderRename) {
|
|
2536
|
+
logger2.warn(`\u68C0\u6D4B\u5230\u7B14\u8BB0\u5220\u9664: ${this.pendingFolderRename.oldName}`);
|
|
2537
|
+
onDelete(this.pendingFolderRename.oldName);
|
|
2538
|
+
}
|
|
2539
|
+
this.pendingFolderRename = null;
|
|
2540
|
+
this.folderRenameTimer = null;
|
|
2541
|
+
}, FOLDER_RENAME_DETECT_WINDOW_MS);
|
|
2542
|
+
}
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
if (!dirCache.has(folderName)) {
|
|
2546
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u521B\u5EFA/\u91CD\u547D\u540D: ${folderName}`);
|
|
2547
|
+
if (this.pendingFolderRename && Date.now() - this.pendingFolderRename.time < FOLDER_RENAME_DETECT_WINDOW_MS) {
|
|
2548
|
+
const oldName = this.pendingFolderRename.oldName;
|
|
2549
|
+
const oldNoteIndex = NoteManager.extractNoteIndex(oldName);
|
|
2550
|
+
if (oldNoteIndex && oldNoteIndex === noteIndex) {
|
|
2551
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u91CD\u547D\u540D: ${oldName} \u2192 ${folderName}`);
|
|
2552
|
+
if (this.folderRenameTimer) {
|
|
2553
|
+
clearTimeout(this.folderRenameTimer);
|
|
2554
|
+
this.folderRenameTimer = null;
|
|
2555
|
+
}
|
|
2556
|
+
onRename(oldName, folderName);
|
|
2557
|
+
this.pendingFolderRename = null;
|
|
2558
|
+
} else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
|
|
2559
|
+
logger2.warn(`\u7D22\u5F15\u51B2\u7A81\uFF0C\u56DE\u9000: ${oldName} -> ${folderName}`);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
dirCache.add(folderName);
|
|
2563
|
+
if (this.pendingFolderRename) {
|
|
2564
|
+
dirCache.delete(this.pendingFolderRename.oldName);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
clearTimers() {
|
|
2569
|
+
if (this.folderRenameTimer) {
|
|
2570
|
+
clearTimeout(this.folderRenameTimer);
|
|
2571
|
+
this.folderRenameTimer = null;
|
|
2572
|
+
}
|
|
2573
|
+
this.pendingFolderRename = null;
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
// services/file-watcher/watchState.ts
|
|
2578
|
+
import { createHash } from "crypto";
|
|
2579
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
2580
|
+
import { join as join6 } from "path";
|
|
2581
|
+
var WatchState = class {
|
|
2582
|
+
constructor(config2) {
|
|
2583
|
+
this.config = config2;
|
|
2584
|
+
}
|
|
2585
|
+
/** 文件哈希缓存 */
|
|
2586
|
+
fileHashes = /* @__PURE__ */ new Map();
|
|
2587
|
+
/** 笔记目录缓存 */
|
|
2588
|
+
noteDirCache = /* @__PURE__ */ new Set();
|
|
2589
|
+
/** 笔记配置缓存 */
|
|
2590
|
+
configCache = /* @__PURE__ */ new Map();
|
|
2591
|
+
/**
|
|
2592
|
+
* 获取指定文件的 MD5 哈希值,若文件不存在或读取失败返回 null
|
|
2593
|
+
*
|
|
2594
|
+
* @param filePath 文件路径
|
|
2595
|
+
* @returns 文件哈希
|
|
2596
|
+
*/
|
|
2597
|
+
getFileHash(filePath) {
|
|
2598
|
+
try {
|
|
2599
|
+
if (!existsSync5(filePath)) return null;
|
|
2600
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
2601
|
+
if (content.length === 0) return null;
|
|
2602
|
+
return createHash("md5").update(content).digest("hex");
|
|
2603
|
+
} catch {
|
|
2604
|
+
return null;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* 更新文件哈希缓存,只有当文件内容发生变化时才更新并返回 true
|
|
2609
|
+
*
|
|
2610
|
+
* @param filePath 文件路径
|
|
2611
|
+
* @returns 是否发生变化
|
|
2612
|
+
*/
|
|
2613
|
+
updateFileHash(filePath) {
|
|
2614
|
+
const current = this.getFileHash(filePath);
|
|
2615
|
+
if (!current) return false;
|
|
2616
|
+
const prev = this.fileHashes.get(filePath);
|
|
2617
|
+
if (prev === current) return false;
|
|
2618
|
+
this.fileHashes.set(filePath, current);
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* 检查指定名称的笔记目录是否已存在于缓存中
|
|
2623
|
+
*
|
|
2624
|
+
* @param name 笔记目录名称
|
|
2625
|
+
* @returns 若存在则返回 true,否则返回 false
|
|
2626
|
+
*/
|
|
2627
|
+
hasNoteDir(name) {
|
|
2628
|
+
return this.noteDirCache.has(name);
|
|
2692
2629
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2630
|
+
/**
|
|
2631
|
+
* 将指定名称的笔记目录添加到缓存中
|
|
2632
|
+
*
|
|
2633
|
+
* @param name 笔记目录名称
|
|
2634
|
+
*/
|
|
2635
|
+
addNoteDir(name) {
|
|
2636
|
+
this.noteDirCache.add(name);
|
|
2699
2637
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
constructor(config2) {
|
|
2708
|
-
this.config = config2;
|
|
2638
|
+
/**
|
|
2639
|
+
* 从缓存中移除指定名称的笔记目录
|
|
2640
|
+
*
|
|
2641
|
+
* @param name 笔记目录名称
|
|
2642
|
+
*/
|
|
2643
|
+
deleteNoteDir(name) {
|
|
2644
|
+
this.noteDirCache.delete(name);
|
|
2709
2645
|
}
|
|
2710
|
-
/**
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
}
|
|
2718
|
-
this.watcher = watch(
|
|
2719
|
-
this.config.notesDir,
|
|
2720
|
-
{ recursive: true },
|
|
2721
|
-
(eventType, filename) => this.handleFsEvent(eventType, filename)
|
|
2722
|
-
);
|
|
2723
|
-
logger2.debug(`\u6587\u4EF6\u76D1\u542C\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u76EE\u5F55\uFF1A${this.config.notesDir}`);
|
|
2646
|
+
/**
|
|
2647
|
+
* 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
|
|
2648
|
+
*/
|
|
2649
|
+
clearAll() {
|
|
2650
|
+
this.fileHashes.clear();
|
|
2651
|
+
this.noteDirCache.clear();
|
|
2652
|
+
this.configCache.clear();
|
|
2724
2653
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2654
|
+
/**
|
|
2655
|
+
* 清除指定笔记目录相关的缓存数据,包括 README.md 和 .tnotes.json 的文件哈希及配置快照
|
|
2656
|
+
*
|
|
2657
|
+
* @param noteDirName 笔记目录名称
|
|
2658
|
+
*/
|
|
2659
|
+
clearNoteCaches(noteDirName) {
|
|
2660
|
+
const readmePath = join6(this.config.notesDir, noteDirName, "README.md");
|
|
2661
|
+
const configPath = join6(this.config.notesDir, noteDirName, ".tnotes.json");
|
|
2662
|
+
this.fileHashes.delete(readmePath);
|
|
2663
|
+
this.fileHashes.delete(configPath);
|
|
2664
|
+
this.configCache.delete(configPath);
|
|
2729
2665
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2666
|
+
/**
|
|
2667
|
+
* 获取指定配置文件路径对应的配置快照
|
|
2668
|
+
*
|
|
2669
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2670
|
+
* @returns 配置快照,若不存在则返回 undefined
|
|
2671
|
+
*/
|
|
2672
|
+
getConfigSnapshot(configPath) {
|
|
2673
|
+
return this.configCache.get(configPath);
|
|
2732
2674
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
onRename(filename);
|
|
2742
|
-
return;
|
|
2743
|
-
}
|
|
2744
|
-
const baseFilename = basename(filename);
|
|
2745
|
-
if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
const fullPath = join6(this.config.notesDir, filename);
|
|
2749
|
-
const event = this.buildWatchEvent(fullPath, filename);
|
|
2750
|
-
if (!event) {
|
|
2751
|
-
return;
|
|
2752
|
-
}
|
|
2753
|
-
onNoteEvent(event);
|
|
2675
|
+
/**
|
|
2676
|
+
* 设置指定配置文件路径的配置快照到缓存中
|
|
2677
|
+
*
|
|
2678
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2679
|
+
* @param snapshot 配置快照对象
|
|
2680
|
+
*/
|
|
2681
|
+
setConfigSnapshot(configPath, snapshot) {
|
|
2682
|
+
this.configCache.set(configPath, snapshot);
|
|
2754
2683
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2684
|
+
/**
|
|
2685
|
+
* 读取指定配置文件的快照
|
|
2686
|
+
*
|
|
2687
|
+
* 解析 .tnotes.json 配置文件,提取 done、enableDiscussions、description 字段。
|
|
2688
|
+
*
|
|
2689
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2690
|
+
* @returns 配置快照,若文件不存在或解析失败则返回 null
|
|
2691
|
+
*/
|
|
2692
|
+
readConfigSnapshot(configPath) {
|
|
2693
|
+
try {
|
|
2694
|
+
if (!existsSync5(configPath)) return null;
|
|
2695
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
2696
|
+
const config2 = JSON.parse(content);
|
|
2697
|
+
return {
|
|
2698
|
+
done: Boolean(config2.done),
|
|
2699
|
+
enableDiscussions: Boolean(config2.enableDiscussions),
|
|
2700
|
+
description: config2.description || ""
|
|
2701
|
+
};
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
this.config.logger.error(`[\u8BFB\u53D6\u914D\u7F6E\u5FEB\u7167] ${error}`);
|
|
2760
2704
|
return null;
|
|
2761
2705
|
}
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* 从磁盘初始化监听状态缓存:
|
|
2709
|
+
* 遍历笔记根目录下的所有子目录,将每个笔记目录的 README.md 和 .tnotes.json
|
|
2710
|
+
* 的哈希值及配置快照加载到缓存中。
|
|
2711
|
+
*/
|
|
2712
|
+
initializeFromDisk() {
|
|
2713
|
+
try {
|
|
2714
|
+
const noteDirs = readdirSync2(this.config.notesDir);
|
|
2715
|
+
this.clearAll();
|
|
2716
|
+
for (const noteDir of noteDirs) {
|
|
2717
|
+
const noteDirPath = join6(this.config.notesDir, noteDir);
|
|
2718
|
+
if (!statSync(noteDirPath).isDirectory()) continue;
|
|
2719
|
+
this.noteDirCache.add(noteDir);
|
|
2720
|
+
const readmePath = join6(noteDirPath, "README.md");
|
|
2721
|
+
const readmeHash = this.getFileHash(readmePath);
|
|
2722
|
+
if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
|
|
2723
|
+
const configPath = join6(noteDirPath, ".tnotes.json");
|
|
2724
|
+
const configHash = this.getFileHash(configPath);
|
|
2725
|
+
if (configHash) {
|
|
2726
|
+
this.fileHashes.set(configPath, configHash);
|
|
2727
|
+
const snapshot = this.readConfigSnapshot(configPath);
|
|
2728
|
+
if (snapshot) this.configCache.set(configPath, snapshot);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
this.config.logger.warn(
|
|
2733
|
+
`[initializeFromDisk] \u521D\u59CB\u5316\u76D1\u542C\u72B6\u6001\u5931\u8D25: ${error}`
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2770
2736
|
}
|
|
2771
2737
|
};
|
|
2772
2738
|
|
|
2739
|
+
// services/note/service.ts
|
|
2740
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "fs";
|
|
2741
|
+
import { join as join7 } from "path";
|
|
2742
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2743
|
+
|
|
2744
|
+
// config/templates.ts
|
|
2745
|
+
function generateNoteTitle(noteIndex, title, repoUrl) {
|
|
2746
|
+
const dirName = `${noteIndex}. ${title}`;
|
|
2747
|
+
const encodedDirName = encodeURIComponent(dirName);
|
|
2748
|
+
return `# [${dirName}](${repoUrl}/${encodedDirName})`;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2773
2751
|
// services/readme/service.ts
|
|
2774
2752
|
import {
|
|
2775
2753
|
existsSync as existsSync6,
|
|
@@ -3091,18 +3069,6 @@ var ReadmeService = class _ReadmeService {
|
|
|
3091
3069
|
}
|
|
3092
3070
|
};
|
|
3093
3071
|
|
|
3094
|
-
// services/note/service.ts
|
|
3095
|
-
import { writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "fs";
|
|
3096
|
-
import { join as join7 } from "path";
|
|
3097
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3098
|
-
|
|
3099
|
-
// config/templates.ts
|
|
3100
|
-
function generateNoteTitle(noteIndex, title, repoUrl) {
|
|
3101
|
-
const dirName = `${noteIndex}. ${title}`;
|
|
3102
|
-
const encodedDirName = encodeURIComponent(dirName);
|
|
3103
|
-
return `# [${dirName}](${repoUrl}/${encodedDirName})`;
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
3072
|
// services/note/service.ts
|
|
3107
3073
|
var NEW_NOTES_README_MD_TEMPLATE = `
|
|
3108
3074
|
<!-- region:toc -->
|
|
@@ -3626,6 +3592,7 @@ var GitService = class {
|
|
|
3626
3592
|
};
|
|
3627
3593
|
|
|
3628
3594
|
// services/timestamp/service.ts
|
|
3595
|
+
import { execSync as execSync3 } from "child_process";
|
|
3629
3596
|
import {
|
|
3630
3597
|
existsSync as existsSync7,
|
|
3631
3598
|
readFileSync as readFileSync6,
|
|
@@ -3634,7 +3601,6 @@ import {
|
|
|
3634
3601
|
statSync as statSync2
|
|
3635
3602
|
} from "fs";
|
|
3636
3603
|
import { join as join8 } from "path";
|
|
3637
|
-
import { execSync as execSync3 } from "child_process";
|
|
3638
3604
|
var BIRTH_DATE = (/* @__PURE__ */ new Date("1999-06-29T00:00:00+08:00")).getTime();
|
|
3639
3605
|
var TimestampService = class {
|
|
3640
3606
|
noteManager;
|
|
@@ -4146,6 +4112,41 @@ var VitepressService = class _VitepressService {
|
|
|
4146
4112
|
}
|
|
4147
4113
|
};
|
|
4148
4114
|
|
|
4115
|
+
// commands/BaseCommand.ts
|
|
4116
|
+
var BaseCommand = class {
|
|
4117
|
+
constructor(name) {
|
|
4118
|
+
this.name = name;
|
|
4119
|
+
this.logger = logger.child(name);
|
|
4120
|
+
}
|
|
4121
|
+
logger;
|
|
4122
|
+
options = {};
|
|
4123
|
+
/** 命令描述(从静态配置读取) */
|
|
4124
|
+
get description() {
|
|
4125
|
+
return COMMAND_DESCRIPTIONS[this.name];
|
|
4126
|
+
}
|
|
4127
|
+
/**
|
|
4128
|
+
* 设置命令选项
|
|
4129
|
+
*/
|
|
4130
|
+
setOptions(options) {
|
|
4131
|
+
this.options = { ...this.options, ...options };
|
|
4132
|
+
}
|
|
4133
|
+
/**
|
|
4134
|
+
* 执行命令(带错误处理)
|
|
4135
|
+
*/
|
|
4136
|
+
async execute() {
|
|
4137
|
+
const startTime = Date.now();
|
|
4138
|
+
try {
|
|
4139
|
+
this.logger.start(this.description);
|
|
4140
|
+
await this.run();
|
|
4141
|
+
const duration = Date.now() - startTime;
|
|
4142
|
+
this.logger.done(`\u547D\u4EE4\u6267\u884C\u8017\u65F6\uFF1A${duration} ms`);
|
|
4143
|
+
} catch (error) {
|
|
4144
|
+
handleError(error);
|
|
4145
|
+
throw error;
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
};
|
|
4149
|
+
|
|
4149
4150
|
// commands/note/UpdateNoteConfigCommand.ts
|
|
4150
4151
|
var UpdateNoteConfigCommand = class extends BaseCommand {
|
|
4151
4152
|
noteService;
|
|
@@ -4295,20 +4296,16 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
4295
4296
|
};
|
|
4296
4297
|
|
|
4297
4298
|
export {
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
COMMAND_OPTIONS,
|
|
4299
|
+
ROOT_DIR_PATH,
|
|
4300
|
+
ROOT_CONFIG_PATH,
|
|
4301
4301
|
handleError,
|
|
4302
4302
|
generateAnchor,
|
|
4303
4303
|
logger,
|
|
4304
4304
|
createLogger,
|
|
4305
4305
|
parseArgs,
|
|
4306
4306
|
parseReadmeCompletedNotes,
|
|
4307
|
-
ROOT_DIR_PATH,
|
|
4308
|
-
ROOT_CONFIG_PATH,
|
|
4309
|
-
NoteManager,
|
|
4310
4307
|
runCommand,
|
|
4311
|
-
|
|
4308
|
+
NoteManager,
|
|
4312
4309
|
NoteIndexCache,
|
|
4313
4310
|
ReadmeService,
|
|
4314
4311
|
NoteService,
|
|
@@ -4316,6 +4313,10 @@ export {
|
|
|
4316
4313
|
GitService,
|
|
4317
4314
|
TimestampService,
|
|
4318
4315
|
VitepressService,
|
|
4316
|
+
COMMAND_NAMES,
|
|
4317
|
+
COMMAND_DESCRIPTIONS,
|
|
4318
|
+
COMMAND_OPTIONS,
|
|
4319
|
+
BaseCommand,
|
|
4319
4320
|
UpdateNoteConfigCommand,
|
|
4320
4321
|
RenameNoteCommand
|
|
4321
4322
|
};
|