@tnotesjs/core 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/commands/BaseCommand.ts +58 -0
  2. package/commands/build/BuildCommand.ts +25 -0
  3. package/commands/build/PreviewCommand.ts +29 -0
  4. package/commands/build/index.ts +8 -0
  5. package/commands/dev/DevCommand.ts +75 -0
  6. package/commands/dev/index.ts +7 -0
  7. package/commands/git/PullCommand.ts +25 -0
  8. package/commands/git/PushCommand.ts +64 -0
  9. package/commands/git/index.ts +8 -0
  10. package/commands/index.ts +11 -0
  11. package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
  12. package/commands/init-sub-repo/index.ts +1 -0
  13. package/commands/misc/HelpCommand.ts +104 -0
  14. package/commands/misc/index.ts +7 -0
  15. package/commands/models.ts +87 -0
  16. package/commands/note/CreateNoteCommand.ts +160 -0
  17. package/commands/note/RenameNoteCommand.ts +147 -0
  18. package/commands/note/UpdateNoteConfigCommand.ts +78 -0
  19. package/commands/note/index.ts +9 -0
  20. package/commands/registry.ts +47 -0
  21. package/commands/update/UpdateCommand.ts +219 -0
  22. package/commands/update/index.ts +7 -0
  23. package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
  24. package/commands/update-completed-count/index.ts +5 -0
  25. package/config/templates.ts +0 -5
  26. package/dist/{chunk-5F5RJ2HV.cjs → chunk-7FEANCT6.cjs} +16 -7
  27. package/dist/chunk-AGVER5MX.cjs +1746 -0
  28. package/dist/{chunk-TJ4527KA.cjs → chunk-BL22KD4V.cjs} +324 -950
  29. package/dist/{chunk-56PKXCOX.js → chunk-GWG75A5M.js} +13 -4
  30. package/dist/{chunk-DUTS75WQ.js → chunk-XNO3PCEH.js} +374 -1000
  31. package/dist/chunk-ZEQS64PE.js +1746 -0
  32. package/dist/cli/index.cjs +76 -155
  33. package/dist/cli/index.js +10 -89
  34. package/dist/markdown/index.cjs +8 -12
  35. package/dist/markdown/index.d.cts +1 -1
  36. package/dist/markdown/index.d.ts +1 -1
  37. package/dist/markdown/index.js +7 -11
  38. package/dist/{types-CwBWwNVv.d.ts → types-C_d0fFeD.d.ts} +8 -1
  39. package/dist/{types-CpsEy8lA.d.cts → types-MRBGgJTX.d.cts} +8 -1
  40. package/dist/vitepress/config/index.cjs +402 -178
  41. package/dist/vitepress/config/index.js +392 -168
  42. package/dist/workspace/index.cjs +4 -1202
  43. package/dist/workspace/index.d.cts +2 -2
  44. package/dist/workspace/index.d.ts +2 -2
  45. package/dist/workspace/index.js +4 -1202
  46. package/markdown/components.ts +86 -0
  47. package/markdown/index.ts +17 -0
  48. package/markdown/noteFormatter.test.ts +44 -0
  49. package/markdown/noteFormatter.ts +237 -0
  50. package/package.json +7 -3
  51. package/services/file-watcher/folderChangeHandler.ts +19 -15
  52. package/services/file-watcher/globalUpdateCoordinator.ts +21 -27
  53. package/services/file-watcher/service.ts +1 -6
  54. package/services/index.ts +0 -1
  55. package/services/note/service.ts +4 -6
  56. package/services/readme/service.ts +11 -18
  57. package/services/reconcileToc.ts +20 -0
  58. package/utils/index.ts +0 -9
  59. package/{services/toc → utils}/moveTocInside.test.ts +1 -1
  60. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
  61. package/vitepress/components/EnWordList/EnWordList.vue +16 -662
  62. package/vitepress/components/Footprints/Footprints.vue +15 -537
  63. package/vitepress/components/Mermaid/Mermaid.vue +13 -588
  64. package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
  65. package/vitepress/components/MindmapPreview/markdown.ts +1 -1
  66. package/vitepress/components/NotesTable/NotesTable.vue +11 -130
  67. package/vitepress/components/constants.ts +2 -2
  68. package/vitepress/components/sidebar.data.ts +8 -5
  69. package/vitepress/configs/markdown.config.ts +170 -26
  70. package/vitepress/plugins/sidebarStructurePlugin.ts +242 -145
  71. package/vitepress/theme/index.ts +9 -13
  72. package/vitepress/theme/styles/base.scss +15 -0
  73. package/workspace/atomic.ts +113 -0
  74. package/workspace/errors.ts +27 -0
  75. package/workspace/index.ts +40 -0
  76. package/workspace/mutationQueue.ts +28 -0
  77. package/workspace/paths.ts +64 -0
  78. package/workspace/reconcile.test.ts +300 -0
  79. package/workspace/reconcile.ts +95 -0
  80. package/workspace/scanner.ts +292 -0
  81. package/workspace/types.ts +224 -0
  82. package/workspace/workspace.test.ts +333 -0
  83. package/workspace/workspace.ts +1020 -0
  84. package/dist/chunk-3R7QSABB.cjs +0 -502
  85. package/dist/chunk-CZXRQPFJ.js +0 -11
  86. package/dist/chunk-HXED7KVT.cjs +0 -11
  87. package/dist/chunk-U6IBLREL.js +0 -502
  88. package/services/toc/index.ts +0 -5
  89. package/services/toc/service.ts +0 -759
  90. package/utils/migrateReadmeToToc.test.ts +0 -111
  91. package/utils/migrateReadmeToToc.ts +0 -135
  92. package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
@@ -7,29 +7,10 @@ var _chunkMZIXIY2Ycjs = require('./chunk-MZIXIY2Y.cjs');
7
7
 
8
8
 
9
9
 
10
+ var _chunkAGVER5MXcjs = require('./chunk-AGVER5MX.cjs');
10
11
 
11
12
 
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
- var _chunk3R7QSABBcjs = require('./chunk-3R7QSABB.cjs');
30
-
31
-
32
- var _chunkHXED7KVTcjs = require('./chunk-HXED7KVT.cjs');
13
+ var _chunk7FEANCT6cjs = require('./chunk-7FEANCT6.cjs');
33
14
 
34
15
  // utils/errorHandler.ts
