@tnotesjs/core 0.0.7 → 0.1.14
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 +6 -3
- package/dist/{chunk-JDVN2QYW.js → chunk-FVKQPBPP.js} +643 -546
- package/dist/{chunk-NASIL5FY.js → chunk-SQVHJR2Z.js} +0 -4
- package/dist/cli/index.js +126 -126
- package/dist/index.js +1 -1
- package/dist/vitepress/config/index.js +229 -208
- package/package.json +16 -3
- package/types/shims.d.ts +3 -4
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +2 -1
- package/vitepress/components/CodeBlockFullscreen/index.ts +2 -1
- package/vitepress/components/Discussions/Discussions.vue +3 -2
- package/vitepress/components/EnWordList/EnWordList.vue +2 -1
- package/vitepress/components/Layout/AboutModal.vue +1 -1
- package/vitepress/components/Layout/AboutPanel.vue +8 -3
- package/vitepress/components/Layout/ContentCollapse.vue +2 -1
- package/vitepress/components/Layout/CustomSidebar.vue +7 -4
- package/vitepress/components/Layout/DocBeforeControls.vue +4 -4
- package/vitepress/components/Layout/DocFooter.vue +2 -1
- package/vitepress/components/Layout/ImagePreview.vue +2 -1
- package/vitepress/components/Layout/Layout.vue +38 -20
- package/vitepress/components/Layout/NoteStatus.vue +2 -1
- package/vitepress/components/Layout/SidebarItems.vue +7 -2
- package/vitepress/components/Layout/SidebarNavBefore.vue +22 -0
- package/vitepress/components/Layout/ToggleFullContent.vue +2 -1
- package/vitepress/components/Layout/ToggleSidebar.vue +2 -1
- package/vitepress/components/Layout/composables/useCollapseControl.ts +1 -1
- package/vitepress/components/Layout/composables/useNoteConfig.ts +2 -4
- package/vitepress/components/Layout/composables/useNoteSave.ts +23 -18
- package/vitepress/components/Layout/composables/useRedirect.ts +8 -5
- package/vitepress/components/Layout/composables/useRenameOverlay.ts +33 -0
- package/vitepress/components/Layout/composables/useRenameRedirect.ts +59 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +7 -4
- package/vitepress/components/Layout/homeReadme.data.ts +1 -1
- package/vitepress/components/LoadingPage/LoadingPage.vue +84 -192
- package/vitepress/components/MarkMap/MarkMap.vue +5 -4
- package/vitepress/components/Mermaid/Mermaid.vue +4 -2
- package/vitepress/components/NotesTable/NotesTable.vue +2 -1
- package/vitepress/components/Settings/Settings.vue +2 -1
- package/vitepress/components/Settings/SettingsDialog.vue +243 -0
- package/vitepress/components/Settings/composables/useSettingsDialog.ts +35 -0
- package/vitepress/components/SidebarCard/MindMapView.vue +10 -9
- package/vitepress/components/SidebarCard/SidebarCard.vue +12 -14
- package/vitepress/config/index.ts +11 -4
- package/vitepress/configs/head.config.ts +2 -1
- package/vitepress/configs/markdown-it.d.ts +6 -3
- package/vitepress/configs/markdown.config.ts +6 -5
- package/vitepress/configs/theme.config.ts +1 -1
- package/vitepress/plugins/buildProgressPlugin.ts +2 -38
- package/vitepress/plugins/fileWatcherBridgePlugin.ts +49 -0
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +3 -2
- package/vitepress/plugins/renameNotePlugin.ts +9 -1
- package/vitepress/plugins/updateConfigPlugin.ts +2 -1
- package/vitepress/theme/index.ts +45 -3
|
@@ -1,41 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConfigManager,
|
|
3
3
|
getConfigManager
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
};
|
|
4
|
+
} from "./chunk-SQVHJR2Z.js";
|
|
39
5
|
|
|
40
6
|
// utils/errorHandler.ts
|
|
41
7
|
var TNotesError = class _TNotesError extends Error {
|
|
@@ -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,292 +2053,116 @@ var ProcessManager = class {
|
|
|
2054
2053
|
}
|
|
2055
2054
|
};
|
|
2056
2055
|
|
|
2057
|
-
//
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
options = {};
|
|
2065
|
-
/** 命令描述(从静态配置读取) */
|
|
2066
|
-
get description() {
|
|
2067
|
-
return COMMAND_DESCRIPTIONS[this.name];
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* 设置命令选项
|
|
2071
|
-
*/
|
|
2072
|
-
setOptions(options) {
|
|
2073
|
-
this.options = { ...this.options, ...options };
|
|
2056
|
+
// services/file-watcher/service.ts
|
|
2057
|
+
import { request as httpRequest } from "http";
|
|
2058
|
+
|
|
2059
|
+
// services/file-watcher/configChangeHandler.ts
|
|
2060
|
+
var ConfigChangeHandler = class {
|
|
2061
|
+
constructor(config2) {
|
|
2062
|
+
this.config = config2;
|
|
2074
2063
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2064
|
+
async handle(events) {
|
|
2065
|
+
if (events.length === 0) return [];
|
|
2066
|
+
const changedIndexes = [];
|
|
2067
|
+
const { state, noteService, noteIndexCache, logger: logger2 } = this.config;
|
|
2068
|
+
for (const change of events) {
|
|
2069
|
+
if (noteService.shouldIgnoreConfigChange(change.path)) {
|
|
2070
|
+
logger2.debug(`\u5FFD\u7565 API \u5199\u5165\u7684\u914D\u7F6E\u6587\u4EF6: ${change.path}`);
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
const snapshot = state.readConfigSnapshot(change.path);
|
|
2074
|
+
if (!snapshot) continue;
|
|
2075
|
+
const cached = state.getConfigSnapshot(change.path);
|
|
2076
|
+
state.setConfigSnapshot(change.path, snapshot);
|
|
2077
|
+
noteIndexCache.updateConfig(change.noteIndex, snapshot);
|
|
2078
|
+
if (!cached) continue;
|
|
2079
|
+
const statusChanged = cached.done !== snapshot.done;
|
|
2080
|
+
const otherChanged = cached.enableDiscussions !== snapshot.enableDiscussions || cached.description !== snapshot.description;
|
|
2081
|
+
if (statusChanged) {
|
|
2082
|
+
changedIndexes.push(change.noteIndex);
|
|
2083
|
+
logger2.info(`\u68C0\u6D4B\u5230\u914D\u7F6E\u72B6\u6001\u53D8\u5316: done(${cached.done}\u2192${snapshot.done})`);
|
|
2084
|
+
} else if (otherChanged) {
|
|
2085
|
+
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");
|
|
2086
|
+
}
|
|
2088
2087
|
}
|
|
2088
|
+
return changedIndexes;
|
|
2089
2089
|
}
|
|
2090
2090
|
};
|
|
2091
2091
|
|
|
2092
|
-
// services/file-watcher/
|
|
2093
|
-
var
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
try {
|
|
2099
|
-
await fn();
|
|
2100
|
-
return true;
|
|
2101
|
-
} catch (error) {
|
|
2102
|
-
logger2.error(`[${label}] ${error}`);
|
|
2103
|
-
return false;
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// services/file-watcher/watchState.ts
|
|
2108
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
2109
|
-
import { createHash } from "crypto";
|
|
2110
|
-
import { join as join3 } from "path";
|
|
2111
|
-
var WatchState = class {
|
|
2092
|
+
// services/file-watcher/eventScheduler.ts
|
|
2093
|
+
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
2094
|
+
var DEFAULT_BATCH_WINDOW_MS = 1e3;
|
|
2095
|
+
var DEFAULT_BATCH_THRESHOLD = 3;
|
|
2096
|
+
var DEFAULT_BATCH_BUFFER_MS = 2e3;
|
|
2097
|
+
var EventScheduler = class {
|
|
2112
2098
|
constructor(config2) {
|
|
2113
2099
|
this.config = config2;
|
|
2100
|
+
this.debounceMs = config2.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2101
|
+
this.batchWindowMs = config2.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS;
|
|
2102
|
+
this.batchThreshold = config2.batchThreshold ?? DEFAULT_BATCH_THRESHOLD;
|
|
2103
|
+
this.batchBufferMs = config2.batchBufferMs ?? DEFAULT_BATCH_BUFFER_MS;
|
|
2114
2104
|
}
|
|
2115
|
-
/**
|
|
2116
|
-
|
|
2117
|
-
/**
|
|
2118
|
-
|
|
2119
|
-
/**
|
|
2120
|
-
|
|
2105
|
+
/** 待处理的文件变更事件队列 */
|
|
2106
|
+
pendingEvents = /* @__PURE__ */ new Map();
|
|
2107
|
+
/** 防抖定时器 */
|
|
2108
|
+
updateTimer = null;
|
|
2109
|
+
/** 批量更新恢复定时器 */
|
|
2110
|
+
batchResumeTimer = null;
|
|
2111
|
+
/** 记录最近的变更时间戳 */
|
|
2112
|
+
recentChanges = [];
|
|
2113
|
+
/** 标记是否正在更新,避免循环触发 - 类似一把更新行为锁 */
|
|
2114
|
+
isUpdating = false;
|
|
2115
|
+
debounceMs;
|
|
2116
|
+
batchWindowMs;
|
|
2117
|
+
batchThreshold;
|
|
2118
|
+
batchBufferMs;
|
|
2121
2119
|
/**
|
|
2122
|
-
*
|
|
2120
|
+
* 设置更新状态锁,用于防止在执行耗时更新操作时被新的文件变更事件打断
|
|
2123
2121
|
*
|
|
2124
|
-
* @param
|
|
2125
|
-
* @returns 文件哈希
|
|
2122
|
+
* @param flag - true 表示正在更新(锁定),false 表示更新完成(解锁)
|
|
2126
2123
|
*/
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
}
|
|
2124
|
+
setUpdating(flag) {
|
|
2125
|
+
this.isUpdating = flag;
|
|
2136
2126
|
}
|
|
2137
2127
|
/**
|
|
2138
|
-
*
|
|
2128
|
+
* 获取当前是否处于更新锁定状态
|
|
2139
2129
|
*
|
|
2140
|
-
* @
|
|
2141
|
-
* @returns 是否发生变化
|
|
2130
|
+
* @returns true 表示正在执行更新操作(事件处理被暂停),false 表示空闲可处理新事件
|
|
2142
2131
|
*/
|
|
2143
|
-
|
|
2144
|
-
|
|
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;
|
|
2132
|
+
getUpdating() {
|
|
2133
|
+
return this.isUpdating;
|
|
2150
2134
|
}
|
|
2151
2135
|
/**
|
|
2152
|
-
*
|
|
2136
|
+
* 将文件变更事件加入待处理队列,并启动防抖定时器
|
|
2153
2137
|
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*/
|
|
2157
|
-
hasNoteDir(name) {
|
|
2158
|
-
return this.noteDirCache.has(name);
|
|
2159
|
-
}
|
|
2160
|
-
/**
|
|
2161
|
-
* 将指定名称的笔记目录添加到缓存中
|
|
2138
|
+
* - 若同一文件路径的事件已存在,则忽略重复事件(去重)
|
|
2139
|
+
* - 每次新事件都会重置防抖计时器,确保在变更停止后才触发处理
|
|
2162
2140
|
*
|
|
2163
|
-
* @param
|
|
2141
|
+
* @param event 文件变更事件
|
|
2164
2142
|
*/
|
|
2165
|
-
|
|
2166
|
-
this.
|
|
2143
|
+
enqueue(event) {
|
|
2144
|
+
if (this.pendingEvents.has(event.path)) return;
|
|
2145
|
+
this.pendingEvents.set(event.path, event);
|
|
2146
|
+
if (this.updateTimer) clearTimeout(this.updateTimer);
|
|
2147
|
+
this.updateTimer = setTimeout(() => this.flush(), this.debounceMs);
|
|
2167
2148
|
}
|
|
2168
2149
|
/**
|
|
2169
|
-
*
|
|
2150
|
+
* 立即触发事件队列的处理(防抖到期或手动调用)
|
|
2170
2151
|
*
|
|
2171
|
-
*
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
this.noteDirCache.delete(name);
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
|
|
2152
|
+
* - 若当前正在更新(isUpdating 为 true),则跳过以避免重复处理
|
|
2153
|
+
* - 清空待处理事件队列,并通过 onFlush 回调交由上层服务处理
|
|
2154
|
+
* - 处理开始后会锁定更新状态,防止处理过程中被新事件打断
|
|
2178
2155
|
*/
|
|
2179
|
-
|
|
2180
|
-
this.
|
|
2181
|
-
this.
|
|
2182
|
-
this.
|
|
2156
|
+
flush() {
|
|
2157
|
+
if (this.isUpdating) return;
|
|
2158
|
+
if (this.pendingEvents.size === 0) return;
|
|
2159
|
+
const events = Array.from(this.pendingEvents.values());
|
|
2160
|
+
this.pendingEvents.clear();
|
|
2161
|
+
this.isUpdating = true;
|
|
2162
|
+
this.config.onFlush(events);
|
|
2183
2163
|
}
|
|
2184
2164
|
/**
|
|
2185
|
-
*
|
|
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
|
-
* 记录当前变更时间并检测是否触发批量更新模式
|
|
2165
|
+
* 记录当前变更时间并检测是否触发批量更新模式
|
|
2343
2166
|
*
|
|
2344
2167
|
* - 维护一个滑动时间窗口(BATCH_UPDATE_WINDOW_MS)内的变更记录
|
|
2345
2168
|
* - 若短时间内(1秒内)变更次数达到阈值(BATCH_UPDATE_THRESHOLD = 3),则判定为批量操作
|
|
@@ -2388,160 +2211,33 @@ var EventScheduler = class {
|
|
|
2388
2211
|
}
|
|
2389
2212
|
};
|
|
2390
2213
|
|
|
2391
|
-
// services/file-watcher/
|
|
2392
|
-
import { existsSync as
|
|
2393
|
-
import { join as
|
|
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
|
-
};
|
|
2214
|
+
// services/file-watcher/folderChangeHandler.ts
|
|
2215
|
+
import { existsSync as existsSync3, promises as fsPromises } from "fs";
|
|
2216
|
+
import { join as join3 } from "path";
|
|
2492
2217
|
|
|
2493
|
-
// services/file-watcher/
|
|
2494
|
-
var
|
|
2495
|
-
|
|
2496
|
-
|
|
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
|
-
}
|
|
2218
|
+
// services/file-watcher/internal.ts
|
|
2219
|
+
var WATCH_EVENT_TYPES = {
|
|
2220
|
+
README: "readme",
|
|
2221
|
+
CONFIG: "config"
|
|
2508
2222
|
};
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
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);
|
|
2223
|
+
async function safeExecute(label, fn, logger2) {
|
|
2224
|
+
try {
|
|
2225
|
+
await fn();
|
|
2226
|
+
return true;
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
logger2.error(`[${label}] ${error}`);
|
|
2229
|
+
return false;
|
|
2539
2230
|
}
|
|
2540
|
-
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// config/templates.ts
|
|
2234
|
+
function generateNoteTitle(noteIndex, title, repoUrl) {
|
|
2235
|
+
const dirName = `${noteIndex}. ${title}`;
|
|
2236
|
+
const encodedDirName = encodeURIComponent(dirName);
|
|
2237
|
+
return `# [${dirName}](${repoUrl}/${encodedDirName})`;
|
|
2238
|
+
}
|
|
2541
2239
|
|
|
2542
2240
|
// services/file-watcher/folderChangeHandler.ts
|
|
2543
|
-
import { existsSync as existsSync5, promises as fsPromises } from "fs";
|
|
2544
|
-
import { join as join5 } from "path";
|
|
2545
2241
|
var RENAME_REVERT_DELAY_MS = 2e3;
|
|
2546
2242
|
var DELETE_REINIT_DELAY_MS = 1e3;
|
|
2547
2243
|
var UPDATE_UNLOCK_DELAY_MS = 500;
|
|
@@ -2619,6 +2315,11 @@ var FolderChangeHandler = class {
|
|
|
2619
2315
|
logger2
|
|
2620
2316
|
);
|
|
2621
2317
|
}
|
|
2318
|
+
this.config.onRenameSuccess?.({
|
|
2319
|
+
oldFolder: oldName,
|
|
2320
|
+
newFolder: newName,
|
|
2321
|
+
noteIndex: newNoteIndex
|
|
2322
|
+
});
|
|
2622
2323
|
} finally {
|
|
2623
2324
|
this.scheduleTimer(() => {
|
|
2624
2325
|
scheduler.setUpdating(false);
|
|
@@ -2647,9 +2348,14 @@ var FolderChangeHandler = class {
|
|
|
2647
2348
|
return { oldNoteIndex, newNoteIndex };
|
|
2648
2349
|
}
|
|
2649
2350
|
async handleTitleOnlyRename(noteIndex, newName) {
|
|
2650
|
-
const { noteIndexCache, readmeService, logger: logger2 } = this.config;
|
|
2351
|
+
const { noteIndexCache, readmeService, logger: logger2, notesDir } = this.config;
|
|
2651
2352
|
logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u672A\u53D8 (${noteIndex})\uFF0C\u53EA\u66F4\u65B0\u6807\u9898`);
|
|
2652
2353
|
noteIndexCache.updateFolderName(noteIndex, newName);
|
|
2354
|
+
await safeExecute(
|
|
2355
|
+
`\u66F4\u65B0 README \u4E00\u7EA7\u6807\u9898 ${noteIndex}`,
|
|
2356
|
+
() => this.rewriteNoteReadmeH1(notesDir, newName, noteIndex),
|
|
2357
|
+
logger2
|
|
2358
|
+
);
|
|
2653
2359
|
const item = noteIndexCache.getByNoteIndex(noteIndex);
|
|
2654
2360
|
if (item) {
|
|
2655
2361
|
await readmeService.updateNoteInReadme(noteIndex, item.noteConfig);
|
|
@@ -2657,6 +2363,37 @@ var FolderChangeHandler = class {
|
|
|
2657
2363
|
await readmeService.regenerateSidebar();
|
|
2658
2364
|
logger2.success(`\u6807\u9898\u66F4\u65B0\u5B8C\u6210`);
|
|
2659
2365
|
}
|
|
2366
|
+
/**
|
|
2367
|
+
* 重写指定笔记目录下 README.md 的一级标题。
|
|
2368
|
+
*
|
|
2369
|
+
* 输入的 `folderName` 形如 `0003. 评论功能的技术实现`,
|
|
2370
|
+
* 据此推导出新标题文本(去掉 `${noteIndex}. ` 前缀)。
|
|
2371
|
+
*/
|
|
2372
|
+
async rewriteNoteReadmeH1(notesDir, folderName, noteIndex) {
|
|
2373
|
+
const readmePath = join3(notesDir, folderName, "README.md");
|
|
2374
|
+
if (!existsSync3(readmePath)) return;
|
|
2375
|
+
const newTitle = folderName.replace(/^\d{4}\.\s*/, "").trim();
|
|
2376
|
+
if (!newTitle) return;
|
|
2377
|
+
const content = await fsPromises.readFile(readmePath, "utf-8");
|
|
2378
|
+
const lines = content.split("\n");
|
|
2379
|
+
let h1Index = -1;
|
|
2380
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2381
|
+
if (lines[i].trim().startsWith("# ")) {
|
|
2382
|
+
h1Index = i;
|
|
2383
|
+
break;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
if (h1Index === -1) {
|
|
2387
|
+
this.config.logger.warn(
|
|
2388
|
+
`\u672A\u5728 ${readmePath} \u627E\u5230\u4E00\u7EA7\u6807\u9898\uFF0C\u8DF3\u8FC7 README H1 \u66F4\u65B0`
|
|
2389
|
+
);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
const newH1 = generateNoteTitle(noteIndex, newTitle, REPO_NOTES_URL);
|
|
2393
|
+
if (lines[h1Index] === newH1) return;
|
|
2394
|
+
lines[h1Index] = newH1;
|
|
2395
|
+
await fsPromises.writeFile(readmePath, lines.join("\n"), "utf-8");
|
|
2396
|
+
}
|
|
2660
2397
|
async handleIndexChangedRename(oldNoteIndex, newNoteIndex) {
|
|
2661
2398
|
const { noteService, noteIndexCache, readmeService, logger: logger2 } = this.config;
|
|
2662
2399
|
logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u53D8\u66F4: ${oldNoteIndex} \u2192 ${newNoteIndex}`);
|
|
@@ -2675,9 +2412,9 @@ var FolderChangeHandler = class {
|
|
|
2675
2412
|
async revertFolderRename(oldName, newName) {
|
|
2676
2413
|
const { notesDir, scheduler, watchState, logger: logger2 } = this.config;
|
|
2677
2414
|
try {
|
|
2678
|
-
const oldPath =
|
|
2679
|
-
const newPath =
|
|
2680
|
-
if (
|
|
2415
|
+
const oldPath = join3(notesDir, oldName);
|
|
2416
|
+
const newPath = join3(notesDir, newName);
|
|
2417
|
+
if (existsSync3(newPath)) {
|
|
2681
2418
|
scheduler.setUpdating(true);
|
|
2682
2419
|
await fsPromises.rename(newPath, oldPath);
|
|
2683
2420
|
logger2.warn(`\u6587\u4EF6\u5939\u5DF2\u56DE\u9000: ${newName} \u2192 ${oldName}`);
|
|
@@ -2690,86 +2427,371 @@ var FolderChangeHandler = class {
|
|
|
2690
2427
|
logger2.error(`\u56DE\u9000\u6587\u4EF6\u5939\u91CD\u547D\u540D\u5931\u8D25: ${error}`);
|
|
2691
2428
|
}
|
|
2692
2429
|
}
|
|
2693
|
-
extractNoteIndexOrWarn(name) {
|
|
2694
|
-
const noteIndex = name.match(/^(\d{4})/)?.[1] || null;
|
|
2695
|
-
if (!noteIndex) {
|
|
2696
|
-
this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
|
|
2697
|
-
}
|
|
2698
|
-
return noteIndex;
|
|
2430
|
+
extractNoteIndexOrWarn(name) {
|
|
2431
|
+
const noteIndex = name.match(/^(\d{4})/)?.[1] || null;
|
|
2432
|
+
if (!noteIndex) {
|
|
2433
|
+
this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
|
|
2434
|
+
}
|
|
2435
|
+
return noteIndex;
|
|
2436
|
+
}
|
|
2437
|
+
// #endregion - 私有实现
|
|
2438
|
+
};
|
|
2439
|
+
|
|
2440
|
+
// services/file-watcher/fsWatcherAdapter.ts
|
|
2441
|
+
import { watch } from "fs";
|
|
2442
|
+
import { basename, dirname, join as join4, sep } from "path";
|
|
2443
|
+
var FsWatcherAdapter = class {
|
|
2444
|
+
constructor(config2) {
|
|
2445
|
+
this.config = config2;
|
|
2446
|
+
}
|
|
2447
|
+
/** 文件系统监听器实例 */
|
|
2448
|
+
watcher = null;
|
|
2449
|
+
start() {
|
|
2450
|
+
const { logger: logger2 } = this.config;
|
|
2451
|
+
if (this.watcher) {
|
|
2452
|
+
logger2.warn("\u6587\u4EF6\u76D1\u542C\u670D\u52A1\u5DF2\u542F\u52A8");
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
this.watcher = watch(
|
|
2456
|
+
this.config.notesDir,
|
|
2457
|
+
{ recursive: true },
|
|
2458
|
+
(eventType, filename) => this.handleFsEvent(eventType, filename)
|
|
2459
|
+
);
|
|
2460
|
+
logger2.debug(`\u6587\u4EF6\u76D1\u542C\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u76EE\u5F55\uFF1A${this.config.notesDir}`);
|
|
2461
|
+
}
|
|
2462
|
+
stop() {
|
|
2463
|
+
if (!this.watcher) return;
|
|
2464
|
+
this.watcher.close();
|
|
2465
|
+
this.watcher = null;
|
|
2466
|
+
}
|
|
2467
|
+
isWatching() {
|
|
2468
|
+
return this.watcher !== null;
|
|
2469
|
+
}
|
|
2470
|
+
handleFsEvent(eventType, filename) {
|
|
2471
|
+
const { isUpdating, onRename, onNoteEvent } = this.config;
|
|
2472
|
+
if (!filename || // 忽略无文件变更
|
|
2473
|
+
isUpdating()) {
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (eventType === "rename" && // 检测文件夹 rename 事件
|
|
2477
|
+
!filename.includes(sep)) {
|
|
2478
|
+
onRename(filename);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const baseFilename = basename(filename);
|
|
2482
|
+
if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const fullPath = join4(this.config.notesDir, filename);
|
|
2486
|
+
const event = this.buildWatchEvent(fullPath, filename);
|
|
2487
|
+
if (!event) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
onNoteEvent(event);
|
|
2491
|
+
}
|
|
2492
|
+
buildWatchEvent(fullPath, filename) {
|
|
2493
|
+
const noteDirName = basename(dirname(fullPath));
|
|
2494
|
+
const noteIndex = NoteManager.extractNoteIndex(noteDirName);
|
|
2495
|
+
if (!noteIndex) {
|
|
2496
|
+
NoteManager.warnInvalidNoteIndex(noteDirName);
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
const fileType = filename.endsWith("README.md") ? WATCH_EVENT_TYPES.README : WATCH_EVENT_TYPES.CONFIG;
|
|
2500
|
+
return {
|
|
2501
|
+
path: fullPath,
|
|
2502
|
+
type: fileType,
|
|
2503
|
+
noteIndex,
|
|
2504
|
+
noteDirName,
|
|
2505
|
+
noteDirPath: dirname(fullPath)
|
|
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/readmeChangeHandler.ts
|
|
2543
|
+
var ReadmeChangeHandler = class {
|
|
2544
|
+
constructor(config2) {
|
|
2545
|
+
this.config = config2;
|
|
2546
|
+
}
|
|
2547
|
+
async handle(events) {
|
|
2548
|
+
if (events.length === 0) return;
|
|
2549
|
+
const indexes = [...new Set(events.map((c) => c.noteIndex))];
|
|
2550
|
+
for (const noteIndex of indexes) {
|
|
2551
|
+
const noteInfo = this.config.noteService.getNoteByIndex(noteIndex);
|
|
2552
|
+
if (noteInfo) {
|
|
2553
|
+
await this.config.noteService.fixNoteTitle(noteInfo);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
// services/file-watcher/renameDetector.ts
|
|
2560
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2561
|
+
import { join as join5 } from "path";
|
|
2562
|
+
var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
|
|
2563
|
+
var RenameDetector = class {
|
|
2564
|
+
constructor(config2) {
|
|
2565
|
+
this.config = config2;
|
|
2566
|
+
}
|
|
2567
|
+
/** 待处理的文件夹重命名 */
|
|
2568
|
+
pendingFolderRename = null;
|
|
2569
|
+
/** 文件夹重命名检测定时器 */
|
|
2570
|
+
folderRenameTimer = null;
|
|
2571
|
+
handleFsRename(folderName) {
|
|
2572
|
+
const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
|
|
2573
|
+
const folderPath = join5(notesDir, folderName);
|
|
2574
|
+
const folderExists = existsSync4(folderPath);
|
|
2575
|
+
const noteIndex = NoteManager.extractNoteIndex(folderName);
|
|
2576
|
+
if (!noteIndex) {
|
|
2577
|
+
logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
if (!folderExists) {
|
|
2581
|
+
if (dirCache.has(folderName)) {
|
|
2582
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u5220\u9664/\u91CD\u547D\u540D: ${folderName}`);
|
|
2583
|
+
this.pendingFolderRename = { oldName: folderName, time: Date.now() };
|
|
2584
|
+
if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer);
|
|
2585
|
+
this.folderRenameTimer = setTimeout(() => {
|
|
2586
|
+
if (this.pendingFolderRename) {
|
|
2587
|
+
logger2.warn(`\u68C0\u6D4B\u5230\u7B14\u8BB0\u5220\u9664: ${this.pendingFolderRename.oldName}`);
|
|
2588
|
+
onDelete(this.pendingFolderRename.oldName);
|
|
2589
|
+
}
|
|
2590
|
+
this.pendingFolderRename = null;
|
|
2591
|
+
this.folderRenameTimer = null;
|
|
2592
|
+
}, FOLDER_RENAME_DETECT_WINDOW_MS);
|
|
2593
|
+
}
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (!dirCache.has(folderName)) {
|
|
2597
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u521B\u5EFA/\u91CD\u547D\u540D: ${folderName}`);
|
|
2598
|
+
if (this.pendingFolderRename && Date.now() - this.pendingFolderRename.time < FOLDER_RENAME_DETECT_WINDOW_MS) {
|
|
2599
|
+
const oldName = this.pendingFolderRename.oldName;
|
|
2600
|
+
const oldNoteIndex = NoteManager.extractNoteIndex(oldName);
|
|
2601
|
+
if (oldNoteIndex && oldNoteIndex === noteIndex) {
|
|
2602
|
+
logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u91CD\u547D\u540D: ${oldName} \u2192 ${folderName}`);
|
|
2603
|
+
if (this.folderRenameTimer) {
|
|
2604
|
+
clearTimeout(this.folderRenameTimer);
|
|
2605
|
+
this.folderRenameTimer = null;
|
|
2606
|
+
}
|
|
2607
|
+
onRename(oldName, folderName);
|
|
2608
|
+
this.pendingFolderRename = null;
|
|
2609
|
+
} else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
|
|
2610
|
+
logger2.warn(`\u7D22\u5F15\u51B2\u7A81\uFF0C\u56DE\u9000: ${oldName} -> ${folderName}`);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
dirCache.add(folderName);
|
|
2614
|
+
if (this.pendingFolderRename) {
|
|
2615
|
+
dirCache.delete(this.pendingFolderRename.oldName);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
clearTimers() {
|
|
2620
|
+
if (this.folderRenameTimer) {
|
|
2621
|
+
clearTimeout(this.folderRenameTimer);
|
|
2622
|
+
this.folderRenameTimer = null;
|
|
2623
|
+
}
|
|
2624
|
+
this.pendingFolderRename = null;
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
|
|
2628
|
+
// services/file-watcher/watchState.ts
|
|
2629
|
+
import { createHash } from "crypto";
|
|
2630
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
2631
|
+
import { join as join6 } from "path";
|
|
2632
|
+
var WatchState = class {
|
|
2633
|
+
constructor(config2) {
|
|
2634
|
+
this.config = config2;
|
|
2635
|
+
}
|
|
2636
|
+
/** 文件哈希缓存 */
|
|
2637
|
+
fileHashes = /* @__PURE__ */ new Map();
|
|
2638
|
+
/** 笔记目录缓存 */
|
|
2639
|
+
noteDirCache = /* @__PURE__ */ new Set();
|
|
2640
|
+
/** 笔记配置缓存 */
|
|
2641
|
+
configCache = /* @__PURE__ */ new Map();
|
|
2642
|
+
/**
|
|
2643
|
+
* 获取指定文件的 MD5 哈希值,若文件不存在或读取失败返回 null
|
|
2644
|
+
*
|
|
2645
|
+
* @param filePath 文件路径
|
|
2646
|
+
* @returns 文件哈希
|
|
2647
|
+
*/
|
|
2648
|
+
getFileHash(filePath) {
|
|
2649
|
+
try {
|
|
2650
|
+
if (!existsSync5(filePath)) return null;
|
|
2651
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
2652
|
+
if (content.length === 0) return null;
|
|
2653
|
+
return createHash("md5").update(content).digest("hex");
|
|
2654
|
+
} catch {
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* 更新文件哈希缓存,只有当文件内容发生变化时才更新并返回 true
|
|
2660
|
+
*
|
|
2661
|
+
* @param filePath 文件路径
|
|
2662
|
+
* @returns 是否发生变化
|
|
2663
|
+
*/
|
|
2664
|
+
updateFileHash(filePath) {
|
|
2665
|
+
const current = this.getFileHash(filePath);
|
|
2666
|
+
if (!current) return false;
|
|
2667
|
+
const prev = this.fileHashes.get(filePath);
|
|
2668
|
+
if (prev === current) return false;
|
|
2669
|
+
this.fileHashes.set(filePath, current);
|
|
2670
|
+
return true;
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* 检查指定名称的笔记目录是否已存在于缓存中
|
|
2674
|
+
*
|
|
2675
|
+
* @param name 笔记目录名称
|
|
2676
|
+
* @returns 若存在则返回 true,否则返回 false
|
|
2677
|
+
*/
|
|
2678
|
+
hasNoteDir(name) {
|
|
2679
|
+
return this.noteDirCache.has(name);
|
|
2699
2680
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
constructor(config2) {
|
|
2708
|
-
this.config = config2;
|
|
2681
|
+
/**
|
|
2682
|
+
* 将指定名称的笔记目录添加到缓存中
|
|
2683
|
+
*
|
|
2684
|
+
* @param name 笔记目录名称
|
|
2685
|
+
*/
|
|
2686
|
+
addNoteDir(name) {
|
|
2687
|
+
this.noteDirCache.add(name);
|
|
2709
2688
|
}
|
|
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}`);
|
|
2689
|
+
/**
|
|
2690
|
+
* 从缓存中移除指定名称的笔记目录
|
|
2691
|
+
*
|
|
2692
|
+
* @param name 笔记目录名称
|
|
2693
|
+
*/
|
|
2694
|
+
deleteNoteDir(name) {
|
|
2695
|
+
this.noteDirCache.delete(name);
|
|
2724
2696
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2697
|
+
/**
|
|
2698
|
+
* 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
|
|
2699
|
+
*/
|
|
2700
|
+
clearAll() {
|
|
2701
|
+
this.fileHashes.clear();
|
|
2702
|
+
this.noteDirCache.clear();
|
|
2703
|
+
this.configCache.clear();
|
|
2729
2704
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2705
|
+
/**
|
|
2706
|
+
* 清除指定笔记目录相关的缓存数据,包括 README.md 和 .tnotes.json 的文件哈希及配置快照
|
|
2707
|
+
*
|
|
2708
|
+
* @param noteDirName 笔记目录名称
|
|
2709
|
+
*/
|
|
2710
|
+
clearNoteCaches(noteDirName) {
|
|
2711
|
+
const readmePath = join6(this.config.notesDir, noteDirName, "README.md");
|
|
2712
|
+
const configPath = join6(this.config.notesDir, noteDirName, ".tnotes.json");
|
|
2713
|
+
this.fileHashes.delete(readmePath);
|
|
2714
|
+
this.fileHashes.delete(configPath);
|
|
2715
|
+
this.configCache.delete(configPath);
|
|
2732
2716
|
}
|
|
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);
|
|
2717
|
+
/**
|
|
2718
|
+
* 获取指定配置文件路径对应的配置快照
|
|
2719
|
+
*
|
|
2720
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2721
|
+
* @returns 配置快照,若不存在则返回 undefined
|
|
2722
|
+
*/
|
|
2723
|
+
getConfigSnapshot(configPath) {
|
|
2724
|
+
return this.configCache.get(configPath);
|
|
2754
2725
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2726
|
+
/**
|
|
2727
|
+
* 设置指定配置文件路径的配置快照到缓存中
|
|
2728
|
+
*
|
|
2729
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2730
|
+
* @param snapshot 配置快照对象
|
|
2731
|
+
*/
|
|
2732
|
+
setConfigSnapshot(configPath, snapshot) {
|
|
2733
|
+
this.configCache.set(configPath, snapshot);
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* 读取指定配置文件的快照
|
|
2737
|
+
*
|
|
2738
|
+
* 解析 .tnotes.json 配置文件,提取 done、enableDiscussions、description 字段。
|
|
2739
|
+
*
|
|
2740
|
+
* @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
|
|
2741
|
+
* @returns 配置快照,若文件不存在或解析失败则返回 null
|
|
2742
|
+
*/
|
|
2743
|
+
readConfigSnapshot(configPath) {
|
|
2744
|
+
try {
|
|
2745
|
+
if (!existsSync5(configPath)) return null;
|
|
2746
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
2747
|
+
const config2 = JSON.parse(content);
|
|
2748
|
+
return {
|
|
2749
|
+
done: Boolean(config2.done),
|
|
2750
|
+
enableDiscussions: Boolean(config2.enableDiscussions),
|
|
2751
|
+
description: config2.description || ""
|
|
2752
|
+
};
|
|
2753
|
+
} catch (error) {
|
|
2754
|
+
this.config.logger.error(`[\u8BFB\u53D6\u914D\u7F6E\u5FEB\u7167] ${error}`);
|
|
2760
2755
|
return null;
|
|
2761
2756
|
}
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* 从磁盘初始化监听状态缓存:
|
|
2760
|
+
* 遍历笔记根目录下的所有子目录,将每个笔记目录的 README.md 和 .tnotes.json
|
|
2761
|
+
* 的哈希值及配置快照加载到缓存中。
|
|
2762
|
+
*/
|
|
2763
|
+
initializeFromDisk() {
|
|
2764
|
+
try {
|
|
2765
|
+
const noteDirs = readdirSync2(this.config.notesDir);
|
|
2766
|
+
this.clearAll();
|
|
2767
|
+
for (const noteDir of noteDirs) {
|
|
2768
|
+
const noteDirPath = join6(this.config.notesDir, noteDir);
|
|
2769
|
+
if (!statSync(noteDirPath).isDirectory()) continue;
|
|
2770
|
+
this.noteDirCache.add(noteDir);
|
|
2771
|
+
const readmePath = join6(noteDirPath, "README.md");
|
|
2772
|
+
const readmeHash = this.getFileHash(readmePath);
|
|
2773
|
+
if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
|
|
2774
|
+
const configPath = join6(noteDirPath, ".tnotes.json");
|
|
2775
|
+
const configHash = this.getFileHash(configPath);
|
|
2776
|
+
if (configHash) {
|
|
2777
|
+
this.fileHashes.set(configPath, configHash);
|
|
2778
|
+
const snapshot = this.readConfigSnapshot(configPath);
|
|
2779
|
+
if (snapshot) this.configCache.set(configPath, snapshot);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
} catch (error) {
|
|
2783
|
+
this.config.logger.warn(
|
|
2784
|
+
`[initializeFromDisk] \u521D\u59CB\u5316\u76D1\u542C\u72B6\u6001\u5931\u8D25: ${error}`
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2770
2787
|
}
|
|
2771
2788
|
};
|
|
2772
2789
|
|
|
2790
|
+
// services/note/service.ts
|
|
2791
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "fs";
|
|
2792
|
+
import { join as join7 } from "path";
|
|
2793
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2794
|
+
|
|
2773
2795
|
// services/readme/service.ts
|
|
2774
2796
|
import {
|
|
2775
2797
|
existsSync as existsSync6,
|
|
@@ -3091,18 +3113,6 @@ var ReadmeService = class _ReadmeService {
|
|
|
3091
3113
|
}
|
|
3092
3114
|
};
|
|
3093
3115
|
|
|
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
3116
|
// services/note/service.ts
|
|
3107
3117
|
var NEW_NOTES_README_MD_TEMPLATE = `
|
|
3108
3118
|
<!-- region:toc -->
|
|
@@ -3355,6 +3365,7 @@ var NoteService = class _NoteService {
|
|
|
3355
3365
|
// services/file-watcher/service.ts
|
|
3356
3366
|
var NOTES_DIR_NOT_SET_ERROR = "NOTES_DIR_PATH \u672A\u8BBE\u7F6E\uFF0C\u65E0\u6CD5\u542F\u52A8\u6587\u4EF6\u76D1\u542C";
|
|
3357
3367
|
var UPDATE_UNLOCK_DELAY_MS2 = 500;
|
|
3368
|
+
var RENAME_BROADCAST_PATH = "/__tnotes_broadcast_rename";
|
|
3358
3369
|
var FileWatcherService = class _FileWatcherService {
|
|
3359
3370
|
constructor(notesDir = NOTES_DIR_PATH) {
|
|
3360
3371
|
this.notesDir = notesDir;
|
|
@@ -3414,7 +3425,58 @@ var FileWatcherService = class _FileWatcherService {
|
|
|
3414
3425
|
noteService: this.noteService,
|
|
3415
3426
|
readmeService: this.readmeService,
|
|
3416
3427
|
noteIndexCache: this.noteIndexCache,
|
|
3417
|
-
logger
|
|
3428
|
+
logger,
|
|
3429
|
+
onRenameSuccess: (payload) => {
|
|
3430
|
+
void this.broadcastRename(payload);
|
|
3431
|
+
}
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
async broadcastRename(payload) {
|
|
3435
|
+
const path2 = `/${repoName}${RENAME_BROADCAST_PATH}`;
|
|
3436
|
+
const body = JSON.stringify(payload);
|
|
3437
|
+
const hosts = ["127.0.0.1", "::1"];
|
|
3438
|
+
let lastError = null;
|
|
3439
|
+
for (const host of hosts) {
|
|
3440
|
+
try {
|
|
3441
|
+
await this.postOnce(host, path2, body);
|
|
3442
|
+
return;
|
|
3443
|
+
} catch (error) {
|
|
3444
|
+
lastError = error;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
logger.warn(
|
|
3448
|
+
`\u5E7F\u64AD\u91CD\u547D\u540D\u4E8B\u4EF6\u5931\u8D25 (POST http://[127.0.0.1|::1]:${port}${path2}): ${String(lastError)}`
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
postOnce(host, path2, body) {
|
|
3452
|
+
return new Promise((resolve2, reject) => {
|
|
3453
|
+
const req = httpRequest(
|
|
3454
|
+
{
|
|
3455
|
+
host,
|
|
3456
|
+
port,
|
|
3457
|
+
path: path2,
|
|
3458
|
+
method: "POST",
|
|
3459
|
+
family: host.includes(":") ? 6 : 4,
|
|
3460
|
+
headers: {
|
|
3461
|
+
"Content-Type": "application/json",
|
|
3462
|
+
"Content-Length": Buffer.byteLength(body)
|
|
3463
|
+
}
|
|
3464
|
+
},
|
|
3465
|
+
(res) => {
|
|
3466
|
+
res.on("data", () => {
|
|
3467
|
+
});
|
|
3468
|
+
res.on("end", () => {
|
|
3469
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
3470
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
resolve2();
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
);
|
|
3477
|
+
req.on("error", reject);
|
|
3478
|
+
req.write(body);
|
|
3479
|
+
req.end();
|
|
3418
3480
|
});
|
|
3419
3481
|
}
|
|
3420
3482
|
initRenameDetector() {
|
|
@@ -3626,6 +3688,7 @@ var GitService = class {
|
|
|
3626
3688
|
};
|
|
3627
3689
|
|
|
3628
3690
|
// services/timestamp/service.ts
|
|
3691
|
+
import { execSync as execSync3 } from "child_process";
|
|
3629
3692
|
import {
|
|
3630
3693
|
existsSync as existsSync7,
|
|
3631
3694
|
readFileSync as readFileSync6,
|
|
@@ -3634,7 +3697,6 @@ import {
|
|
|
3634
3697
|
statSync as statSync2
|
|
3635
3698
|
} from "fs";
|
|
3636
3699
|
import { join as join8 } from "path";
|
|
3637
|
-
import { execSync as execSync3 } from "child_process";
|
|
3638
3700
|
var BIRTH_DATE = (/* @__PURE__ */ new Date("1999-06-29T00:00:00+08:00")).getTime();
|
|
3639
3701
|
var TimestampService = class {
|
|
3640
3702
|
noteManager;
|
|
@@ -4146,6 +4208,41 @@ var VitepressService = class _VitepressService {
|
|
|
4146
4208
|
}
|
|
4147
4209
|
};
|
|
4148
4210
|
|
|
4211
|
+
// commands/BaseCommand.ts
|
|
4212
|
+
var BaseCommand = class {
|
|
4213
|
+
constructor(name) {
|
|
4214
|
+
this.name = name;
|
|
4215
|
+
this.logger = logger.child(name);
|
|
4216
|
+
}
|
|
4217
|
+
logger;
|
|
4218
|
+
options = {};
|
|
4219
|
+
/** 命令描述(从静态配置读取) */
|
|
4220
|
+
get description() {
|
|
4221
|
+
return COMMAND_DESCRIPTIONS[this.name];
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* 设置命令选项
|
|
4225
|
+
*/
|
|
4226
|
+
setOptions(options) {
|
|
4227
|
+
this.options = { ...this.options, ...options };
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* 执行命令(带错误处理)
|
|
4231
|
+
*/
|
|
4232
|
+
async execute() {
|
|
4233
|
+
const startTime = Date.now();
|
|
4234
|
+
try {
|
|
4235
|
+
this.logger.start(this.description);
|
|
4236
|
+
await this.run();
|
|
4237
|
+
const duration = Date.now() - startTime;
|
|
4238
|
+
this.logger.done(`\u547D\u4EE4\u6267\u884C\u8017\u65F6\uFF1A${duration} ms`);
|
|
4239
|
+
} catch (error) {
|
|
4240
|
+
handleError(error);
|
|
4241
|
+
throw error;
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
|
|
4149
4246
|
// commands/note/UpdateNoteConfigCommand.ts
|
|
4150
4247
|
var UpdateNoteConfigCommand = class extends BaseCommand {
|
|
4151
4248
|
noteService;
|
|
@@ -4295,20 +4392,16 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
4295
4392
|
};
|
|
4296
4393
|
|
|
4297
4394
|
export {
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
COMMAND_OPTIONS,
|
|
4395
|
+
ROOT_DIR_PATH,
|
|
4396
|
+
ROOT_CONFIG_PATH,
|
|
4301
4397
|
handleError,
|
|
4302
4398
|
generateAnchor,
|
|
4303
4399
|
logger,
|
|
4304
4400
|
createLogger,
|
|
4305
4401
|
parseArgs,
|
|
4306
4402
|
parseReadmeCompletedNotes,
|
|
4307
|
-
ROOT_DIR_PATH,
|
|
4308
|
-
ROOT_CONFIG_PATH,
|
|
4309
|
-
NoteManager,
|
|
4310
4403
|
runCommand,
|
|
4311
|
-
|
|
4404
|
+
NoteManager,
|
|
4312
4405
|
NoteIndexCache,
|
|
4313
4406
|
ReadmeService,
|
|
4314
4407
|
NoteService,
|
|
@@ -4316,6 +4409,10 @@ export {
|
|
|
4316
4409
|
GitService,
|
|
4317
4410
|
TimestampService,
|
|
4318
4411
|
VitepressService,
|
|
4412
|
+
COMMAND_NAMES,
|
|
4413
|
+
COMMAND_DESCRIPTIONS,
|
|
4414
|
+
COMMAND_OPTIONS,
|
|
4415
|
+
BaseCommand,
|
|
4319
4416
|
UpdateNoteConfigCommand,
|
|
4320
4417
|
RenameNoteCommand
|
|
4321
4418
|
};
|