35
16
  var TNotesError = class _TNotesError extends Error {
@@ -419,7 +400,7 @@ function generateToc(titles, baseLevel = 2, eol = "\n") {
419
400
  const toc = titles.map((title) => {
420
401
  const level = title.indexOf(" ");
421
402
  const text = title.slice(level).trim();
422
- const anchor = _chunkHXED7KVTcjs.generateAnchor.call(void 0, text);
403
+ const anchor = _chunk7FEANCT6cjs.generateAnchor.call(void 0, text);
423
404
  const indent = Math.max(0, (level - baseLevel) * 2);
424
405
  return " ".repeat(indent) + `- [${text}](#${anchor})`;
425
406
  }).join(eol);
@@ -476,81 +457,6 @@ function parseReadmeCompletedNotes(content) {
476
457
  };
477
458
  }
478
459
 
479
- // utils/migrateReadmeToToc.ts
480
- var README_TOC_END_TAG = "<!-- endregion:toc -->";
481
- function extractReadmeBodyAfterToc(content) {
482
- const endTagIndex = content.indexOf(README_TOC_END_TAG);
483
- if (endTagIndex === -1) {
484
- throw new Error(
485
- `README.md \u4E2D\u672A\u627E\u5230 ${README_TOC_END_TAG}\uFF0C\u65E0\u6CD5\u6267\u884C init-toc \u8FC1\u79FB`
486
- );
487
- }
488
- const afterTag = content.slice(endTagIndex + README_TOC_END_TAG.length);
489
- return afterTag.split("\n");
490
- }
491
- function migrateReadmeToToc(content) {
492
- const lines = extractReadmeBodyAfterToc(content);
493
- const warnings = [];
494
- const seenIndexes = /* @__PURE__ */ new Set();
495
- const sections = [];
496
- let currentHeading = null;
497
- let currentGroup = [];
498
- const flushGroup = () => {
499
- if (currentGroup.length > 0) {
500
- sections.push({ heading: currentHeading, items: currentGroup });
501
- }
502
- currentGroup = [];
503
- };
504
- for (const line of lines) {
505
- const headingMatch = line.match(/^(#{2,})\s+(.+)$/);
506
- if (headingMatch) {
507
- flushGroup();
508
- currentHeading = headingMatch[2].trim();
509
- continue;
510
- }
511
- const parsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, line);
512
- if (!parsed.isMatch || !parsed.noteIndex) continue;
513
- if (seenIndexes.has(parsed.noteIndex)) {
514
- warnings.push(`\u8DF3\u8FC7\u91CD\u590D\u7B14\u8BB0\u7D22\u5F15: ${parsed.noteIndex}`);
515
- continue;
516
- }
517
- seenIndexes.add(parsed.noteIndex);
518
- currentGroup.push({
519
- noteIndex: parsed.noteIndex,
520
- completed: parsed.completed
521
- });
522
- }
523
- flushGroup();
524
- const entries = [];
525
- for (const section of sections) {
526
- if (section.heading) {
527
- entries.push({
528
- kind: "folder",
529
- folderTitle: section.heading,
530
- indent: 0
531
- });
532
- for (const item of section.items) {
533
- entries.push({
534
- kind: "note",
535
- noteIndex: item.noteIndex,
536
- indent: 1,
537
- completed: item.completed
538
- });
539
- }
540
- continue;
541
- }
542
- for (const item of section.items) {
543
- entries.push({
544
- kind: "note",
545
- noteIndex: item.noteIndex,
546
- indent: 0,
547
- completed: item.completed
548
- });
549
- }
550
- }
551
- return { entries, warnings };
552
- }
553
-
554
460
  // utils/portUtils.ts
555
461
 
556
462
  function isPortInUse(port2) {
@@ -1213,7 +1119,6 @@ var COMMAND_NAMES = {
1213
1119
  PULL: "pull",
1214
1120
  PUSH: "push",
1215
1121
  RENAME_NOTE: "rename-note",
1216
- INIT_TOC: "init-toc",
1217
1122
  INIT_SUB_REPO: "init-sub-repo",
1218
1123
  UPDATE: "update",
1219
1124
  UPDATE_COMPLETED_COUNT: "update-completed-count",
@@ -1223,7 +1128,6 @@ var COMMAND_DESCRIPTIONS = {
1223
1128
  [COMMAND_NAMES.DEV]: "\u542F\u52A8\u77E5\u8BC6\u5E93\u5F00\u53D1\u670D\u52A1",
1224
1129
  [COMMAND_NAMES.BUILD]: "\u6784\u5EFA\u77E5\u8BC6\u5E93",
1225
1130
  [COMMAND_NAMES.PREVIEW]: "\u9884\u89C8\u6784\u5EFA\u540E\u7684\u77E5\u8BC6\u5E93",
1226
- [COMMAND_NAMES.INIT_TOC]: "\u4ECE README.md \u521D\u59CB\u5316 TOC.md\uFF08v0.2.x \u8FC7\u6E21\u547D\u4EE4\uFF0Cv0.3.x \u79FB\u9664\uFF09",
1227
1131
  [COMMAND_NAMES.INIT_SUB_REPO]: "\u521D\u59CB\u5316 TNotes \u5B50\u77E5\u8BC6\u5E93\uFF08\u4E00\u6B21\u6027\u811A\u624B\u67B6\uFF09",
1228
1132
  [COMMAND_NAMES.UPDATE]: "\u6839\u636E\u7B14\u8BB0\u5185\u5BB9\u66F4\u65B0\u77E5\u8BC6\u5E93",
1229
1133
  [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",
@@ -1385,783 +1289,6 @@ var NoteIndexCache = class _NoteIndexCache {
1385
1289
  }
1386
1290
  };
1387
1291
 
1388
- // services/toc/service.ts
1389
-
1390
-
1391
-
1392
-
1393
-
1394
-
1395
-
1396
- // services/note/service.ts
1397
-
1398
-
1399
- var _uuid = require('uuid');
1400
- var NoteService = class _NoteService {
1401
- constructor() {
1402
- this.ignoredConfigPaths = /* @__PURE__ */ new Set();
1403
- this.noteManager = NoteManager.getInstance();
1404
- this.noteIndexCache = NoteIndexCache.getInstance();
1405
- }
1406
- static getInstance() {
1407
- if (!_NoteService.instance) {
1408
- _NoteService.instance = new _NoteService();
1409
- }
1410
- return _NoteService.instance;
1411
- }
1412
- /**
1413
- * 标记配置文件在下次变更时被忽略(防止 API 写入触发文件监听循环)
1414
- * @param configPath - 配置文件路径
1415
- */
1416
- ignoreNextConfigChange(configPath) {
1417
- this.ignoredConfigPaths.add(configPath);
1418
- }
1419
- /**
1420
- * 检查配置文件是否应该被忽略
1421
- * @param configPath - 配置文件路径
1422
- * @returns 是否应该忽略
1423
- */
1424
- shouldIgnoreConfigChange(configPath) {
1425
- if (this.ignoredConfigPaths.has(configPath)) {
1426
- this.ignoredConfigPaths.delete(configPath);
1427
- return true;
1428
- }
1429
- return false;
1430
- }
1431
- /**
1432
- * 获取所有笔记
1433
- * dev 模式下(缓存已初始化)从内存读取,其他模式回退到文件扫描
1434
- * @returns 笔记信息数组
1435
- */
1436
- getAllNotes() {
1437
- if (this.noteIndexCache.isInitialized()) {
1438
- return this.noteIndexCache.toNoteInfoList();
1439
- }
1440
- return this.noteManager.scanNotes();
1441
- }
1442
- /**
1443
- * 获取笔记(通过索引)
1444
- * @param noteIndex - 笔记索引(文件夹前 4 位数字)
1445
- * @returns 笔记信息,未找到时返回 undefined
1446
- */
1447
- getNoteByIndex(noteIndex) {
1448
- return this.noteManager.getNoteByIndex(noteIndex);
1449
- }
1450
- /**
1451
- * 创建新笔记
1452
- * @param options - 创建选项
1453
- * @returns 新创建的笔记信息
1454
- */
1455
- async createNote(options = {}) {
1456
- const {
1457
- title = "new",
1458
- category,
1459
- enableDiscussions = false,
1460
- configId,
1461
- usedIndexes
1462
- } = options;
1463
- const noteIndex = this.generateNextNoteIndex(usedIndexes);
1464
- const dirName = `${noteIndex}. ${title}`;
1465
- const notePath = _path.join.call(void 0, NOTES_PATH, dirName);
1466
- await ensureDirectory(notePath);
1467
- const readmePath = _path.join.call(void 0, notePath, "README.md");
1468
- const noteTitle = _chunk3R7QSABBcjs.generateNoteTitle.call(void 0, noteIndex, title, REPO_NOTES_URL);
1469
- const readmeContent = noteTitle + "\n" + _chunk3R7QSABBcjs.getNewNoteReadmeBody.call(void 0, );
1470
- _fs.writeFileSync.call(void 0, readmePath, readmeContent, "utf-8");
1471
- const configPath = _path.join.call(void 0, notePath, ".tnotes.json");
1472
- const config2 = {
1473
- id: configId || _uuid.v4.call(void 0, ),
1474
- // 配置 ID 使用 UUID(跨知识库唯一)
1475
- bilibili: [],
1476
- tnotes: [],
1477
- yuque: [],
1478
- done: false,
1479
- category,
1480
- enableDiscussions
1481
- };
1482
- this.noteManager.writeNoteConfig(configPath, config2);
1483
- const noteInfo = {
1484
- index: noteIndex,
1485
- // 返回的 id 是笔记索引(目录前缀)
1486
- path: notePath,
1487
- dirName,
1488
- readmePath,
1489
- configPath,
1490
- config: config2
1491
- };
1492
- if (this.noteIndexCache.isInitialized()) {
1493
- this.noteIndexCache.add(noteInfo);
1494
- }
1495
- logger.info(`Created new note: ${dirName}`);
1496
- return noteInfo;
1497
- }
1498
- /**
1499
- * 删除笔记文件夹
1500
- * @param noteIndex - 笔记索引
1501
- * @returns 被删除的笔记信息
1502
- */
1503
- async deleteNote(noteIndex) {
1504
- const note = this.getNoteByIndex(noteIndex);
1505
- if (!note) {
1506
- throw new Error(`\u7B14\u8BB0\u672A\u627E\u5230: ${noteIndex}`);
1507
- }
1508
- await _fs.promises.rm(note.path, { recursive: true, force: true });
1509
- if (this.noteIndexCache.isInitialized()) {
1510
- this.noteIndexCache.delete(noteIndex);
1511
- }
1512
- logger.info(`Deleted note: ${note.dirName}`);
1513
- return note;
1514
- }
1515
- /**
1516
- * 生成下一个笔记索引(填充空缺)
1517
- * @param usedIndexes - 可选的已使用编号集合,不传则内部扫描
1518
- * @returns 新的笔记索引(4位数字字符串,从 0001 到 9999)
1519
- */
1520
- generateNextNoteIndex(usedIndexes) {
1521
- if (!usedIndexes) {
1522
- const notes = this.getAllNotes();
1523
- usedIndexes = /* @__PURE__ */ new Set();
1524
- for (const note of notes) {
1525
- const id = parseInt(note.index, 10);
1526
- if (!isNaN(id) && id >= 1 && id <= 9999) {
1527
- usedIndexes.add(id);
1528
- }
1529
- }
1530
- }
1531
- if (usedIndexes.size === 0) {
1532
- return "0001";
1533
- }
1534
- for (let i = 1; i <= 9999; i++) {
1535
- if (!usedIndexes.has(i)) {
1536
- return i.toString().padStart(CONSTANTS.NOTE_INDEX_LENGTH, "0");
1537
- }
1538
- }
1539
- throw new Error("\u6240\u6709\u7B14\u8BB0\u7F16\u53F7 (0001-9999) \u5DF2\u88AB\u5360\u7528\uFF0C\u65E0\u6CD5\u521B\u5EFA\u65B0\u7B14\u8BB0");
1540
- }
1541
- /**
1542
- * 更新笔记配置
1543
- * @param noteIndex - 笔记索引
1544
- * @param updates - 配置更新
1545
- */
1546
- async updateNoteConfig(noteIndex, updates) {
1547
- const note = this.getNoteByIndex(noteIndex);
1548
- if (!note || !note.config) {
1549
- throw new Error(`Note not found or no config: ${noteIndex}`);
1550
- }
1551
- const oldConfig = { ...note.config };
1552
- const updatedConfig = {
1553
- ...note.config,
1554
- ...updates
1555
- };
1556
- this.ignoreNextConfigChange(note.configPath);
1557
- this.noteManager.updateNoteConfig(note, updatedConfig);
1558
- this.noteIndexCache.updateConfig(noteIndex, updatedConfig);
1559
- const needsGlobalUpdate = this.checkNeedsGlobalUpdate(
1560
- oldConfig,
1561
- updatedConfig
1562
- );
1563
- if (needsGlobalUpdate) {
1564
- logger.info(`\u68C0\u6D4B\u5230\u5168\u5C40\u5B57\u6BB5\u53D8\u66F4 (${noteIndex})\uFF0C\u6B63\u5728\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...`);
1565
- const tocService = TocService.getInstance();
1566
- await tocService.updateNoteInToc(noteIndex, updates);
1567
- await tocService.regenerateSidebar();
1568
- logger.info(`\u5168\u5C40\u6587\u4EF6\u589E\u91CF\u66F4\u65B0\u5B8C\u6210 (${noteIndex})`);
1569
- } else {
1570
- logger.debug(`\u914D\u7F6E\u66F4\u65B0\u4E0D\u5F71\u54CD\u5168\u5C40\u6587\u4EF6 (${noteIndex})`);
1571
- }
1572
- }
1573
- /**
1574
- * 检查配置更新是否需要触发全局更新
1575
- * @param oldConfig - 旧配置
1576
- * @param newConfig - 新配置
1577
- * @returns 是否需要全局更新
1578
- */
1579
- checkNeedsGlobalUpdate(oldConfig, newConfig) {
1580
- const globalFields = ["done"];
1581
- for (const field of globalFields) {
1582
- if (oldConfig[field] !== newConfig[field]) {
1583
- return true;
1584
- }
1585
- }
1586
- return false;
1587
- }
1588
- /**
1589
- * 修正笔记标题
1590
- * @param noteInfo - 笔记信息
1591
- * @returns 是否进行了修正
1592
- */
1593
- async fixNoteTitle(noteInfo) {
1594
- try {
1595
- const readmeContent = _fs.readFileSync.call(void 0, noteInfo.readmePath, "utf-8");
1596
- if (readmeContent.length === 0) return false;
1597
- const lines = readmeContent.split("\n");
1598
- const match = noteInfo.dirName.match(/^\d{4}\.\s+(.+)$/);
1599
- if (!match) {
1600
- logger.warn(`\u68C0\u6D4B\u5230\u9519\u8BEF\u7684\u7B14\u8BB0\u76EE\u5F55\u540D\u79F0\uFF1A${noteInfo.dirName}`);
1601
- return false;
1602
- }
1603
- const expectedTitle = match[1];
1604
- const expectedH1 = _chunk3R7QSABBcjs.generateNoteTitle.call(void 0,
1605
- noteInfo.index,
1606
- expectedTitle,
1607
- REPO_NOTES_URL
1608
- );
1609
- const firstLine = lines[0].trim();
1610
- if (!firstLine.startsWith("# ")) {
1611
- lines.unshift(expectedH1);
1612
- _fs.writeFileSync.call(void 0, noteInfo.readmePath, lines.join("\n"), "utf-8");
1613
- logger.info(`Added title to: ${noteInfo.dirName}`);
1614
- return true;
1615
- }
1616
- if (firstLine !== expectedH1) {
1617
- lines[0] = expectedH1;
1618
- _fs.writeFileSync.call(void 0, noteInfo.readmePath, lines.join("\n"), "utf-8");
1619
- logger.info(`Fixed title for: ${noteInfo.dirName}`);
1620
- return true;
1621
- }
1622
- return false;
1623
- } catch (error) {
1624
- logger.error(`Failed to fix title for: ${noteInfo.dirName}`, error);
1625
- return false;
1626
- }
1627
- }
1628
- /**
1629
- * 修正所有笔记的标题
1630
- * @param providedNotes - 可选的笔记列表,不传则内部扫描
1631
- * @returns 修正的笔记数量
1632
- */
1633
- async fixAllNoteTitles(providedNotes) {
1634
- const notes = _nullishCoalesce(providedNotes, () => ( this.getAllNotes()));
1635
- let fixedCount = 0;
1636
- for (const note of notes) {
1637
- const fixed = await this.fixNoteTitle(note);
1638
- if (fixed) {
1639
- fixedCount++;
1640
- }
1641
- }
1642
- if (fixedCount > 0) {
1643
- logger.info(`Fixed ${fixedCount} note titles`);
1644
- }
1645
- return fixedCount;
1646
- }
1647
- };
1648
-
1649
- // services/toc/service.ts
1650
- var TocService = class _TocService {
1651
- constructor() {
1652
- this.noteManager = NoteManager.getInstance();
1653
- this.configManager = _chunkMZIXIY2Ycjs.ConfigManager.getInstance();
1654
- this.noteIndexCache = NoteIndexCache.getInstance();
1655
- }
1656
- static getInstance() {
1657
- if (!_TocService.instance) {
1658
- _TocService.instance = new _TocService();
1659
- }
1660
- return _TocService.instance;
1661
- }
1662
- /**
1663
- * 确保 TOC.md 存在
1664
- */
1665
- ensureTocExists() {
1666
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) {
1667
- throw new Error(
1668
- `TOC.md \u4E0D\u5B58\u5728: ${ROOT_TOC_PATH}\u3002\u8BF7\u5728\u77E5\u8BC6\u5E93\u6839\u76EE\u5F55\u521B\u5EFA TOC.md \u4F5C\u4E3A\u76EE\u5F55\u6570\u636E\u6E90\u3002`
1669
- );
1670
- }
1671
- }
1672
- /**
1673
- * update 主流程:规范化 TOC.md
1674
- */
1675
- async normalizeToc(notes) {
1676
- this.ensureTocExists();
1677
- const content = await _fs.promises.readFile(ROOT_TOC_PATH, "utf-8");
1678
- const lines = content.split("\n");
1679
- const tree = _chunk3R7QSABBcjs.parseTocToTree.call(void 0, lines, notes);
1680
- const existingIndexes = /* @__PURE__ */ new Set();
1681
- const collectNoteIndexes = (nodes) => {
1682
- for (const node of nodes) {
1683
- if (node.kind === "note") {
1684
- existingIndexes.add(node.noteIndex);
1685
- }
1686
- collectNoteIndexes(node.children);
1687
- }
1688
- };
1689
- collectNoteIndexes(tree);
1690
- const missingNotes = notes.filter((n) => !existingIndexes.has(n.index));
1691
- if (missingNotes.length > 0) {
1692
- logger.info(`TOC.md \u8FFD\u52A0 ${missingNotes.length} \u7BC7\u7F3A\u5931\u7B14\u8BB0`);
1693
- missingNotes.sort((a, b) => a.index.localeCompare(b.index));
1694
- for (const note of missingNotes) {
1695
- tree.push({
1696
- kind: "note",
1697
- noteIndex: note.index,
1698
- indent: 0,
1699
- tocLineIndex: 0,
1700
- children: []
1701
- });
1702
- }
1703
- }
1704
- const configByIndex = /* @__PURE__ */ new Map();
1705
- for (const note of notes) {
1706
- configByIndex.set(note.index, {
1707
- done: _chunk3R7QSABBcjs.getTocLineCompleted.call(void 0, note)
1708
- });
1709
- }
1710
- const normalizedLines = _chunk3R7QSABBcjs.serializeTocTree.call(void 0, tree, notes, configByIndex);
1711
- await this.writeTocLines(normalizedLines);
1712
- logger.info("\u5DF2\u89C4\u8303\u5316 TOC.md");
1713
- }
1714
- /**
1715
- * 从 TOC.md 重新生成 sidebar.json
1716
- */
1717
- async regenerateSidebar(notes) {
1718
- this.ensureTocExists();
1719
- const allNotes = _nullishCoalesce(notes, () => ( (this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes())));
1720
- const content = _fs.readFileSync.call(void 0, ROOT_TOC_PATH, "utf-8");
1721
- const lines = content.split("\n");
1722
- const tree = _chunk3R7QSABBcjs.parseTocToTree.call(void 0, lines, allNotes);
1723
- const sidebarShowNoteId2 = this.configManager.get("sidebarShowNoteId");
1724
- const hierarchicalSidebar = _chunk3R7QSABBcjs.buildSidebarFromTocTree.call(void 0, tree, allNotes, {
1725
- sidebarShowNoteId: sidebarShowNoteId2,
1726
- sidebarIsCollapsed: true
1727
- });
1728
- const sidebarContent = JSON.stringify(hierarchicalSidebar, null, 2);
1729
- const existingContent = _fs.existsSync.call(void 0, VP_SIDEBAR_PATH) ? _fs.readFileSync.call(void 0, VP_SIDEBAR_PATH, "utf-8") : "";
1730
- if (sidebarContent !== existingContent) {
1731
- _fs.writeFileSync.call(void 0, VP_SIDEBAR_PATH, sidebarContent, "utf-8");
1732
- }
1733
- logger.info("\u5DF2\u66F4\u65B0\u4FA7\u8FB9\u680F\u914D\u7F6E");
1734
- }
1735
- /**
1736
- * 增量更新 TOC.md 中某笔记的完成状态与链接
1737
- */
1738
- async updateNoteInToc(noteIndex, updates) {
1739
- this.ensureTocExists();
1740
- const item = this.noteIndexCache.getByNoteIndex(noteIndex);
1741
- if (!item) {
1742
- logger.warn(`\u5C1D\u8BD5\u66F4\u65B0 TOC \u4E2D\u4E0D\u5B58\u5728\u7684\u7B14\u8BB0: ${noteIndex}`);
1743
- return;
1744
- }
1745
- const notes = this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes();
1746
- const note = _chunk3R7QSABBcjs.resolveNoteFromIndex.call(void 0, noteIndex, notes);
1747
- if (!note) return;
1748
- const mergedConfig = {
1749
- ...note.config,
1750
- ...updates,
1751
- ...item.noteConfig
1752
- };
1753
- const tempNote = {
1754
- ...note,
1755
- dirName: item.folderName,
1756
- config: mergedConfig
1757
- };
1758
- const lines = await this.readTocLines();
1759
- let updated = false;
1760
- for (let i = 0; i < lines.length; i++) {
1761
- const parsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[i]);
1762
- if (parsed.noteIndex === noteIndex) {
1763
- lines[i] = _chunk3R7QSABBcjs.buildTocLine.call(void 0,
1764
- tempNote,
1765
- parsed.indentLevel,
1766
- _chunk3R7QSABBcjs.getTocLineCompleted.call(void 0, tempNote)
1767
- );
1768
- updated = true;
1769
- }
1770
- }
1771
- if (updated) {
1772
- await this.writeTocLines(lines);
1773
- logger.info(`\u589E\u91CF\u66F4\u65B0 TOC.md \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
1774
- } else {
1775
- logger.warn(`TOC.md \u4E2D\u672A\u627E\u5230\u7B14\u8BB0: ${noteIndex}`);
1776
- }
1777
- }
1778
- /**
1779
- * 在 TOC.md 末尾或父节点下追加笔记
1780
- */
1781
- async appendNoteToToc(noteIndex, options) {
1782
- const item = this.noteIndexCache.getByNoteIndex(noteIndex);
1783
- if (!item) {
1784
- logger.warn(`\u5C1D\u8BD5\u6DFB\u52A0\u4E0D\u5B58\u5728\u7684\u7B14\u8BB0\u5230 TOC: ${noteIndex}`);
1785
- return;
1786
- }
1787
- const notes = this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes();
1788
- const note = _chunk3R7QSABBcjs.resolveNoteFromIndex.call(void 0, noteIndex, notes);
1789
- if (!note) return;
1790
- const tempNote = {
1791
- ...note,
1792
- dirName: item.folderName,
1793
- config: item.noteConfig
1794
- };
1795
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) {
1796
- await this.writeTocLines([_chunk3R7QSABBcjs.buildTocLine.call(void 0, tempNote, 0, false)]);
1797
- logger.info(`\u521B\u5EFA TOC.md \u5E76\u6DFB\u52A0\u7B14\u8BB0: ${noteIndex}`);
1798
- return;
1799
- }
1800
- const lines = await this.readTocLines();
1801
- if (_optionalChain([options, 'optionalAccess', _11 => _11.parentTocLineIndex]) !== void 0) {
1802
- await this.insertNotesUnderTocLine(options.parentTocLineIndex, [tempNote]);
1803
- return;
1804
- }
1805
- if (_optionalChain([options, 'optionalAccess', _12 => _12.parentNoteIndex])) {
1806
- const parentIndex = _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, options.parentNoteIndex);
1807
- await this.insertNotesUnderTocLine(parentIndex, [tempNote]);
1808
- return;
1809
- }
1810
- if (_optionalChain([options, 'optionalAccess', _13 => _13.parentFolderPath]) && options.parentFolderPath.length > 0) {
1811
- const folderIndex = _chunk3R7QSABBcjs.findFolderLineIndex.call(void 0, lines, options.parentFolderPath);
1812
- await this.insertNotesUnderTocLine(folderIndex, [tempNote]);
1813
- return;
1814
- }
1815
- lines.push(_chunk3R7QSABBcjs.buildTocLine.call(void 0, tempNote, 0, false));
1816
- await this.writeTocLines(lines);
1817
- logger.info(`\u5728 TOC.md \u672B\u5C3E\u6DFB\u52A0\u7B14\u8BB0: ${noteIndex}`);
1818
- }
1819
- /**
1820
- * 删除前按 TOC 文档顺序获取上一项笔记索引;首项返回 null
1821
- */
1822
- async getPreviousNoteIndexBeforeDelete(noteIndex) {
1823
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) return null;
1824
- const lines = await this.readTocLines();
1825
- return _chunk3R7QSABBcjs.getPreviousTocNoteIndex.call(void 0, lines, noteIndex);
1826
- }
1827
- /**
1828
- * 从 TOC.md 删除笔记及其缩进子树
1829
- */
1830
- async deleteNoteFromToc(noteIndex) {
1831
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) return;
1832
- const lines = await this.readTocLines();
1833
- let lineIndex;
1834
- try {
1835
- lineIndex = _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, noteIndex);
1836
- } catch (e3) {
1837
- logger.warn(`TOC.md \u4E2D\u672A\u627E\u5230\u7B14\u8BB0: ${noteIndex}`);
1838
- return;
1839
- }
1840
- const { start, end } = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, lineIndex);
1841
- lines.splice(start, end - start);
1842
- await this.writeTocLines(lines);
1843
- logger.info(`\u4ECE TOC.md \u5220\u9664\u7B14\u8BB0\u53CA\u5B50\u6811: ${noteIndex} (${end - start} \u884C)`);
1844
- }
1845
- /**
1846
- * 重命名 TOC.md 中的笔记行(保留位置与缩进)
1847
- */
1848
- async renameNoteInToc(noteIndex, newDirName) {
1849
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) return;
1850
- const item = this.noteIndexCache.getByNoteIndex(noteIndex);
1851
- const notes = this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes();
1852
- const note = _chunk3R7QSABBcjs.resolveNoteFromIndex.call(void 0, noteIndex, notes);
1853
- if (!note) return;
1854
- const tempNote = {
1855
- ...note,
1856
- dirName: newDirName,
1857
- config: _nullishCoalesce(_optionalChain([item, 'optionalAccess', _14 => _14.noteConfig]), () => ( note.config))
1858
- };
1859
- const lines = await this.readTocLines();
1860
- let updated = false;
1861
- for (let i = 0; i < lines.length; i++) {
1862
- const parsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[i]);
1863
- if (parsed.noteIndex === noteIndex) {
1864
- lines[i] = _chunk3R7QSABBcjs.buildTocLine.call(void 0,
1865
- tempNote,
1866
- parsed.indentLevel,
1867
- parsed.completed
1868
- );
1869
- updated = true;
1870
- }
1871
- }
1872
- if (updated) {
1873
- await this.writeTocLines(lines);
1874
- logger.info(`TOC.md \u91CD\u547D\u540D\u7B14\u8BB0: ${noteIndex} -> ${newDirName}`);
1875
- }
1876
- }
1877
- /**
1878
- * 移动笔记(含子树)到目标位置
1879
- */
1880
- async moveNoteInToc(noteIndex, target) {
1881
- const lines = await this.readTocLines();
1882
- const sourceIndex = _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, noteIndex);
1883
- await this.moveTocEntryByLineIndex(sourceIndex, target);
1884
- }
1885
- /**
1886
- * 移动任意 TOC 行(分组或笔记)及其子树
1887
- */
1888
- async moveTocEntryByLineIndex(sourceTocLineIndex, target) {
1889
- const lines = await this.readTocLines();
1890
- const sourceParsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[sourceTocLineIndex]);
1891
- if (!sourceParsed.isMatch) {
1892
- throw new Error(`\u65E0\u6548\u7684 TOC \u884C\u7D22\u5F15: ${sourceTocLineIndex}`);
1893
- }
1894
- const { start, end } = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, sourceTocLineIndex);
1895
- const resolvedTargetIndex = this.resolveMoveTargetLineIndex(lines, target);
1896
- if (resolvedTargetIndex >= start && resolvedTargetIndex < end) {
1897
- throw new Error("\u4E0D\u80FD\u79FB\u52A8\u5230\u81EA\u8EAB\u6216\u5B50\u6811\u5185");
1898
- }
1899
- const movingLines = lines.splice(start, end - start);
1900
- if (movingLines.length === 0) {
1901
- throw new Error(`\u65E0\u6CD5\u79FB\u52A8\u7A7A\u5B50\u6811: \u884C ${sourceTocLineIndex}`);
1902
- }
1903
- const targetLineIndex = _chunk3R7QSABBcjs.adjustTocLineIndexAfterSubtreeRemoval.call(void 0,
1904
- resolvedTargetIndex,
1905
- start,
1906
- end
1907
- );
1908
- const targetParsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[targetLineIndex]);
1909
- if (!targetParsed.isMatch) {
1910
- throw new Error(`\u65E0\u6548\u7684\u76EE\u6807 TOC \u884C\u7D22\u5F15: ${targetLineIndex}`);
1911
- }
1912
- let insertIndex;
1913
- let newIndent;
1914
- if (target.placement === "inside") {
1915
- insertIndex = targetLineIndex + 1;
1916
- newIndent = targetParsed.indentLevel + 1;
1917
- } else if (target.placement === "before") {
1918
- insertIndex = targetLineIndex;
1919
- newIndent = targetParsed.indentLevel;
1920
- } else {
1921
- insertIndex = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, targetLineIndex).end;
1922
- newIndent = targetParsed.indentLevel;
1923
- }
1924
- const oldBaseIndent = _chunk3R7QSABBcjs.parseTocLine.call(void 0, movingLines[0]).indentLevel;
1925
- const indentDelta = newIndent - oldBaseIndent;
1926
- const adjustedLines = this.adjustSubtreeIndent(movingLines, indentDelta);
1927
- const maxDepth = this.getSidebarMaxDepth();
1928
- if (maxDepth > 0) {
1929
- const maxIndent = this.getMaxSubtreeIndent(adjustedLines);
1930
- const maxAllowedIndent = maxDepth - 1;
1931
- if (maxIndent > maxAllowedIndent) {
1932
- throw new Error(`\u79FB\u52A8\u540E\u5C06\u8D85\u51FA\u76EE\u5F55\u6700\u5927\u5C42\u7EA7\uFF08${maxDepth} \u5C42\uFF09`);
1933
- }
1934
- }
1935
- lines.splice(insertIndex, 0, ...adjustedLines);
1936
- await this.writeTocLines(lines);
1937
- logger.info(
1938
- `TOC.md \u79FB\u52A8\u884C ${sourceTocLineIndex} -> \u884C ${targetLineIndex} (${target.placement})`
1939
- );
1940
- }
1941
- /**
1942
- * 语雀式移动:成为 target 的下一同级
1943
- */
1944
- async moveAfterByNodeId(sourceNodeId, targetNodeId) {
1945
- const lines = await this.readTocLines();
1946
- const sourceIndex = this.resolveNodeIdToTocLineIndex(lines, sourceNodeId);
1947
- const targetIndex = this.resolveNodeIdToTocLineIndex(lines, targetNodeId);
1948
- await this.moveTocEntryByLineIndex(sourceIndex, {
1949
- targetTocLineIndex: targetIndex,
1950
- placement: "after"
1951
- });
1952
- }
1953
- /**
1954
- * 语雀式:prepend 到侧栏列表顶(无 target_uuid,book 即容器)
1955
- */
1956
- async prependToRootByNodeId(sourceNodeId) {
1957
- const lines = await this.readTocLines();
1958
- const sourceIndex = this.resolveNodeIdToTocLineIndex(lines, sourceNodeId);
1959
- let targetLineIndex = 0;
1960
- while (targetLineIndex < lines.length && !_chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[targetLineIndex]).isMatch) {
1961
- targetLineIndex++;
1962
- }
1963
- if (targetLineIndex >= lines.length) {
1964
- throw new Error("TOC.md \u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u79FB\u52A8\u5230\u9876\u90E8");
1965
- }
1966
- await this.moveTocEntryByLineIndex(sourceIndex, {
1967
- targetTocLineIndex: targetLineIndex,
1968
- placement: "before"
1969
- });
1970
- }
1971
- /**
1972
- * 语雀式移动:成为 target 的第一个子项
1973
- */
1974
- async prependChildByNodeId(sourceNodeId, targetNodeId) {
1975
- const lines = await this.readTocLines();
1976
- const sourceIndex = this.resolveNodeIdToTocLineIndex(lines, sourceNodeId);
1977
- const targetIndex = this.resolveNodeIdToTocLineIndex(lines, targetNodeId);
1978
- await this.moveTocEntryByLineIndex(sourceIndex, {
1979
- targetTocLineIndex: targetIndex,
1980
- placement: "inside"
1981
- });
1982
- }
1983
- resolveNodeIdToTocLineIndex(lines, nodeId) {
1984
- const parsed = _chunk3R7QSABBcjs.parseNodeId.call(void 0, nodeId);
1985
- if (parsed.kind === "note" && parsed.noteIndex) {
1986
- return _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, parsed.noteIndex);
1987
- }
1988
- if (parsed.kind === "line" && parsed.tocLineIndex !== void 0) {
1989
- return parsed.tocLineIndex;
1990
- }
1991
- if (parsed.kind === "folder" && parsed.folderPath) {
1992
- return _chunk3R7QSABBcjs.findFolderLineIndex.call(void 0, lines, parsed.folderPath);
1993
- }
1994
- throw new Error(`\u65E0\u6CD5\u89E3\u6790 nodeId: ${nodeId}`);
1995
- }
1996
- /**
1997
- * 重命名 TOC 目录行
1998
- */
1999
- async renameFolderInToc(tocLineIndex, newTitle) {
2000
- const lines = await this.readTocLines();
2001
- const updated = _chunk3R7QSABBcjs.renameFolderLine.call(void 0, lines, tocLineIndex, newTitle);
2002
- await this.writeTocLines(updated);
2003
- logger.info(`TOC.md \u91CD\u547D\u540D\u76EE\u5F55\u884C ${tocLineIndex}: ${newTitle.trim()}`);
2004
- }
2005
- /**
2006
- * 删除子树前获取回退笔记(子树之前的最后一篇笔记)
2007
- */
2008
- async getPreviousNoteIndexBeforeEntryDelete(tocLineIndex) {
2009
- const lines = await this.readTocLines();
2010
- return _chunk3R7QSABBcjs.getPreviousNoteIndexOutsideSubtree.call(void 0, lines, tocLineIndex);
2011
- }
2012
- /**
2013
- * 判断 noteIndex 是否在指定 TOC 行子树内
2014
- */
2015
- async isNoteInTocEntrySubtree(tocLineIndex, noteIndex) {
2016
- const lines = await this.readTocLines();
2017
- return _chunk3R7QSABBcjs.isNoteIndexInSubtree.call(void 0, lines, tocLineIndex, noteIndex);
2018
- }
2019
- /**
2020
- * 级联删除 TOC 子树,并删除子树内所有笔记磁盘目录
2021
- */
2022
- async deleteTocEntryCascade(tocLineIndex) {
2023
- if (!_fs.existsSync.call(void 0, ROOT_TOC_PATH)) return [];
2024
- const lines = await this.readTocLines();
2025
- const noteIndexes = _chunk3R7QSABBcjs.collectNoteIndexesInSubtree.call(void 0, lines, tocLineIndex);
2026
- const { start, end } = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, tocLineIndex);
2027
- lines.splice(start, end - start);
2028
- await this.writeTocLines(lines);
2029
- const noteService = NoteService.getInstance();
2030
- for (const noteIndex of noteIndexes) {
2031
- await noteService.deleteNote(noteIndex);
2032
- }
2033
- logger.info(
2034
- `\u7EA7\u8054\u5220\u9664 TOC \u884C ${tocLineIndex}\uFF08${end - start} \u884C\uFF0C${noteIndexes.length} \u7BC7\u7B14\u8BB0\uFF09`
2035
- );
2036
- return noteIndexes;
2037
- }
2038
- /**
2039
- * 在指定 TOC 行(folder 或 note)子树末尾插入笔记
2040
- */
2041
- async insertNotesUnderTocLine(parentTocLineIndex, notesToInsert) {
2042
- if (notesToInsert.length === 0) return;
2043
- const lines = await this.readTocLines();
2044
- const parentParsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[parentTocLineIndex]);
2045
- if (!parentParsed.isMatch) {
2046
- throw new Error(`\u65E0\u6548\u7684 TOC \u884C\u7D22\u5F15: ${parentTocLineIndex}`);
2047
- }
2048
- const childIndent = parentParsed.indentLevel + 1;
2049
- const { end } = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, parentTocLineIndex);
2050
- const noteLines = notesToInsert.map(
2051
- (note) => _chunk3R7QSABBcjs.buildTocLine.call(void 0, note, childIndent, _chunk3R7QSABBcjs.getTocLineCompleted.call(void 0, note))
2052
- );
2053
- lines.splice(end, 0, ...noteLines);
2054
- await this.writeTocLines(lines);
2055
- logger.info(
2056
- `\u5728 TOC \u884C ${parentTocLineIndex} \u4E0B\u63D2\u5165 ${notesToInsert.length} \u7BC7\u7B14\u8BB0`
2057
- );
2058
- }
2059
- /**
2060
- * 在指定 TOC 行(folder 或 note)子树末尾插入子目录
2061
- */
2062
- async insertFolderUnderParent(parentTocLineIndex, title) {
2063
- const trimmed = title.trim();
2064
- if (!trimmed) {
2065
- throw new Error("\u76EE\u5F55\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A");
2066
- }
2067
- const lines = await this.readTocLines();
2068
- const parentParsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[parentTocLineIndex]);
2069
- if (!parentParsed.isMatch) {
2070
- throw new Error(`\u65E0\u6548\u7684 TOC \u884C\u7D22\u5F15: ${parentTocLineIndex}`);
2071
- }
2072
- const childIndent = parentParsed.indentLevel + 1;
2073
- const { end } = _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, parentTocLineIndex);
2074
- lines.splice(end, 0, _chunk3R7QSABBcjs.buildFolderTocLine.call(void 0, trimmed, childIndent));
2075
- await this.writeTocLines(lines);
2076
- logger.info(`\u5728 TOC \u884C ${parentTocLineIndex} \u4E0B\u63D2\u5165\u76EE\u5F55: ${trimmed}`);
2077
- }
2078
- /**
2079
- * 在指定笔记上方或下方插入笔记
2080
- */
2081
- async insertNotesAroundNote(targetNoteIndex, notesToInsert, placement) {
2082
- if (notesToInsert.length === 0) return;
2083
- const lines = await this.readTocLines();
2084
- const targetIndex = _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, targetNoteIndex);
2085
- const targetParsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, lines[targetIndex]);
2086
- const indent = targetParsed.indentLevel;
2087
- const noteLines = notesToInsert.map(
2088
- (note) => _chunk3R7QSABBcjs.buildTocLine.call(void 0, note, indent, _chunk3R7QSABBcjs.getTocLineCompleted.call(void 0, note))
2089
- );
2090
- const insertIndex = placement === "before" ? targetIndex : _chunk3R7QSABBcjs.getTocEntrySubtreeRange.call(void 0, lines, targetIndex).end;
2091
- lines.splice(insertIndex, 0, ...noteLines);
2092
- await this.writeTocLines(lines);
2093
- logger.info(
2094
- `\u5728\u7B14\u8BB0 ${targetNoteIndex} ${placement === "before" ? "\u4E0A\u65B9" : "\u4E0B\u65B9"}\u63D2\u5165 ${notesToInsert.length} \u7BC7\u7B14\u8BB0`
2095
- );
2096
- }
2097
- /**
2098
- * 在目录子树末尾插入笔记
2099
- */
2100
- async insertNotesUnderFolder(folderPath, notesToInsert) {
2101
- if (notesToInsert.length === 0) return;
2102
- const lines = await this.readTocLines();
2103
- const folderIndex = _chunk3R7QSABBcjs.findFolderLineIndex.call(void 0, lines, folderPath);
2104
- await this.insertNotesUnderTocLine(folderIndex, notesToInsert);
2105
- }
2106
- async insertNotesUnderParent(parentNoteIndex, notesToInsert) {
2107
- if (notesToInsert.length === 0) return;
2108
- const lines = await this.readTocLines();
2109
- const parentIndex = _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, parentNoteIndex);
2110
- await this.insertNotesUnderTocLine(parentIndex, notesToInsert);
2111
- }
2112
- /**
2113
- * 规范化 TOC 并刷新 sidebar
2114
- */
2115
- async refreshTocAndSidebar(notes) {
2116
- const allNotes = _nullishCoalesce(notes, () => ( (this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes())));
2117
- await this.normalizeToc(allNotes);
2118
- await this.regenerateSidebar(allNotes);
2119
- }
2120
- resolveMoveTargetLineIndex(lines, target) {
2121
- if ("targetTocLineIndex" in target) {
2122
- return target.targetTocLineIndex;
2123
- }
2124
- if (target.targetType === "folder") {
2125
- return _chunk3R7QSABBcjs.findFolderLineIndex.call(void 0, lines, target.targetFolderPath);
2126
- }
2127
- return _chunk3R7QSABBcjs.findTocLineIndex.call(void 0, lines, target.targetNoteIndex);
2128
- }
2129
- getSidebarMaxDepth() {
2130
- const config2 = this.configManager.getAll();
2131
- const depth = _nullishCoalesce(config2.sidebarMaxDepth, () => ( 0));
2132
- return depth > 0 ? depth : 0;
2133
- }
2134
- getMaxSubtreeIndent(lines) {
2135
- let max = 0;
2136
- for (const line of lines) {
2137
- const parsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, line);
2138
- if (parsed.isMatch) {
2139
- max = Math.max(max, parsed.indentLevel);
2140
- }
2141
- }
2142
- return max;
2143
- }
2144
- adjustSubtreeIndent(lines, delta) {
2145
- if (delta === 0) return lines;
2146
- return lines.map((line) => {
2147
- const parsed = _chunk3R7QSABBcjs.parseTocLine.call(void 0, line);
2148
- if (!parsed.isMatch) return line;
2149
- const newIndent = Math.max(0, parsed.indentLevel + delta);
2150
- const withoutIndent = line.trimStart();
2151
- return `${" ".repeat(newIndent * _chunk3R7QSABBcjs.TOC_INDENT_SPACES)}${withoutIndent}`;
2152
- });
2153
- }
2154
- async readTocLines() {
2155
- this.ensureTocExists();
2156
- const content = await _fs.promises.readFile(ROOT_TOC_PATH, "utf-8");
2157
- return content.split("\n");
2158
- }
2159
- async writeTocLines(lines) {
2160
- const content = _chunk3R7QSABBcjs.processTocEmptyLines.call(void 0, lines).join("\n");
2161
- await _fs.promises.writeFile(ROOT_TOC_PATH, content, "utf-8");
2162
- }
2163
- };
2164
-
2165
1292
  // services/file-watcher/service.ts
2166
1293
  var _http = require('http');
2167
1294
 
@@ -2335,6 +1462,17 @@ async function safeExecute(label, fn, logger2) {
2335
1462
  }
2336
1463
  }
2337
1464
 
1465
+ // services/reconcileToc.ts
1466
+ var workspaces = /* @__PURE__ */ new Map();
1467
+ async function reconcileTocFromFiles(repoRoot) {
1468
+ let workspace = workspaces.get(repoRoot);
1469
+ if (!workspace) {
1470
+ workspace = _chunkAGVER5MXcjs.createWorkspace.call(void 0, { rootPath: repoRoot });
1471
+ workspaces.set(repoRoot, workspace);
1472
+ }
1473
+ await workspace.toc.reconcileFromFiles();
1474
+ }
1475
+
2338
1476
  // services/file-watcher/folderChangeHandler.ts
2339
1477
  var RENAME_REVERT_DELAY_MS = 2e3;
2340
1478
  var DELETE_REINIT_DELAY_MS = 1e3;
@@ -2364,7 +1502,7 @@ var FolderChangeHandler = class {
2364
1502
  this.activeTimers.add(timer);
2365
1503
  }
2366
1504
  async handleFolderDeletion(deletedFolderName) {
2367
- const { scheduler, watchState, noteIndexCache, tocService, logger: logger2 } = this.config;
1505
+ const { scheduler, watchState, noteIndexCache, logger: logger2, notesDir } = this.config;
2368
1506
  if (scheduler.getUpdating()) return;
2369
1507
  scheduler.setUpdating(true);
2370
1508
  try {
@@ -2377,9 +1515,8 @@ var FolderChangeHandler = class {
2377
1515
  await safeExecute(
2378
1516
  `\u5220\u9664\u7B14\u8BB0 ${noteIndex}`,
2379
1517
  async () => {
2380
- await tocService.deleteNoteFromToc(noteIndex);
2381
- await tocService.regenerateSidebar();
2382
- _optionalChain([this, 'access', _15 => _15.config, 'access', _16 => _16.onNoteStructureChanged, 'optionalCall', _17 => _17(`watcher:delete:${noteIndex}`)]);
1518
+ await reconcileTocFromFiles(_path.dirname.call(void 0, notesDir));
1519
+ _optionalChain([this, 'access', _11 => _11.config, 'access', _12 => _12.onNoteStructureChanged, 'optionalCall', _13 => _13(`watcher:delete:${noteIndex}`)]);
2383
1520
  },
2384
1521
  logger2
2385
1522
  );
@@ -2414,7 +1551,7 @@ var FolderChangeHandler = class {
2414
1551
  logger2
2415
1552
  );
2416
1553
  }
2417
- _optionalChain([this, 'access', _18 => _18.config, 'access', _19 => _19.onRenameSuccess, 'optionalCall', _20 => _20({
1554
+ _optionalChain([this, 'access', _14 => _14.config, 'access', _15 => _15.onRenameSuccess, 'optionalCall', _16 => _16({
2418
1555
  oldFolder: oldName,
2419
1556
  newFolder: newName,
2420
1557
  noteIndex: newNoteIndex
@@ -2447,7 +1584,7 @@ var FolderChangeHandler = class {
2447
1584
  return { oldNoteIndex, newNoteIndex };
2448
1585
  }
2449
1586
  async handleTitleOnlyRename(noteIndex, newName) {
2450
- const { noteIndexCache, tocService, logger: logger2, notesDir } = this.config;
1587
+ const { noteIndexCache, logger: logger2, notesDir } = this.config;
2451
1588
  logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u672A\u53D8 (${noteIndex})\uFF0C\u53EA\u66F4\u65B0\u6807\u9898`);
2452
1589
  noteIndexCache.updateFolderName(noteIndex, newName);
2453
1590
  await safeExecute(
@@ -2455,9 +1592,12 @@ var FolderChangeHandler = class {
2455
1592
  () => this.rewriteNoteReadmeH1(notesDir, newName, noteIndex),
2456
1593
  logger2
2457
1594
  );
2458
- await tocService.renameNoteInToc(noteIndex, newName);
2459
- await tocService.regenerateSidebar();
2460
- _optionalChain([this, 'access', _21 => _21.config, 'access', _22 => _22.onNoteStructureChanged, 'optionalCall', _23 => _23(`watcher:rename-title:${noteIndex}`)]);
1595
+ await safeExecute(
1596
+ `\u6807\u9898\u91CD\u547D\u540D\u540C\u6B65\u76EE\u5F55 ${noteIndex}`,
1597
+ () => reconcileTocFromFiles(_path.dirname.call(void 0, notesDir)),
1598
+ logger2
1599
+ );
1600
+ _optionalChain([this, 'access', _17 => _17.config, 'access', _18 => _18.onNoteStructureChanged, 'optionalCall', _19 => _19(`watcher:rename-title:${noteIndex}`)]);
2461
1601
  logger2.success(`\u6807\u9898\u66F4\u65B0\u5B8C\u6210`);
2462
1602
  }
2463
1603
  /**
@@ -2486,22 +1626,24 @@ var FolderChangeHandler = class {
2486
1626
  );
2487
1627
  return;
2488
1628
  }
2489
- const newH1 = _chunk3R7QSABBcjs.generateNoteTitle.call(void 0, noteIndex, newTitle, REPO_NOTES_URL);
1629
+ const newH1 = _chunkAGVER5MXcjs.generateNoteTitle.call(void 0, noteIndex, newTitle, REPO_NOTES_URL);
2490
1630
  if (lines[h1Index] === newH1) return;
2491
1631
  lines[h1Index] = newH1;
2492
1632
  await _fs.promises.writeFile(readmePath, lines.join("\n"), "utf-8");
2493
1633
  }
2494
1634
  async handleIndexChangedRename(oldNoteIndex, newNoteIndex) {
2495
- const { noteService, noteIndexCache, tocService, logger: logger2 } = this.config;
1635
+ const { noteService, noteIndexCache, logger: logger2, notesDir } = this.config;
2496
1636
  logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u53D8\u66F4: ${oldNoteIndex} \u2192 ${newNoteIndex}`);
2497
- await tocService.deleteNoteFromToc(oldNoteIndex);
1637
+ await safeExecute(
1638
+ `\u7D22\u5F15\u53D8\u66F4\u540C\u6B65\u76EE\u5F55 ${oldNoteIndex}\u2192${newNoteIndex}`,
1639
+ () => reconcileTocFromFiles(_path.dirname.call(void 0, notesDir)),
1640
+ logger2
1641
+ );
2498
1642
  const newNote = noteService.getNoteByIndex(newNoteIndex);
2499
1643
  if (newNote) {
2500
1644
  noteIndexCache.delete(oldNoteIndex);
2501
1645
  noteIndexCache.add(newNote);
2502
- await tocService.appendNoteToToc(newNoteIndex);
2503
- await tocService.regenerateSidebar();
2504
- _optionalChain([this, 'access', _24 => _24.config, 'access', _25 => _25.onNoteStructureChanged, 'optionalCall', _26 => _26(
1646
+ _optionalChain([this, 'access', _20 => _20.config, 'access', _21 => _21.onNoteStructureChanged, 'optionalCall', _22 => _22(
2505
1647
  `watcher:rename-index:${oldNoteIndex}->${newNoteIndex}`
2506
1648
  )]);
2507
1649
  logger2.success(`\u7B14\u8BB0\u7D22\u5F15\u53D8\u66F4\u5904\u7406\u5B8C\u6210`);
@@ -2528,7 +1670,7 @@ var FolderChangeHandler = class {
2528
1670
  }
2529
1671
  }
2530
1672
  extractNoteIndexOrWarn(name) {
2531
- const noteIndex = _optionalChain([name, 'access', _27 => _27.match, 'call', _28 => _28(/^(\d{4})/), 'optionalAccess', _29 => _29[1]]) || null;
1673
+ const noteIndex = _optionalChain([name, 'access', _23 => _23.match, 'call', _24 => _24(/^(\d{4})/), 'optionalAccess', _25 => _25[1]]) || null;
2532
1674
  if (!noteIndex) {
2533
1675
  this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
2534
1676
  }
@@ -2608,29 +1750,21 @@ var FsWatcherAdapter = class {
2608
1750
  };
2609
1751
 
2610
1752
  // services/file-watcher/globalUpdateCoordinator.ts
1753
+
2611
1754
  var GlobalUpdateCoordinator = class {
2612
1755
  constructor(config2) {
2613
1756
  this.config = config2;
2614
1757
  }
2615
1758
  async applyConfigUpdates(changedNoteIndexes) {
2616
1759
  if (changedNoteIndexes.length === 0) return;
2617
- const { tocService, noteIndexCache, logger: logger2 } = this.config;
2618
- logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...");
2619
- for (const noteIndex of changedNoteIndexes) {
2620
- await safeExecute(
2621
- `\u589E\u91CF\u66F4\u65B0 ${noteIndex}`,
2622
- async () => {
2623
- const item = noteIndexCache.getByNoteIndex(noteIndex);
2624
- await tocService.updateNoteInToc(
2625
- noteIndex,
2626
- _optionalChain([item, 'optionalAccess', _30 => _30.noteConfig]) || {}
2627
- );
2628
- logger2.info(`\u589E\u91CF\u66F4\u65B0 TOC.md \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
2629
- },
2630
- logger2
2631
- );
2632
- }
2633
- await tocService.regenerateSidebar();
1760
+ const { logger: logger2, notesDir } = this.config;
1761
+ logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u540C\u6B65\u5168\u5C40\u6587\u4EF6...");
1762
+ await safeExecute(
1763
+ "\u914D\u7F6E\u53D8\u66F4\u540C\u6B65\u76EE\u5F55",
1764
+ () => reconcileTocFromFiles(_path.dirname.call(void 0, notesDir)),
1765
+ logger2
1766
+ );
1767
+ logger2.info(`\u5DF2\u540C\u6B65\u914D\u7F6E\u53D8\u66F4\u7684\u7B14\u8BB0: ${changedNoteIndexes.join(", ")}`);
2634
1768
  }
2635
1769
  async updateNoteReadmesOnly(events) {
2636
1770
  const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))];
@@ -2751,7 +1885,7 @@ var WatchState = class {
2751
1885
  const content = _fs.readFileSync.call(void 0, filePath, "utf-8");
2752
1886
  if (content.length === 0) return null;
2753
1887
  return _crypto.createHash.call(void 0, "md5").update(content).digest("hex");
2754
- } catch (e4) {
1888
+ } catch (e3) {
2755
1889
  return null;
2756
1890
  }
2757
1891
  }
@@ -2887,6 +2021,257 @@ var WatchState = class {
2887
2021
  }
2888
2022
  };
2889
2023
 
2024
+ // services/note/service.ts
2025
+
2026
+
2027
+ var _uuid = require('uuid');
2028
+ var NoteService = class _NoteService {
2029
+ constructor() {
2030
+ this.ignoredConfigPaths = /* @__PURE__ */ new Set();
2031
+ this.noteManager = NoteManager.getInstance();
2032
+ this.noteIndexCache = NoteIndexCache.getInstance();
2033
+ }
2034
+ static getInstance() {
2035
+ if (!_NoteService.instance) {
2036
+ _NoteService.instance = new _NoteService();
2037
+ }
2038
+ return _NoteService.instance;
2039
+ }
2040
+ /**
2041
+ * 标记配置文件在下次变更时被忽略(防止 API 写入触发文件监听循环)
2042
+ * @param configPath - 配置文件路径
2043
+ */
2044
+ ignoreNextConfigChange(configPath) {
2045
+ this.ignoredConfigPaths.add(configPath);
2046
+ }
2047
+ /**
2048
+ * 检查配置文件是否应该被忽略
2049
+ * @param configPath - 配置文件路径
2050
+ * @returns 是否应该忽略
2051
+ */
2052
+ shouldIgnoreConfigChange(configPath) {
2053
+ if (this.ignoredConfigPaths.has(configPath)) {
2054
+ this.ignoredConfigPaths.delete(configPath);
2055
+ return true;
2056
+ }
2057
+ return false;
2058
+ }
2059
+ /**
2060
+ * 获取所有笔记
2061
+ * dev 模式下(缓存已初始化)从内存读取,其他模式回退到文件扫描
2062
+ * @returns 笔记信息数组
2063
+ */
2064
+ getAllNotes() {
2065
+ if (this.noteIndexCache.isInitialized()) {
2066
+ return this.noteIndexCache.toNoteInfoList();
2067
+ }
2068
+ return this.noteManager.scanNotes();
2069
+ }
2070
+ /**
2071
+ * 获取笔记(通过索引)
2072
+ * @param noteIndex - 笔记索引(文件夹前 4 位数字)
2073
+ * @returns 笔记信息,未找到时返回 undefined
2074
+ */
2075
+ getNoteByIndex(noteIndex) {
2076
+ return this.noteManager.getNoteByIndex(noteIndex);
2077
+ }
2078
+ /**
2079
+ * 创建新笔记
2080
+ * @param options - 创建选项
2081
+ * @returns 新创建的笔记信息
2082
+ */
2083
+ async createNote(options = {}) {
2084
+ const {
2085
+ title = "new",
2086
+ category,
2087
+ enableDiscussions = false,
2088
+ configId,
2089
+ usedIndexes
2090
+ } = options;
2091
+ const noteIndex = this.generateNextNoteIndex(usedIndexes);
2092
+ const dirName = `${noteIndex}. ${title}`;
2093
+ const notePath = _path.join.call(void 0, NOTES_PATH, dirName);
2094
+ await ensureDirectory(notePath);
2095
+ const readmePath = _path.join.call(void 0, notePath, "README.md");
2096
+ const noteTitle = _chunkAGVER5MXcjs.generateNoteTitle.call(void 0, noteIndex, title, REPO_NOTES_URL);
2097
+ const readmeContent = noteTitle + "\n" + _chunkAGVER5MXcjs.getNewNoteReadmeBody.call(void 0, );
2098
+ _fs.writeFileSync.call(void 0, readmePath, readmeContent, "utf-8");
2099
+ const configPath = _path.join.call(void 0, notePath, ".tnotes.json");
2100
+ const config2 = {
2101
+ id: configId || _uuid.v4.call(void 0, ),
2102
+ // 配置 ID 使用 UUID(跨知识库唯一)
2103
+ bilibili: [],
2104
+ tnotes: [],
2105
+ yuque: [],
2106
+ done: false,
2107
+ category,
2108
+ enableDiscussions
2109
+ };
2110
+ this.noteManager.writeNoteConfig(configPath, config2);
2111
+ const noteInfo = {
2112
+ index: noteIndex,
2113
+ // 返回的 id 是笔记索引(目录前缀)
2114
+ path: notePath,
2115
+ dirName,
2116
+ readmePath,
2117
+ configPath,
2118
+ config: config2
2119
+ };
2120
+ if (this.noteIndexCache.isInitialized()) {
2121
+ this.noteIndexCache.add(noteInfo);
2122
+ }
2123
+ logger.info(`Created new note: ${dirName}`);
2124
+ return noteInfo;
2125
+ }
2126
+ /**
2127
+ * 删除笔记文件夹
2128
+ * @param noteIndex - 笔记索引
2129
+ * @returns 被删除的笔记信息
2130
+ */
2131
+ async deleteNote(noteIndex) {
2132
+ const note = this.getNoteByIndex(noteIndex);
2133
+ if (!note) {
2134
+ throw new Error(`\u7B14\u8BB0\u672A\u627E\u5230: ${noteIndex}`);
2135
+ }
2136
+ await _fs.promises.rm(note.path, { recursive: true, force: true });
2137
+ if (this.noteIndexCache.isInitialized()) {
2138
+ this.noteIndexCache.delete(noteIndex);
2139
+ }
2140
+ logger.info(`Deleted note: ${note.dirName}`);
2141
+ return note;
2142
+ }
2143
+ /**
2144
+ * 生成下一个笔记索引(填充空缺)
2145
+ * @param usedIndexes - 可选的已使用编号集合,不传则内部扫描
2146
+ * @returns 新的笔记索引(4位数字字符串,从 0001 到 9999)
2147
+ */
2148
+ generateNextNoteIndex(usedIndexes) {
2149
+ if (!usedIndexes) {
2150
+ const notes = this.getAllNotes();
2151
+ usedIndexes = /* @__PURE__ */ new Set();
2152
+ for (const note of notes) {
2153
+ const id = parseInt(note.index, 10);
2154
+ if (!isNaN(id) && id >= 1 && id <= 9999) {
2155
+ usedIndexes.add(id);
2156
+ }
2157
+ }
2158
+ }
2159
+ if (usedIndexes.size === 0) {
2160
+ return "0001";
2161
+ }
2162
+ for (let i = 1; i <= 9999; i++) {
2163
+ if (!usedIndexes.has(i)) {
2164
+ return i.toString().padStart(CONSTANTS.NOTE_INDEX_LENGTH, "0");
2165
+ }
2166
+ }
2167
+ throw new Error("\u6240\u6709\u7B14\u8BB0\u7F16\u53F7 (0001-9999) \u5DF2\u88AB\u5360\u7528\uFF0C\u65E0\u6CD5\u521B\u5EFA\u65B0\u7B14\u8BB0");
2168
+ }
2169
+ /**
2170
+ * 更新笔记配置
2171
+ * @param noteIndex - 笔记索引
2172
+ * @param updates - 配置更新
2173
+ */
2174
+ async updateNoteConfig(noteIndex, updates) {
2175
+ const note = this.getNoteByIndex(noteIndex);
2176
+ if (!note || !note.config) {
2177
+ throw new Error(`Note not found or no config: ${noteIndex}`);
2178
+ }
2179
+ const oldConfig = { ...note.config };
2180
+ const updatedConfig = {
2181
+ ...note.config,
2182
+ ...updates
2183
+ };
2184
+ this.ignoreNextConfigChange(note.configPath);
2185
+ this.noteManager.updateNoteConfig(note, updatedConfig);
2186
+ this.noteIndexCache.updateConfig(noteIndex, updatedConfig);
2187
+ const needsGlobalUpdate = this.checkNeedsGlobalUpdate(
2188
+ oldConfig,
2189
+ updatedConfig
2190
+ );
2191
+ if (needsGlobalUpdate) {
2192
+ logger.info(`\u68C0\u6D4B\u5230\u5168\u5C40\u5B57\u6BB5\u53D8\u66F4 (${noteIndex})\uFF0C\u6B63\u5728\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...`);
2193
+ await reconcileTocFromFiles(ROOT_DIR_PATH);
2194
+ logger.info(`\u5168\u5C40\u6587\u4EF6\u589E\u91CF\u66F4\u65B0\u5B8C\u6210 (${noteIndex})`);
2195
+ } else {
2196
+ logger.debug(`\u914D\u7F6E\u66F4\u65B0\u4E0D\u5F71\u54CD\u5168\u5C40\u6587\u4EF6 (${noteIndex})`);
2197
+ }
2198
+ }
2199
+ /**
2200
+ * 检查配置更新是否需要触发全局更新
2201
+ * @param oldConfig - 旧配置
2202
+ * @param newConfig - 新配置
2203
+ * @returns 是否需要全局更新
2204
+ */
2205
+ checkNeedsGlobalUpdate(oldConfig, newConfig) {
2206
+ const globalFields = ["done"];
2207
+ for (const field of globalFields) {
2208
+ if (oldConfig[field] !== newConfig[field]) {
2209
+ return true;
2210
+ }
2211
+ }
2212
+ return false;
2213
+ }
2214
+ /**
2215
+ * 修正笔记标题
2216
+ * @param noteInfo - 笔记信息
2217
+ * @returns 是否进行了修正
2218
+ */
2219
+ async fixNoteTitle(noteInfo) {
2220
+ try {
2221
+ const readmeContent = _fs.readFileSync.call(void 0, noteInfo.readmePath, "utf-8");
2222
+ if (readmeContent.length === 0) return false;
2223
+ const lines = readmeContent.split("\n");
2224
+ const match = noteInfo.dirName.match(/^\d{4}\.\s+(.+)$/);
2225
+ if (!match) {
2226
+ logger.warn(`\u68C0\u6D4B\u5230\u9519\u8BEF\u7684\u7B14\u8BB0\u76EE\u5F55\u540D\u79F0\uFF1A${noteInfo.dirName}`);
2227
+ return false;
2228
+ }
2229
+ const expectedTitle = match[1];
2230
+ const expectedH1 = _chunkAGVER5MXcjs.generateNoteTitle.call(void 0,
2231
+ noteInfo.index,
2232
+ expectedTitle,
2233
+ REPO_NOTES_URL
2234
+ );
2235
+ const firstLine = lines[0].trim();
2236
+ if (!firstLine.startsWith("# ")) {
2237
+ lines.unshift(expectedH1);
2238
+ _fs.writeFileSync.call(void 0, noteInfo.readmePath, lines.join("\n"), "utf-8");
2239
+ logger.info(`Added title to: ${noteInfo.dirName}`);
2240
+ return true;
2241
+ }
2242
+ if (firstLine !== expectedH1) {
2243
+ lines[0] = expectedH1;
2244
+ _fs.writeFileSync.call(void 0, noteInfo.readmePath, lines.join("\n"), "utf-8");
2245
+ logger.info(`Fixed title for: ${noteInfo.dirName}`);
2246
+ return true;
2247
+ }
2248
+ return false;
2249
+ } catch (error) {
2250
+ logger.error(`Failed to fix title for: ${noteInfo.dirName}`, error);
2251
+ return false;
2252
+ }
2253
+ }
2254
+ /**
2255
+ * 修正所有笔记的标题
2256
+ * @param providedNotes - 可选的笔记列表,不传则内部扫描
2257
+ * @returns 修正的笔记数量
2258
+ */
2259
+ async fixAllNoteTitles(providedNotes) {
2260
+ const notes = _nullishCoalesce(providedNotes, () => ( this.getAllNotes()));
2261
+ let fixedCount = 0;
2262
+ for (const note of notes) {
2263
+ const fixed = await this.fixNoteTitle(note);
2264
+ if (fixed) {
2265
+ fixedCount++;
2266
+ }
2267
+ }
2268
+ if (fixedCount > 0) {
2269
+ logger.info(`Fixed ${fixedCount} note titles`);
2270
+ }
2271
+ return fixedCount;
2272
+ }
2273
+ };
2274
+
2890
2275
  // services/readme/service.ts
2891
2276
 
2892
2277
 
@@ -3190,7 +2575,6 @@ var ReadmeService = class _ReadmeService {
3190
2575
  this.readmeGenerator = new ReadmeGenerator();
3191
2576
  this.configManager = _chunkMZIXIY2Ycjs.ConfigManager.getInstance();
3192
2577
  this.noteIndexCache = NoteIndexCache.getInstance();
3193
- this.tocService = TocService.getInstance();
3194
2578
  }
3195
2579
  static getInstance() {
3196
2580
  if (!_ReadmeService.instance) {
@@ -3230,8 +2614,7 @@ var ReadmeService = class _ReadmeService {
3230
2614
  await this.updateHomeReadme(notes);
3231
2615
  }
3232
2616
  if (updateSidebar) {
3233
- await this.tocService.normalizeToc(notes);
3234
- await this.tocService.regenerateSidebar(notes);
2617
+ await reconcileTocFromFiles(ROOT_DIR_PATH);
3235
2618
  }
3236
2619
  logger.info("\u77E5\u8BC6\u5E93\u66F4\u65B0\u5B8C\u6210\uFF01");
3237
2620
  }
@@ -3342,7 +2725,7 @@ var ReadmeService = class _ReadmeService {
3342
2725
  continue;
3343
2726
  }
3344
2727
  let statusEmoji = "\u23F0 ";
3345
- if (_optionalChain([note, 'optionalAccess', _31 => _31.config])) {
2728
+ if (_optionalChain([note, 'optionalAccess', _26 => _26.config])) {
3346
2729
  if (note.config.done) {
3347
2730
  statusEmoji = "\u2705 ";
3348
2731
  }
@@ -3604,17 +2987,15 @@ var ReadmeService = class _ReadmeService {
3604
2987
  /**
3605
2988
  * 更新 TOC.md 和 sidebar,不重写每篇笔记 README
3606
2989
  */
3607
- async refreshHomeReadmeAndSidebar(notes) {
3608
- const allNotes = _nullishCoalesce(notes, () => ( (this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes())));
3609
- await this.tocService.normalizeToc(allNotes);
3610
- await this.tocService.regenerateSidebar(allNotes);
2990
+ async refreshHomeReadmeAndSidebar(_notes) {
2991
+ await reconcileTocFromFiles(ROOT_DIR_PATH);
3611
2992
  }
3612
2993
  /**
3613
2994
  * 重新生成 sidebar.json(基于当前 TOC.md)
3614
2995
  * @param notes - 可选的笔记列表,不传则内部扫描
3615
2996
  */
3616
- async regenerateSidebar(notes) {
3617
- await this.tocService.regenerateSidebar(notes);
2997
+ async regenerateSidebar(_notes) {
2998
+ await reconcileTocFromFiles(ROOT_DIR_PATH);
3618
2999
  logger.info("\u91CD\u65B0\u751F\u6210 sidebar.json");
3619
3000
  }
3620
3001
  async readHomeReadmeLines() {
@@ -3731,7 +3112,6 @@ var FileWatcherService = class _FileWatcherService {
3731
3112
  init() {
3732
3113
  this.noteService = NoteService.getInstance();
3733
3114
  this.readmeService = ReadmeService.getInstance();
3734
- this.tocService = TocService.getInstance();
3735
3115
  this.noteIndexCache = NoteIndexCache.getInstance();
3736
3116
  this.watchState = this.initWatchState();
3737
3117
  this.scheduler = this.initScheduler();
@@ -3762,7 +3142,6 @@ var FileWatcherService = class _FileWatcherService {
3762
3142
  scheduler: this.scheduler,
3763
3143
  noteService: this.noteService,
3764
3144
  readmeService: this.readmeService,
3765
- tocService: this.tocService,
3766
3145
  noteIndexCache: this.noteIndexCache,
3767
3146
  logger,
3768
3147
  onRenameSuccess: (payload) => {
@@ -3864,9 +3243,8 @@ var FileWatcherService = class _FileWatcherService {
3864
3243
  }
3865
3244
  initCoordinator() {
3866
3245
  return new GlobalUpdateCoordinator({
3246
+ notesDir: this.notesDir,
3867
3247
  readmeService: this.readmeService,
3868
- tocService: this.tocService,
3869
- noteIndexCache: this.noteIndexCache,
3870
3248
  logger
3871
3249
  });
3872
3250
  }
@@ -3974,7 +3352,7 @@ var FileWatcherService = class _FileWatcherService {
3974
3352
  var GitManager = class {
3975
3353
  constructor(dir, logger2) {
3976
3354
  this.dir = dir;
3977
- this.logger = _optionalChain([logger2, 'optionalAccess', _32 => _32.child, 'call', _33 => _33("git")]) || new Logger({ prefix: "git" });
3355
+ this.logger = _optionalChain([logger2, 'optionalAccess', _27 => _27.child, 'call', _28 => _28("git")]) || new Logger({ prefix: "git" });
3978
3356
  }
3979
3357
  /**
3980
3358
  * 检查是否为有效的 Git 仓库
@@ -3986,7 +3364,7 @@ var GitManager = class {
3986
3364
  this.dir
3987
3365
  );
3988
3366
  return result.trim() === "true";
3989
- } catch (e5) {
3367
+ } catch (e4) {
3990
3368
  return false;
3991
3369
  }
3992
3370
  }
@@ -4036,7 +3414,7 @@ var GitManager = class {
4036
3414
  const [behindStr, aheadStr] = aheadBehind.trim().split(" ");
4037
3415
  behind = parseInt(behindStr) || 0;
4038
3416
  ahead = parseInt(aheadStr) || 0;
4039
- } catch (e6) {
3417
+ } catch (e5) {
4040
3418
  }
4041
3419
  return {
4042
3420
  hasChanges: lines.length > 0,
@@ -4083,7 +3461,7 @@ var GitManager = class {
4083
3461
  };
4084
3462
  }
4085
3463
  return { url, type: "unknown" };
4086
- } catch (e7) {
3464
+ } catch (e6) {
4087
3465
  return null;
4088
3466
  }
4089
3467
  }
@@ -4155,7 +3533,7 @@ var GitManager = class {
4155
3533
  });
4156
3534
  }
4157
3535
  this.logger.success(`\u62C9\u53D6\u6210\u529F: ${changedFiles.length} \u4E2A\u6587\u4EF6\u5DF2\u66F4\u65B0`);
4158
- } catch (e8) {
3536
+ } catch (e7) {
4159
3537
  this.logger.success("\u62C9\u53D6\u6210\u529F");
4160
3538
  }
4161
3539
  } else {
@@ -4242,7 +3620,7 @@ var GitManager = class {
4242
3620
  const message = commitMessage || `update: ${status.changedFiles} files modified`;
4243
3621
  await runCommand(`git commit -m "${message}"`, this.dir);
4244
3622
  let cmd = "git push";
4245
- if (_optionalChain([options, 'optionalAccess', _34 => _34.force])) cmd += " --force";
3623
+ if (_optionalChain([options, 'optionalAccess', _29 => _29.force])) cmd += " --force";
4246
3624
  await runCommand(cmd, this.dir);
4247
3625
  const remoteInfo = await this.getRemoteInfo();
4248
3626
  if (remoteInfo) {
@@ -4423,7 +3801,7 @@ var ProcessManager = class {
4423
3801
  if (!processInfo) return false;
4424
3802
  try {
4425
3803
  return process.kill(processInfo.pid, 0);
4426
- } catch (e9) {
3804
+ } catch (e8) {
4427
3805
  return false;
4428
3806
  }
4429
3807
  }
@@ -4620,8 +3998,8 @@ var VitepressService = class _VitepressService {
4620
3998
  }
4621
3999
  const elapsed = Date.now() - startTime;
4622
4000
  const seconds = (elapsed / 1e3).toFixed(0);
4623
- _optionalChain([process, 'access', _35 => _35.stderr, 'access', _36 => _36.clearLine, 'optionalCall', _37 => _37(0)]);
4624
- _optionalChain([process, 'access', _38 => _38.stderr, 'access', _39 => _39.cursorTo, 'optionalCall', _40 => _40(0)]);
4001
+ _optionalChain([process, 'access', _30 => _30.stderr, 'access', _31 => _31.clearLine, 'optionalCall', _32 => _32(0)]);
4002
+ _optionalChain([process, 'access', _33 => _33.stderr, 'access', _34 => _34.cursorTo, 'optionalCall', _35 => _35(0)]);
4625
4003
  process.stderr.write(`\u23F3 \u542F\u52A8\u4E2D: \u5DF2\u7528 ${seconds}s...`);
4626
4004
  }, _VitepressService.SERVER_STATUS_LINE_INTERVAL);
4627
4005
  const handleOutput = (data) => {
@@ -4633,16 +4011,16 @@ var VitepressService = class _VitepressService {
4633
4011
  if (!serverReady && (text.includes("Local:") || text.includes("http://localhost") || text.includes("\u279C") && text.includes("Local"))) {
4634
4012
  serverReady = true;
4635
4013
  clearInterval(statusTimer);
4636
- _optionalChain([process, 'access', _41 => _41.stderr, 'access', _42 => _42.clearLine, 'optionalCall', _43 => _43(0)]);
4637
- _optionalChain([process, 'access', _44 => _44.stderr, 'access', _45 => _45.cursorTo, 'optionalCall', _46 => _46(0)]);
4014
+ _optionalChain([process, 'access', _36 => _36.stderr, 'access', _37 => _37.clearLine, 'optionalCall', _38 => _38(0)]);
4015
+ _optionalChain([process, 'access', _39 => _39.stderr, 'access', _40 => _40.cursorTo, 'optionalCall', _41 => _41(0)]);
4638
4016
  const elapsed = Date.now() - startTime;
4639
4017
  setTimeout(() => resolve4({ version, elapsed }), 200);
4640
4018
  return;
4641
4019
  }
4642
4020
  if (!serverReady) {
4643
4021
  if (text.includes("error") || text.includes("Error") || text.includes("Port") && text.includes("is in use")) {
4644
- _optionalChain([process, 'access', _47 => _47.stderr, 'access', _48 => _48.clearLine, 'optionalCall', _49 => _49(0)]);
4645
- _optionalChain([process, 'access', _50 => _50.stderr, 'access', _51 => _51.cursorTo, 'optionalCall', _52 => _52(0)]);
4022
+ _optionalChain([process, 'access', _42 => _42.stderr, 'access', _43 => _43.clearLine, 'optionalCall', _44 => _44(0)]);
4023
+ _optionalChain([process, 'access', _45 => _45.stderr, 'access', _46 => _46.cursorTo, 'optionalCall', _47 => _47(0)]);
4646
4024
  process.stdout.write(data);
4647
4025
  }
4648
4026
  }
@@ -4659,8 +4037,8 @@ var VitepressService = class _VitepressService {
4659
4037
  if (!serverReady) {
4660
4038
  serverReady = true;
4661
4039
  clearInterval(statusTimer);
4662
- _optionalChain([process, 'access', _53 => _53.stderr, 'access', _54 => _54.clearLine, 'optionalCall', _55 => _55(0)]);
4663
- _optionalChain([process, 'access', _56 => _56.stderr, 'access', _57 => _57.cursorTo, 'optionalCall', _58 => _58(0)]);
4040
+ _optionalChain([process, 'access', _48 => _48.stderr, 'access', _49 => _49.clearLine, 'optionalCall', _50 => _50(0)]);
4041
+ _optionalChain([process, 'access', _51 => _51.stderr, 'access', _52 => _52.cursorTo, 'optionalCall', _53 => _53(0)]);
4664
4042
  logger.warn("\u542F\u52A8\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5 VitePress \u8F93\u51FA");
4665
4043
  resolve4({ version, elapsed: _VitepressService.SERVER_STARTUP_TIMEOUT });
4666
4044
  }
@@ -4694,8 +4072,8 @@ var VitepressService = class _VitepressService {
4694
4072
  return;
4695
4073
  }
4696
4074
  };
4697
- _optionalChain([child, 'access', _59 => _59.stdout, 'optionalAccess', _60 => _60.on, 'call', _61 => _61("data", filterOutput)]);
4698
- _optionalChain([child, 'access', _62 => _62.stderr, 'optionalAccess', _63 => _63.on, 'call', _64 => _64("data", filterOutput)]);
4075
+ _optionalChain([child, 'access', _54 => _54.stdout, 'optionalAccess', _55 => _55.on, 'call', _56 => _56("data", filterOutput)]);
4076
+ _optionalChain([child, 'access', _57 => _57.stderr, 'optionalAccess', _58 => _58.on, 'call', _59 => _59("data", filterOutput)]);
4699
4077
  child.on("error", (err) => {
4700
4078
  reject(err);
4701
4079
  });
@@ -4842,13 +4220,13 @@ function buildRepoNotesUrl(repoName2) {
4842
4220
  return `https://github.com/${GITHUB_ORG}/${repoName2}/tree/main/notes`;
4843
4221
  }
4844
4222
  function renderFirstNoteReadme(ctx) {
4845
- const noteTitle = _chunk3R7QSABBcjs.generateNoteTitle.call(void 0,
4223
+ const noteTitle = _chunkAGVER5MXcjs.generateNoteTitle.call(void 0,
4846
4224
  "0001",
4847
4225
  ctx.noteTitle,
4848
4226
  buildRepoNotesUrl(ctx.repoName)
4849
4227
  );
4850
4228
  return `${noteTitle}
4851
- ${_chunk3R7QSABBcjs.getNewNoteReadmeBody.call(void 0, )}`;
4229
+ ${_chunkAGVER5MXcjs.getNewNoteReadmeBody.call(void 0, )}`;
4852
4230
  }
4853
4231
  function renderIndexMd(ctx) {
4854
4232
  return `---
@@ -5134,7 +4512,6 @@ var RenameNoteCommand = class extends BaseCommand {
5134
4512
  constructor() {
5135
4513
  super("rename-note");
5136
4514
  this.noteService = NoteService.getInstance();
5137
- this.tocService = TocService.getInstance();
5138
4515
  }
5139
4516
  async run() {
5140
4517
  const noteIndex = process.env.NOTE_ID;
@@ -5196,7 +4573,7 @@ var RenameNoteCommand = class extends BaseCommand {
5196
4573
  }
5197
4574
  }
5198
4575
  if (h1Index !== -1) {
5199
- const newH1 = _chunk3R7QSABBcjs.generateNoteTitle.call(void 0,
4576
+ const newH1 = _chunkAGVER5MXcjs.generateNoteTitle.call(void 0,
5200
4577
  noteIndex,
5201
4578
  newTitle.trim(),
5202
4579
  REPO_NOTES_URL
@@ -5215,8 +4592,7 @@ var RenameNoteCommand = class extends BaseCommand {
5215
4592
  }
5216
4593
  try {
5217
4594
  this.logger.info("\u6B63\u5728\u66F4\u65B0 TOC.md \u548C sidebar.json...");
5218
- await this.tocService.renameNoteInToc(noteIndex, newDirName);
5219
- await this.tocService.regenerateSidebar();
4595
+ await reconcileTocFromFiles(ROOT_DIR_PATH);
5220
4596
  this.logger.success("\u2705 \u5168\u5C40\u6587\u4EF6\u5DF2\u66F4\u65B0");
5221
4597
  } catch (error) {
5222
4598
  this.logger.warn("\u26A0\uFE0F \u6587\u4EF6\u5939\u91CD\u547D\u540D\u6210\u529F,\u4F46\u66F4\u65B0\u5168\u5C40\u6587\u4EF6\u65F6\u51FA\u9519:", error);
@@ -5259,6 +4635,4 @@ var RenameNoteCommand = class extends BaseCommand {
5259
4635
 
5260
4636
 
5261
4637
 
5262
-
5263
-
5264
- exports.ROOT_DIR_PATH = ROOT_DIR_PATH; exports.ROOT_README_PATH = ROOT_README_PATH; exports.ROOT_TOC_PATH = ROOT_TOC_PATH; exports.ROOT_CONFIG_PATH = ROOT_CONFIG_PATH; exports.handleError = handleError; exports.logger = logger; exports.createLogger = createLogger; exports.parseArgs = parseArgs; exports.parseReadmeCompletedNotes = parseReadmeCompletedNotes; exports.migrateReadmeToToc = migrateReadmeToToc; exports.runCommand = runCommand; exports.NoteManager = NoteManager; exports.NoteIndexCache = NoteIndexCache; exports.TocService = TocService; exports.NoteService = NoteService; exports.ReadmeService = ReadmeService; exports.FileWatcherService = FileWatcherService; exports.GitManager = GitManager; exports.GitService = GitService; exports.VitepressService = VitepressService; exports.DEFAULT_PORT = DEFAULT_PORT; exports.DEFAULT_DESCRIPTION = DEFAULT_DESCRIPTION; exports.defaultDisplayName = defaultDisplayName; exports.validateTopic = validateTopic; exports.validatePort = validatePort; exports.buildInitContext = buildInitContext; exports.isAlreadyInitialized = isAlreadyInitialized; exports.getInitializedConfigPath = getInitializedConfigPath; exports.buildManualSteps = buildManualSteps; exports.InitSubRepoService = InitSubRepoService; exports.COMMAND_NAMES = COMMAND_NAMES; exports.COMMAND_DESCRIPTIONS = COMMAND_DESCRIPTIONS; exports.COMMAND_OPTIONS = COMMAND_OPTIONS; exports.BaseCommand = BaseCommand; exports.UpdateNoteConfigCommand = UpdateNoteConfigCommand; exports.RenameNoteCommand = RenameNoteCommand;
4638
+ exports.ROOT_DIR_PATH = ROOT_DIR_PATH; exports.ROOT_TOC_PATH = ROOT_TOC_PATH; exports.ROOT_CONFIG_PATH = ROOT_CONFIG_PATH; exports.reconcileTocFromFiles = reconcileTocFromFiles; exports.handleError = handleError; exports.logger = logger; exports.createLogger = createLogger; exports.parseArgs = parseArgs; exports.parseReadmeCompletedNotes = parseReadmeCompletedNotes; exports.runCommand = runCommand; exports.NoteManager = NoteManager; exports.NoteIndexCache = NoteIndexCache; exports.NoteService = NoteService; exports.ReadmeService = ReadmeService; exports.FileWatcherService = FileWatcherService; exports.GitManager = GitManager; exports.GitService = GitService; exports.VitepressService = VitepressService; exports.DEFAULT_PORT = DEFAULT_PORT; exports.DEFAULT_DESCRIPTION = DEFAULT_DESCRIPTION; exports.defaultDisplayName = defaultDisplayName; exports.validateTopic = validateTopic; exports.validatePort = validatePort; exports.buildInitContext = buildInitContext; exports.isAlreadyInitialized = isAlreadyInitialized; exports.getInitializedConfigPath = getInitializedConfigPath; exports.buildManualSteps = buildManualSteps; exports.InitSubRepoService = InitSubRepoService; exports.COMMAND_NAMES = COMMAND_NAMES; exports.COMMAND_DESCRIPTIONS = COMMAND_DESCRIPTIONS; exports.COMMAND_OPTIONS = COMMAND_OPTIONS; exports.BaseCommand = BaseCommand; exports.UpdateNoteConfigCommand = UpdateNoteConfigCommand; exports.RenameNoteCommand = RenameNoteCommand;