@tnotesjs/core 0.6.1 → 0.7.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.
- package/config/templates.ts +0 -5
- package/dist/{chunk-5F5RJ2HV.cjs → chunk-7FEANCT6.cjs} +16 -7
- package/dist/chunk-AGVER5MX.cjs +1746 -0
- package/dist/{chunk-TJ4527KA.cjs → chunk-BL22KD4V.cjs} +324 -950
- package/dist/{chunk-56PKXCOX.js → chunk-GWG75A5M.js} +13 -4
- package/dist/{chunk-DUTS75WQ.js → chunk-XNO3PCEH.js} +374 -1000
- package/dist/chunk-ZEQS64PE.js +1746 -0
- package/dist/cli/index.cjs +76 -155
- package/dist/cli/index.js +10 -89
- package/dist/markdown/index.cjs +2 -3
- package/dist/markdown/index.d.cts +1 -1
- package/dist/markdown/index.d.ts +1 -1
- package/dist/markdown/index.js +1 -2
- package/dist/{types-CwBWwNVv.d.ts → types-C_d0fFeD.d.ts} +8 -1
- package/dist/{types-CpsEy8lA.d.cts → types-MRBGgJTX.d.cts} +8 -1
- package/dist/vitepress/config/index.cjs +200 -132
- package/dist/vitepress/config/index.js +187 -119
- package/dist/workspace/index.cjs +4 -1202
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.ts +2 -2
- package/dist/workspace/index.js +4 -1202
- package/package.json +1 -1
- package/services/file-watcher/folderChangeHandler.ts +19 -15
- package/services/file-watcher/globalUpdateCoordinator.ts +21 -27
- package/services/file-watcher/service.ts +1 -6
- package/services/index.ts +0 -1
- package/services/note/service.ts +4 -6
- package/services/readme/service.ts +11 -18
- package/services/reconcileToc.ts +20 -0
- package/utils/index.ts +0 -9
- package/{services/toc → utils}/moveTocInside.test.ts +1 -1
- package/vitepress/components/constants.ts +2 -2
- package/vitepress/components/sidebar.data.ts +8 -5
- package/vitepress/plugins/sidebarStructurePlugin.ts +242 -145
- package/dist/chunk-3R7QSABB.cjs +0 -502
- package/dist/chunk-CZXRQPFJ.js +0 -11
- package/dist/chunk-HXED7KVT.cjs +0 -11
- package/dist/chunk-U6IBLREL.js +0 -502
- package/services/toc/index.ts +0 -5
- package/services/toc/service.ts +0 -759
- package/utils/migrateReadmeToToc.test.ts +0 -111
- package/utils/migrateReadmeToToc.ts +0 -135
|
@@ -4,32 +4,13 @@ import {
|
|
|
4
4
|
getDefaultConfig
|
|
5
5
|
} from "./chunk-UJG3UU5X.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
adjustTocLineIndexAfterSubtreeRemoval,
|
|
9
|
-
buildFolderTocLine,
|
|
10
|
-
buildSidebarFromTocTree,
|
|
11
|
-
buildTocLine,
|
|
12
|
-
collectNoteIndexesInSubtree,
|
|
13
|
-
findFolderLineIndex,
|
|
14
|
-
findTocLineIndex,
|
|
7
|
+
createWorkspace,
|
|
15
8
|
generateNoteTitle,
|
|
16
|
-
getNewNoteReadmeBody
|
|
17
|
-
|
|
18
|
-
getPreviousTocNoteIndex,
|
|
19
|
-
getTocEntrySubtreeRange,
|
|
20
|
-
getTocLineCompleted,
|
|
21
|
-
isNoteIndexInSubtree,
|
|
22
|
-
parseNodeId,
|
|
23
|
-
parseTocLine,
|
|
24
|
-
parseTocToTree,
|
|
25
|
-
processTocEmptyLines,
|
|
26
|
-
renameFolderLine,
|
|
27
|
-
resolveNoteFromIndex,
|
|
28
|
-
serializeTocTree
|
|
29
|
-
} from "./chunk-U6IBLREL.js";
|
|
9
|
+
getNewNoteReadmeBody
|
|
10
|
+
} from "./chunk-ZEQS64PE.js";
|
|
30
11
|
import {
|
|
31
12
|
generateAnchor
|
|
32
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GWG75A5M.js";
|
|
33
14
|
|
|
34
15
|
// utils/errorHandler.ts
|
|
35
16
|
var TNotesError = class _TNotesError extends Error {
|
|
@@ -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 = parseTocLine(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
|
import { execSync as execSync2 } from "child_process";
|
|
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
|
-
import {
|
|
1390
|
-
existsSync as existsSync2,
|
|
1391
|
-
readFileSync as readFileSync3,
|
|
1392
|
-
writeFileSync as writeFileSync3,
|
|
1393
|
-
promises as fsPromises2
|
|
1394
|
-
} from "fs";
|
|
1395
|
-
|
|
1396
|
-
// services/note/service.ts
|
|
1397
|
-
import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, promises as fsPromises } from "fs";
|
|
1398
|
-
import { join as join3 } from "path";
|
|
1399
|
-
import { v4 as uuidv4 } from "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 = join3(NOTES_PATH, dirName);
|
|
1466
|
-
await ensureDirectory(notePath);
|
|
1467
|
-
const readmePath = join3(notePath, "README.md");
|
|
1468
|
-
const noteTitle = generateNoteTitle(noteIndex, title, REPO_NOTES_URL);
|
|
1469
|
-
const readmeContent = noteTitle + "\n" + getNewNoteReadmeBody();
|
|
1470
|
-
writeFileSync2(readmePath, readmeContent, "utf-8");
|
|
1471
|
-
const configPath = join3(notePath, ".tnotes.json");
|
|
1472
|
-
const config2 = {
|
|
1473
|
-
id: configId || uuidv4(),
|
|
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 fsPromises.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 = readFileSync2(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 = generateNoteTitle(
|
|
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
|
-
writeFileSync2(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
|
-
writeFileSync2(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 = 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 = 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 (!existsSync2(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 fsPromises2.readFile(ROOT_TOC_PATH, "utf-8");
|
|
1678
|
-
const lines = content.split("\n");
|
|
1679
|
-
const tree = parseTocToTree(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: getTocLineCompleted(note)
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
const normalizedLines = serializeTocTree(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 = notes ?? (this.noteIndexCache.isInitialized() ? this.noteIndexCache.toNoteInfoList() : this.noteManager.scanNotes());
|
|
1720
|
-
const content = readFileSync3(ROOT_TOC_PATH, "utf-8");
|
|
1721
|
-
const lines = content.split("\n");
|
|
1722
|
-
const tree = parseTocToTree(lines, allNotes);
|
|
1723
|
-
const sidebarShowNoteId2 = this.configManager.get("sidebarShowNoteId");
|
|
1724
|
-
const hierarchicalSidebar = buildSidebarFromTocTree(tree, allNotes, {
|
|
1725
|
-
sidebarShowNoteId: sidebarShowNoteId2,
|
|
1726
|
-
sidebarIsCollapsed: true
|
|
1727
|
-
});
|
|
1728
|
-
const sidebarContent = JSON.stringify(hierarchicalSidebar, null, 2);
|
|
1729
|
-
const existingContent = existsSync2(VP_SIDEBAR_PATH) ? readFileSync3(VP_SIDEBAR_PATH, "utf-8") : "";
|
|
1730
|
-
if (sidebarContent !== existingContent) {
|
|
1731
|
-
writeFileSync3(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 = resolveNoteFromIndex(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 = parseTocLine(lines[i]);
|
|
1762
|
-
if (parsed.noteIndex === noteIndex) {
|
|
1763
|
-
lines[i] = buildTocLine(
|
|
1764
|
-
tempNote,
|
|
1765
|
-
parsed.indentLevel,
|
|
1766
|
-
getTocLineCompleted(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 = resolveNoteFromIndex(noteIndex, notes);
|
|
1789
|
-
if (!note) return;
|
|
1790
|
-
const tempNote = {
|
|
1791
|
-
...note,
|
|
1792
|
-
dirName: item.folderName,
|
|
1793
|
-
config: item.noteConfig
|
|
1794
|
-
};
|
|
1795
|
-
if (!existsSync2(ROOT_TOC_PATH)) {
|
|
1796
|
-
await this.writeTocLines([buildTocLine(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 (options?.parentTocLineIndex !== void 0) {
|
|
1802
|
-
await this.insertNotesUnderTocLine(options.parentTocLineIndex, [tempNote]);
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
if (options?.parentNoteIndex) {
|
|
1806
|
-
const parentIndex = findTocLineIndex(lines, options.parentNoteIndex);
|
|
1807
|
-
await this.insertNotesUnderTocLine(parentIndex, [tempNote]);
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
if (options?.parentFolderPath && options.parentFolderPath.length > 0) {
|
|
1811
|
-
const folderIndex = findFolderLineIndex(lines, options.parentFolderPath);
|
|
1812
|
-
await this.insertNotesUnderTocLine(folderIndex, [tempNote]);
|
|
1813
|
-
return;
|
|
1814
|
-
}
|
|
1815
|
-
lines.push(buildTocLine(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 (!existsSync2(ROOT_TOC_PATH)) return null;
|
|
1824
|
-
const lines = await this.readTocLines();
|
|
1825
|
-
return getPreviousTocNoteIndex(lines, noteIndex);
|
|
1826
|
-
}
|
|
1827
|
-
/**
|
|
1828
|
-
* 从 TOC.md 删除笔记及其缩进子树
|
|
1829
|
-
*/
|
|
1830
|
-
async deleteNoteFromToc(noteIndex) {
|
|
1831
|
-
if (!existsSync2(ROOT_TOC_PATH)) return;
|
|
1832
|
-
const lines = await this.readTocLines();
|
|
1833
|
-
let lineIndex;
|
|
1834
|
-
try {
|
|
1835
|
-
lineIndex = findTocLineIndex(lines, noteIndex);
|
|
1836
|
-
} catch {
|
|
1837
|
-
logger.warn(`TOC.md \u4E2D\u672A\u627E\u5230\u7B14\u8BB0: ${noteIndex}`);
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
const { start, end } = getTocEntrySubtreeRange(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 (!existsSync2(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 = resolveNoteFromIndex(noteIndex, notes);
|
|
1853
|
-
if (!note) return;
|
|
1854
|
-
const tempNote = {
|
|
1855
|
-
...note,
|
|
1856
|
-
dirName: newDirName,
|
|
1857
|
-
config: item?.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 = parseTocLine(lines[i]);
|
|
1863
|
-
if (parsed.noteIndex === noteIndex) {
|
|
1864
|
-
lines[i] = buildTocLine(
|
|
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 = findTocLineIndex(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 = parseTocLine(lines[sourceTocLineIndex]);
|
|
1891
|
-
if (!sourceParsed.isMatch) {
|
|
1892
|
-
throw new Error(`\u65E0\u6548\u7684 TOC \u884C\u7D22\u5F15: ${sourceTocLineIndex}`);
|
|
1893
|
-
}
|
|
1894
|
-
const { start, end } = getTocEntrySubtreeRange(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 = adjustTocLineIndexAfterSubtreeRemoval(
|
|
1904
|
-
resolvedTargetIndex,
|
|
1905
|
-
start,
|
|
1906
|
-
end
|
|
1907
|
-
);
|
|
1908
|
-
const targetParsed = parseTocLine(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 = getTocEntrySubtreeRange(lines, targetLineIndex).end;
|
|
1922
|
-
newIndent = targetParsed.indentLevel;
|
|
1923
|
-
}
|
|
1924
|
-
const oldBaseIndent = parseTocLine(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 && !parseTocLine(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 = parseNodeId(nodeId);
|
|
1985
|
-
if (parsed.kind === "note" && parsed.noteIndex) {
|
|
1986
|
-
return findTocLineIndex(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 findFolderLineIndex(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 = renameFolderLine(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 getPreviousNoteIndexOutsideSubtree(lines, tocLineIndex);
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* 判断 noteIndex 是否在指定 TOC 行子树内
|
|
2014
|
-
*/
|
|
2015
|
-
async isNoteInTocEntrySubtree(tocLineIndex, noteIndex) {
|
|
2016
|
-
const lines = await this.readTocLines();
|
|
2017
|
-
return isNoteIndexInSubtree(lines, tocLineIndex, noteIndex);
|
|
2018
|
-
}
|
|
2019
|
-
/**
|
|
2020
|
-
* 级联删除 TOC 子树,并删除子树内所有笔记磁盘目录
|
|
2021
|
-
*/
|
|
2022
|
-
async deleteTocEntryCascade(tocLineIndex) {
|
|
2023
|
-
if (!existsSync2(ROOT_TOC_PATH)) return [];
|
|
2024
|
-
const lines = await this.readTocLines();
|
|
2025
|
-
const noteIndexes = collectNoteIndexesInSubtree(lines, tocLineIndex);
|
|
2026
|
-
const { start, end } = getTocEntrySubtreeRange(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 = parseTocLine(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 } = getTocEntrySubtreeRange(lines, parentTocLineIndex);
|
|
2050
|
-
const noteLines = notesToInsert.map(
|
|
2051
|
-
(note) => buildTocLine(note, childIndent, getTocLineCompleted(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 = parseTocLine(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 } = getTocEntrySubtreeRange(lines, parentTocLineIndex);
|
|
2074
|
-
lines.splice(end, 0, buildFolderTocLine(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 = findTocLineIndex(lines, targetNoteIndex);
|
|
2085
|
-
const targetParsed = parseTocLine(lines[targetIndex]);
|
|
2086
|
-
const indent = targetParsed.indentLevel;
|
|
2087
|
-
const noteLines = notesToInsert.map(
|
|
2088
|
-
(note) => buildTocLine(note, indent, getTocLineCompleted(note))
|
|
2089
|
-
);
|
|
2090
|
-
const insertIndex = placement === "before" ? targetIndex : getTocEntrySubtreeRange(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 = findFolderLineIndex(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 = findTocLineIndex(lines, parentNoteIndex);
|
|
2110
|
-
await this.insertNotesUnderTocLine(parentIndex, notesToInsert);
|
|
2111
|
-
}
|
|
2112
|
-
/**
|
|
2113
|
-
* 规范化 TOC 并刷新 sidebar
|
|
2114
|
-
*/
|
|
2115
|
-
async refreshTocAndSidebar(notes) {
|
|
2116
|
-
const allNotes = 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 findFolderLineIndex(lines, target.targetFolderPath);
|
|
2126
|
-
}
|
|
2127
|
-
return findTocLineIndex(lines, target.targetNoteIndex);
|
|
2128
|
-
}
|
|
2129
|
-
getSidebarMaxDepth() {
|
|
2130
|
-
const config2 = this.configManager.getAll();
|
|
2131
|
-
const depth = 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 = parseTocLine(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 = parseTocLine(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 * TOC_INDENT_SPACES)}${withoutIndent}`;
|
|
2152
|
-
});
|
|
2153
|
-
}
|
|
2154
|
-
async readTocLines() {
|
|
2155
|
-
this.ensureTocExists();
|
|
2156
|
-
const content = await fsPromises2.readFile(ROOT_TOC_PATH, "utf-8");
|
|
2157
|
-
return content.split("\n");
|
|
2158
|
-
}
|
|
2159
|
-
async writeTocLines(lines) {
|
|
2160
|
-
const content = processTocEmptyLines(lines).join("\n");
|
|
2161
|
-
await fsPromises2.writeFile(ROOT_TOC_PATH, content, "utf-8");
|
|
2162
|
-
}
|
|
2163
|
-
};
|
|
2164
|
-
|
|
2165
1292
|
// services/file-watcher/service.ts
|
|
2166
1293
|
import { request as httpRequest } from "http";
|
|
2167
1294
|
|
|
@@ -2317,8 +1444,8 @@ var EventScheduler = class {
|
|
|
2317
1444
|
};
|
|
2318
1445
|
|
|
2319
1446
|
// services/file-watcher/folderChangeHandler.ts
|
|
2320
|
-
import { existsSync as
|
|
2321
|
-
import { join as
|
|
1447
|
+
import { existsSync as existsSync2, promises as fsPromises } from "fs";
|
|
1448
|
+
import { dirname, join as join3 } from "path";
|
|
2322
1449
|
|
|
2323
1450
|
// services/file-watcher/internal.ts
|
|
2324
1451
|
var WATCH_EVENT_TYPES = {
|
|
@@ -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 = createWorkspace({ 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,
|
|
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,8 +1515,7 @@ var FolderChangeHandler = class {
|
|
|
2377
1515
|
await safeExecute(
|
|
2378
1516
|
`\u5220\u9664\u7B14\u8BB0 ${noteIndex}`,
|
|
2379
1517
|
async () => {
|
|
2380
|
-
await
|
|
2381
|
-
await tocService.regenerateSidebar();
|
|
1518
|
+
await reconcileTocFromFiles(dirname(notesDir));
|
|
2382
1519
|
this.config.onNoteStructureChanged?.(`watcher:delete:${noteIndex}`);
|
|
2383
1520
|
},
|
|
2384
1521
|
logger2
|
|
@@ -2447,7 +1584,7 @@ var FolderChangeHandler = class {
|
|
|
2447
1584
|
return { oldNoteIndex, newNoteIndex };
|
|
2448
1585
|
}
|
|
2449
1586
|
async handleTitleOnlyRename(noteIndex, newName) {
|
|
2450
|
-
const { noteIndexCache,
|
|
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,8 +1592,11 @@ var FolderChangeHandler = class {
|
|
|
2455
1592
|
() => this.rewriteNoteReadmeH1(notesDir, newName, noteIndex),
|
|
2456
1593
|
logger2
|
|
2457
1594
|
);
|
|
2458
|
-
await
|
|
2459
|
-
|
|
1595
|
+
await safeExecute(
|
|
1596
|
+
`\u6807\u9898\u91CD\u547D\u540D\u540C\u6B65\u76EE\u5F55 ${noteIndex}`,
|
|
1597
|
+
() => reconcileTocFromFiles(dirname(notesDir)),
|
|
1598
|
+
logger2
|
|
1599
|
+
);
|
|
2460
1600
|
this.config.onNoteStructureChanged?.(`watcher:rename-title:${noteIndex}`);
|
|
2461
1601
|
logger2.success(`\u6807\u9898\u66F4\u65B0\u5B8C\u6210`);
|
|
2462
1602
|
}
|
|
@@ -2467,11 +1607,11 @@ var FolderChangeHandler = class {
|
|
|
2467
1607
|
* 据此推导出新标题文本(去掉 `${noteIndex}. ` 前缀)。
|
|
2468
1608
|
*/
|
|
2469
1609
|
async rewriteNoteReadmeH1(notesDir, folderName, noteIndex) {
|
|
2470
|
-
const readmePath =
|
|
2471
|
-
if (!
|
|
1610
|
+
const readmePath = join3(notesDir, folderName, "README.md");
|
|
1611
|
+
if (!existsSync2(readmePath)) return;
|
|
2472
1612
|
const newTitle = folderName.replace(/^\d{4}\.\s*/, "").trim();
|
|
2473
1613
|
if (!newTitle) return;
|
|
2474
|
-
const content = await
|
|
1614
|
+
const content = await fsPromises.readFile(readmePath, "utf-8");
|
|
2475
1615
|
const lines = content.split("\n");
|
|
2476
1616
|
let h1Index = -1;
|
|
2477
1617
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -2489,18 +1629,20 @@ var FolderChangeHandler = class {
|
|
|
2489
1629
|
const newH1 = generateNoteTitle(noteIndex, newTitle, REPO_NOTES_URL);
|
|
2490
1630
|
if (lines[h1Index] === newH1) return;
|
|
2491
1631
|
lines[h1Index] = newH1;
|
|
2492
|
-
await
|
|
1632
|
+
await fsPromises.writeFile(readmePath, lines.join("\n"), "utf-8");
|
|
2493
1633
|
}
|
|
2494
1634
|
async handleIndexChangedRename(oldNoteIndex, newNoteIndex) {
|
|
2495
|
-
const { noteService, noteIndexCache,
|
|
1635
|
+
const { noteService, noteIndexCache, logger: logger2, notesDir } = this.config;
|
|
2496
1636
|
logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u53D8\u66F4: ${oldNoteIndex} \u2192 ${newNoteIndex}`);
|
|
2497
|
-
await
|
|
1637
|
+
await safeExecute(
|
|
1638
|
+
`\u7D22\u5F15\u53D8\u66F4\u540C\u6B65\u76EE\u5F55 ${oldNoteIndex}\u2192${newNoteIndex}`,
|
|
1639
|
+
() => reconcileTocFromFiles(dirname(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
1646
|
this.config.onNoteStructureChanged?.(
|
|
2505
1647
|
`watcher:rename-index:${oldNoteIndex}->${newNoteIndex}`
|
|
2506
1648
|
);
|
|
@@ -2512,11 +1654,11 @@ var FolderChangeHandler = class {
|
|
|
2512
1654
|
async revertFolderRename(oldName, newName) {
|
|
2513
1655
|
const { notesDir, scheduler, watchState, logger: logger2 } = this.config;
|
|
2514
1656
|
try {
|
|
2515
|
-
const oldPath =
|
|
2516
|
-
const newPath =
|
|
2517
|
-
if (
|
|
1657
|
+
const oldPath = join3(notesDir, oldName);
|
|
1658
|
+
const newPath = join3(notesDir, newName);
|
|
1659
|
+
if (existsSync2(newPath)) {
|
|
2518
1660
|
scheduler.setUpdating(true);
|
|
2519
|
-
await
|
|
1661
|
+
await fsPromises.rename(newPath, oldPath);
|
|
2520
1662
|
logger2.warn(`\u6587\u4EF6\u5939\u5DF2\u56DE\u9000: ${newName} \u2192 ${oldName}`);
|
|
2521
1663
|
this.scheduleTimer(() => {
|
|
2522
1664
|
scheduler.setUpdating(false);
|
|
@@ -2539,7 +1681,7 @@ var FolderChangeHandler = class {
|
|
|
2539
1681
|
|
|
2540
1682
|
// services/file-watcher/fsWatcherAdapter.ts
|
|
2541
1683
|
import { watch } from "fs";
|
|
2542
|
-
import { basename, dirname, join as
|
|
1684
|
+
import { basename, dirname as dirname2, join as join4, sep } from "path";
|
|
2543
1685
|
var FsWatcherAdapter = class {
|
|
2544
1686
|
constructor(config2) {
|
|
2545
1687
|
this.config = config2;
|
|
@@ -2582,7 +1724,7 @@ var FsWatcherAdapter = class {
|
|
|
2582
1724
|
if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
|
|
2583
1725
|
return;
|
|
2584
1726
|
}
|
|
2585
|
-
const fullPath =
|
|
1727
|
+
const fullPath = join4(this.config.notesDir, filename);
|
|
2586
1728
|
const event = this.buildWatchEvent(fullPath, filename);
|
|
2587
1729
|
if (!event) {
|
|
2588
1730
|
return;
|
|
@@ -2590,7 +1732,7 @@ var FsWatcherAdapter = class {
|
|
|
2590
1732
|
onNoteEvent(event);
|
|
2591
1733
|
}
|
|
2592
1734
|
buildWatchEvent(fullPath, filename) {
|
|
2593
|
-
const noteDirName = basename(
|
|
1735
|
+
const noteDirName = basename(dirname2(fullPath));
|
|
2594
1736
|
const noteIndex = NoteManager.extractNoteIndex(noteDirName);
|
|
2595
1737
|
if (!noteIndex) {
|
|
2596
1738
|
NoteManager.warnInvalidNoteIndex(noteDirName);
|
|
@@ -2602,35 +1744,27 @@ var FsWatcherAdapter = class {
|
|
|
2602
1744
|
type: fileType,
|
|
2603
1745
|
noteIndex,
|
|
2604
1746
|
noteDirName,
|
|
2605
|
-
noteDirPath:
|
|
1747
|
+
noteDirPath: dirname2(fullPath)
|
|
2606
1748
|
};
|
|
2607
1749
|
}
|
|
2608
1750
|
};
|
|
2609
1751
|
|
|
2610
1752
|
// services/file-watcher/globalUpdateCoordinator.ts
|
|
1753
|
+
import { dirname as dirname3 } from "path";
|
|
2611
1754
|
var GlobalUpdateCoordinator = class {
|
|
2612
1755
|
constructor(config2) {
|
|
2613
|
-
this.config = config2;
|
|
2614
|
-
}
|
|
2615
|
-
async applyConfigUpdates(changedNoteIndexes) {
|
|
2616
|
-
if (changedNoteIndexes.length === 0) return;
|
|
2617
|
-
const {
|
|
2618
|
-
logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
noteIndex,
|
|
2626
|
-
item?.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();
|
|
1756
|
+
this.config = config2;
|
|
1757
|
+
}
|
|
1758
|
+
async applyConfigUpdates(changedNoteIndexes) {
|
|
1759
|
+
if (changedNoteIndexes.length === 0) return;
|
|
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(dirname3(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))];
|
|
@@ -2657,8 +1791,8 @@ var ReadmeChangeHandler = class {
|
|
|
2657
1791
|
};
|
|
2658
1792
|
|
|
2659
1793
|
// services/file-watcher/renameDetector.ts
|
|
2660
|
-
import { existsSync as
|
|
2661
|
-
import { join as
|
|
1794
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1795
|
+
import { join as join5 } from "path";
|
|
2662
1796
|
var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
|
|
2663
1797
|
var RenameDetector = class {
|
|
2664
1798
|
constructor(config2) {
|
|
@@ -2670,8 +1804,8 @@ var RenameDetector = class {
|
|
|
2670
1804
|
}
|
|
2671
1805
|
handleFsRename(folderName) {
|
|
2672
1806
|
const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
|
|
2673
|
-
const folderPath =
|
|
2674
|
-
const folderExists =
|
|
1807
|
+
const folderPath = join5(notesDir, folderName);
|
|
1808
|
+
const folderExists = existsSync3(folderPath);
|
|
2675
1809
|
const noteIndex = NoteManager.extractNoteIndex(folderName);
|
|
2676
1810
|
if (!noteIndex) {
|
|
2677
1811
|
logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
|
|
@@ -2727,8 +1861,8 @@ var RenameDetector = class {
|
|
|
2727
1861
|
|
|
2728
1862
|
// services/file-watcher/watchState.ts
|
|
2729
1863
|
import { createHash } from "crypto";
|
|
2730
|
-
import { existsSync as
|
|
2731
|
-
import { join as
|
|
1864
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
1865
|
+
import { join as join6 } from "path";
|
|
2732
1866
|
var WatchState = class {
|
|
2733
1867
|
constructor(config2) {
|
|
2734
1868
|
this.config = config2;
|
|
@@ -2747,8 +1881,8 @@ var WatchState = class {
|
|
|
2747
1881
|
*/
|
|
2748
1882
|
getFileHash(filePath) {
|
|
2749
1883
|
try {
|
|
2750
|
-
if (!
|
|
2751
|
-
const content =
|
|
1884
|
+
if (!existsSync4(filePath)) return null;
|
|
1885
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
2752
1886
|
if (content.length === 0) return null;
|
|
2753
1887
|
return createHash("md5").update(content).digest("hex");
|
|
2754
1888
|
} catch {
|
|
@@ -2808,8 +1942,8 @@ var WatchState = class {
|
|
|
2808
1942
|
* @param noteDirName 笔记目录名称
|
|
2809
1943
|
*/
|
|
2810
1944
|
clearNoteCaches(noteDirName) {
|
|
2811
|
-
const readmePath =
|
|
2812
|
-
const configPath =
|
|
1945
|
+
const readmePath = join6(this.config.notesDir, noteDirName, "README.md");
|
|
1946
|
+
const configPath = join6(this.config.notesDir, noteDirName, ".tnotes.json");
|
|
2813
1947
|
this.fileHashes.delete(readmePath);
|
|
2814
1948
|
this.fileHashes.delete(configPath);
|
|
2815
1949
|
this.configCache.delete(configPath);
|
|
@@ -2842,8 +1976,8 @@ var WatchState = class {
|
|
|
2842
1976
|
*/
|
|
2843
1977
|
readConfigSnapshot(configPath) {
|
|
2844
1978
|
try {
|
|
2845
|
-
if (!
|
|
2846
|
-
const content =
|
|
1979
|
+
if (!existsSync4(configPath)) return null;
|
|
1980
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
2847
1981
|
const config2 = JSON.parse(content);
|
|
2848
1982
|
return {
|
|
2849
1983
|
done: Boolean(config2.done),
|
|
@@ -2865,13 +1999,13 @@ var WatchState = class {
|
|
|
2865
1999
|
const noteDirs = readdirSync2(this.config.notesDir);
|
|
2866
2000
|
this.clearAll();
|
|
2867
2001
|
for (const noteDir of noteDirs) {
|
|
2868
|
-
const noteDirPath =
|
|
2002
|
+
const noteDirPath = join6(this.config.notesDir, noteDir);
|
|
2869
2003
|
if (!statSync(noteDirPath).isDirectory()) continue;
|
|
2870
2004
|
this.noteDirCache.add(noteDir);
|
|
2871
|
-
const readmePath =
|
|
2005
|
+
const readmePath = join6(noteDirPath, "README.md");
|
|
2872
2006
|
const readmeHash = this.getFileHash(readmePath);
|
|
2873
2007
|
if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
|
|
2874
|
-
const configPath =
|
|
2008
|
+
const configPath = join6(noteDirPath, ".tnotes.json");
|
|
2875
2009
|
const configHash = this.getFileHash(configPath);
|
|
2876
2010
|
if (configHash) {
|
|
2877
2011
|
this.fileHashes.set(configPath, configHash);
|
|
@@ -2887,16 +2021,267 @@ var WatchState = class {
|
|
|
2887
2021
|
}
|
|
2888
2022
|
};
|
|
2889
2023
|
|
|
2024
|
+
// services/note/service.ts
|
|
2025
|
+
import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, promises as fsPromises2 } from "fs";
|
|
2026
|
+
import { join as join7 } from "path";
|
|
2027
|
+
import { v4 as uuidv4 } from "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 = join7(NOTES_PATH, dirName);
|
|
2094
|
+
await ensureDirectory(notePath);
|
|
2095
|
+
const readmePath = join7(notePath, "README.md");
|
|
2096
|
+
const noteTitle = generateNoteTitle(noteIndex, title, REPO_NOTES_URL);
|
|
2097
|
+
const readmeContent = noteTitle + "\n" + getNewNoteReadmeBody();
|
|
2098
|
+
writeFileSync2(readmePath, readmeContent, "utf-8");
|
|
2099
|
+
const configPath = join7(notePath, ".tnotes.json");
|
|
2100
|
+
const config2 = {
|
|
2101
|
+
id: configId || uuidv4(),
|
|
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 fsPromises2.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 = readFileSync3(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 = generateNoteTitle(
|
|
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
|
+
writeFileSync2(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
|
+
writeFileSync2(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 = 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
|
import {
|
|
2892
|
-
existsSync as
|
|
2893
|
-
readFileSync as
|
|
2894
|
-
writeFileSync as
|
|
2895
|
-
promises as
|
|
2277
|
+
existsSync as existsSync6,
|
|
2278
|
+
readFileSync as readFileSync5,
|
|
2279
|
+
writeFileSync as writeFileSync4,
|
|
2280
|
+
promises as fsPromises3
|
|
2896
2281
|
} from "fs";
|
|
2897
2282
|
|
|
2898
2283
|
// core/ReadmeGenerator.ts
|
|
2899
|
-
import { readFileSync as
|
|
2284
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
2900
2285
|
|
|
2901
2286
|
// core/TocGenerator.ts
|
|
2902
2287
|
var BILIBILI_VIDEO_BASE_URL = "https://www.bilibili.com/video/";
|
|
@@ -3062,7 +2447,7 @@ var ReadmeGenerator = class {
|
|
|
3062
2447
|
logger.warn(`\u7B14\u8BB0 ${noteInfo.dirName} \u7F3A\u5C11\u914D\u7F6E\u6587\u4EF6`);
|
|
3063
2448
|
return;
|
|
3064
2449
|
}
|
|
3065
|
-
const content =
|
|
2450
|
+
const content = readFileSync4(noteInfo.readmePath, "utf-8");
|
|
3066
2451
|
if (content.length === 0) return;
|
|
3067
2452
|
const lines = content.split(EOL);
|
|
3068
2453
|
const repoName2 = this.configManager.get("repoName");
|
|
@@ -3073,7 +2458,7 @@ var ReadmeGenerator = class {
|
|
|
3073
2458
|
repoName2
|
|
3074
2459
|
);
|
|
3075
2460
|
const updatedContent = lines.join(EOL);
|
|
3076
|
-
|
|
2461
|
+
writeFileSync3(noteInfo.readmePath, updatedContent, "utf-8");
|
|
3077
2462
|
}
|
|
3078
2463
|
/**
|
|
3079
2464
|
* 更新首页 README
|
|
@@ -3082,11 +2467,11 @@ var ReadmeGenerator = class {
|
|
|
3082
2467
|
* @param homeReadmePath - 首页 README 路径
|
|
3083
2468
|
*/
|
|
3084
2469
|
updateHomeReadme(notes, homeReadmePath) {
|
|
3085
|
-
if (!
|
|
2470
|
+
if (!existsSync5(homeReadmePath)) {
|
|
3086
2471
|
logger.error(`\u6839\u76EE\u5F55\u4E0B\u7684 README.md \u6587\u4EF6\u672A\u627E\u5230\uFF1A${homeReadmePath}`);
|
|
3087
2472
|
return;
|
|
3088
2473
|
}
|
|
3089
|
-
const content =
|
|
2474
|
+
const content = readFileSync4(homeReadmePath, "utf-8");
|
|
3090
2475
|
const lines = content.split(EOL);
|
|
3091
2476
|
const noteByIndexMap = /* @__PURE__ */ new Map();
|
|
3092
2477
|
for (const note of notes) {
|
|
@@ -3178,7 +2563,7 @@ var ReadmeGenerator = class {
|
|
|
3178
2563
|
this.tocGenerator.updateHomeToc(lines, titles, titlesNotesCount);
|
|
3179
2564
|
const processedLines = processEmptyLines(lines);
|
|
3180
2565
|
const updatedContent = processedLines.join(EOL);
|
|
3181
|
-
|
|
2566
|
+
writeFileSync3(homeReadmePath, updatedContent, "utf-8");
|
|
3182
2567
|
logger.info("\u5DF2\u66F4\u65B0\u9996\u9875 README");
|
|
3183
2568
|
}
|
|
3184
2569
|
};
|
|
@@ -3190,7 +2575,6 @@ var ReadmeService = class _ReadmeService {
|
|
|
3190
2575
|
this.readmeGenerator = new ReadmeGenerator();
|
|
3191
2576
|
this.configManager = 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
|
|
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
|
}
|
|
@@ -3311,11 +2694,11 @@ var ReadmeService = class _ReadmeService {
|
|
|
3311
2694
|
* @param notes - 笔记信息数组
|
|
3312
2695
|
*/
|
|
3313
2696
|
async updateSidebar(notes) {
|
|
3314
|
-
if (!
|
|
2697
|
+
if (!existsSync6(ROOT_README_PATH)) {
|
|
3315
2698
|
logger.error("\u672A\u627E\u5230\u9996\u9875 README\uFF0C\u65E0\u6CD5\u751F\u6210\u4FA7\u8FB9\u680F");
|
|
3316
2699
|
return;
|
|
3317
2700
|
}
|
|
3318
|
-
const content =
|
|
2701
|
+
const content = readFileSync5(ROOT_README_PATH, "utf-8");
|
|
3319
2702
|
const lines = content.split("\n");
|
|
3320
2703
|
const itemList = [];
|
|
3321
2704
|
const titles = [];
|
|
@@ -3378,7 +2761,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3378
2761
|
titlesNotesCount,
|
|
3379
2762
|
sidebarIsCollapsed
|
|
3380
2763
|
);
|
|
3381
|
-
|
|
2764
|
+
writeFileSync4(
|
|
3382
2765
|
VP_SIDEBAR_PATH,
|
|
3383
2766
|
JSON.stringify(hierarchicalSidebar, null, 2),
|
|
3384
2767
|
"utf-8"
|
|
@@ -3403,7 +2786,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3403
2786
|
logger.warn(`\u5C1D\u8BD5\u66F4\u65B0\u4E0D\u5B58\u5728\u7684\u7B14\u8BB0: ${noteIndex}`);
|
|
3404
2787
|
return;
|
|
3405
2788
|
}
|
|
3406
|
-
const content = await
|
|
2789
|
+
const content = await fsPromises3.readFile(ROOT_README_PATH, "utf-8");
|
|
3407
2790
|
const lines = content.split("\n");
|
|
3408
2791
|
const repoOwner = this.configManager.get("author");
|
|
3409
2792
|
const repoName2 = this.configManager.get("repoName");
|
|
@@ -3425,7 +2808,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3425
2808
|
}
|
|
3426
2809
|
}
|
|
3427
2810
|
if (updated) {
|
|
3428
|
-
await
|
|
2811
|
+
await fsPromises3.writeFile(ROOT_README_PATH, lines.join("\n"), "utf-8");
|
|
3429
2812
|
logger.info(`\u589E\u91CF\u66F4\u65B0 README.md \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
|
|
3430
2813
|
} else {
|
|
3431
2814
|
logger.warn(`README.md \u4E2D\u672A\u627E\u5230\u7B14\u8BB0: ${noteIndex}`);
|
|
@@ -3436,7 +2819,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3436
2819
|
* @param noteIndex - 笔记索引
|
|
3437
2820
|
*/
|
|
3438
2821
|
async deleteNoteFromReadme(noteIndex) {
|
|
3439
|
-
const content = await
|
|
2822
|
+
const content = await fsPromises3.readFile(ROOT_README_PATH, "utf-8");
|
|
3440
2823
|
const lines = content.split("\n");
|
|
3441
2824
|
const linesToRemove = [];
|
|
3442
2825
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -3449,7 +2832,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3449
2832
|
for (let i = linesToRemove.length - 1; i >= 0; i--) {
|
|
3450
2833
|
lines.splice(linesToRemove[i], 1);
|
|
3451
2834
|
}
|
|
3452
|
-
await
|
|
2835
|
+
await fsPromises3.writeFile(ROOT_README_PATH, lines.join("\n"), "utf-8");
|
|
3453
2836
|
logger.info(
|
|
3454
2837
|
`\u4ECE README.md \u4E2D\u5220\u9664\u7B14\u8BB0: ${noteIndex} (${linesToRemove.length} \u5904\u5F15\u7528)`
|
|
3455
2838
|
);
|
|
@@ -3467,7 +2850,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3467
2850
|
logger.warn(`\u5C1D\u8BD5\u6DFB\u52A0\u4E0D\u5B58\u5728\u7684\u7B14\u8BB0: ${noteIndex}`);
|
|
3468
2851
|
return;
|
|
3469
2852
|
}
|
|
3470
|
-
const content = await
|
|
2853
|
+
const content = await fsPromises3.readFile(ROOT_README_PATH, "utf-8");
|
|
3471
2854
|
const lines = content.split("\n");
|
|
3472
2855
|
const repoOwner = this.configManager.get("author");
|
|
3473
2856
|
const repoName2 = this.configManager.get("repoName");
|
|
@@ -3481,7 +2864,7 @@ var ReadmeService = class _ReadmeService {
|
|
|
3481
2864
|
};
|
|
3482
2865
|
const noteLine = buildNoteLineMarkdown(tempNoteInfo, repoOwner, repoName2);
|
|
3483
2866
|
lines.push(noteLine);
|
|
3484
|
-
await
|
|
2867
|
+
await fsPromises3.writeFile(ROOT_README_PATH, lines.join("\n"), "utf-8");
|
|
3485
2868
|
logger.info(`\u5728 README.md \u672B\u5C3E\u6DFB\u52A0\u7B14\u8BB0: ${noteIndex}`);
|
|
3486
2869
|
}
|
|
3487
2870
|
/**
|
|
@@ -3604,26 +2987,24 @@ var ReadmeService = class _ReadmeService {
|
|
|
3604
2987
|
/**
|
|
3605
2988
|
* 更新 TOC.md 和 sidebar,不重写每篇笔记 README
|
|
3606
2989
|
*/
|
|
3607
|
-
async refreshHomeReadmeAndSidebar(
|
|
3608
|
-
|
|
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(
|
|
3617
|
-
await
|
|
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() {
|
|
3621
|
-
const content = await
|
|
3002
|
+
const content = await fsPromises3.readFile(ROOT_README_PATH, "utf-8");
|
|
3622
3003
|
return content.split("\n");
|
|
3623
3004
|
}
|
|
3624
3005
|
async writeHomeReadmeLines(lines) {
|
|
3625
3006
|
const content = processEmptyLines(lines).join("\n");
|
|
3626
|
-
await
|
|
3007
|
+
await fsPromises3.writeFile(ROOT_README_PATH, content, "utf-8");
|
|
3627
3008
|
}
|
|
3628
3009
|
assertGroupPath(groupPath) {
|
|
3629
3010
|
if (!Array.isArray(groupPath) || groupPath.length === 0) {
|
|
@@ -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
|
}
|
|
@@ -4746,12 +4124,12 @@ var VitepressService = class _VitepressService {
|
|
|
4746
4124
|
};
|
|
4747
4125
|
|
|
4748
4126
|
// services/init-sub-repo/service.ts
|
|
4749
|
-
import { copyFileSync, existsSync as
|
|
4750
|
-
import { dirname as
|
|
4127
|
+
import { copyFileSync, existsSync as existsSync8, mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
4128
|
+
import { dirname as dirname4, join as join9, resolve as resolve3 } from "path";
|
|
4751
4129
|
import { fileURLToPath } from "url";
|
|
4752
4130
|
|
|
4753
4131
|
// services/init-sub-repo/initSubRepoLogic.ts
|
|
4754
|
-
import { existsSync as
|
|
4132
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
4755
4133
|
import { join as join8, resolve as resolve2 } from "path";
|
|
4756
4134
|
var TOPIC_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
4757
4135
|
var DEFAULT_PORT = 8e3;
|
|
@@ -4944,20 +4322,20 @@ function buildDynamicFiles(ctx) {
|
|
|
4944
4322
|
];
|
|
4945
4323
|
}
|
|
4946
4324
|
function isAlreadyInitialized(rootPath2) {
|
|
4947
|
-
return
|
|
4325
|
+
return existsSync7(resolve2(rootPath2, ".tnotes.json"));
|
|
4948
4326
|
}
|
|
4949
4327
|
function getInitializedConfigPath(rootPath2) {
|
|
4950
4328
|
return resolve2(rootPath2, ".tnotes.json");
|
|
4951
4329
|
}
|
|
4952
4330
|
function resolvePackageJsonMerge(targetPath, template) {
|
|
4953
|
-
if (!
|
|
4331
|
+
if (!existsSync7(targetPath)) {
|
|
4954
4332
|
return {
|
|
4955
4333
|
content: `${JSON.stringify(template, null, 2)}
|
|
4956
4334
|
`,
|
|
4957
4335
|
action: "created"
|
|
4958
4336
|
};
|
|
4959
4337
|
}
|
|
4960
|
-
const existing = JSON.parse(
|
|
4338
|
+
const existing = JSON.parse(readFileSync6(targetPath, "utf-8"));
|
|
4961
4339
|
const merged = mergePackageJson(existing, template);
|
|
4962
4340
|
return {
|
|
4963
4341
|
content: `${JSON.stringify(merged, null, 2)}
|
|
@@ -4980,14 +4358,14 @@ function buildManualSteps(ctx) {
|
|
|
4980
4358
|
|
|
4981
4359
|
// services/init-sub-repo/service.ts
|
|
4982
4360
|
function getCorePackageRoot(fromModuleUrl) {
|
|
4983
|
-
let dir =
|
|
4984
|
-
while (dir !==
|
|
4361
|
+
let dir = dirname4(fileURLToPath(fromModuleUrl));
|
|
4362
|
+
while (dir !== dirname4(dir)) {
|
|
4985
4363
|
const pkgPath = join9(dir, "package.json");
|
|
4986
|
-
if (
|
|
4987
|
-
const pkg = JSON.parse(
|
|
4364
|
+
if (existsSync8(pkgPath)) {
|
|
4365
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4988
4366
|
if (pkg.name === "@tnotesjs/core") return dir;
|
|
4989
4367
|
}
|
|
4990
|
-
dir =
|
|
4368
|
+
dir = dirname4(dir);
|
|
4991
4369
|
}
|
|
4992
4370
|
throw new Error("\u65E0\u6CD5\u5B9A\u4F4D @tnotesjs/core \u5305\u6839\u76EE\u5F55");
|
|
4993
4371
|
}
|
|
@@ -5009,12 +4387,12 @@ var InitSubRepoService = class _InitSubRepoService {
|
|
|
5009
4387
|
const updated = [];
|
|
5010
4388
|
for (const relativePath of STATIC_TEMPLATE_FILES) {
|
|
5011
4389
|
const targetPath = resolve3(this.rootPath, relativePath);
|
|
5012
|
-
if (
|
|
4390
|
+
if (existsSync8(targetPath)) {
|
|
5013
4391
|
skipped.push(relativePath);
|
|
5014
4392
|
continue;
|
|
5015
4393
|
}
|
|
5016
4394
|
const sourcePath = join9(this.templateRoot, relativePath);
|
|
5017
|
-
mkdirSync(
|
|
4395
|
+
mkdirSync(dirname4(targetPath), { recursive: true });
|
|
5018
4396
|
copyFileSync(sourcePath, targetPath);
|
|
5019
4397
|
created.push(relativePath);
|
|
5020
4398
|
}
|
|
@@ -5026,18 +4404,18 @@ var InitSubRepoService = class _InitSubRepoService {
|
|
|
5026
4404
|
targetPath,
|
|
5027
4405
|
packageTemplate
|
|
5028
4406
|
);
|
|
5029
|
-
mkdirSync(
|
|
5030
|
-
|
|
4407
|
+
mkdirSync(dirname4(targetPath), { recursive: true });
|
|
4408
|
+
writeFileSync5(targetPath, content, "utf-8");
|
|
5031
4409
|
if (action === "created") created.push(file.relativePath);
|
|
5032
4410
|
else updated.push(file.relativePath);
|
|
5033
4411
|
continue;
|
|
5034
4412
|
}
|
|
5035
|
-
if (
|
|
4413
|
+
if (existsSync8(targetPath)) {
|
|
5036
4414
|
skipped.push(file.relativePath);
|
|
5037
4415
|
continue;
|
|
5038
4416
|
}
|
|
5039
|
-
mkdirSync(
|
|
5040
|
-
|
|
4417
|
+
mkdirSync(dirname4(targetPath), { recursive: true });
|
|
4418
|
+
writeFileSync5(targetPath, file.content, "utf-8");
|
|
5041
4419
|
created.push(file.relativePath);
|
|
5042
4420
|
}
|
|
5043
4421
|
return { created, skipped, updated };
|
|
@@ -5128,13 +4506,12 @@ var UpdateNoteConfigCommand = class extends BaseCommand {
|
|
|
5128
4506
|
};
|
|
5129
4507
|
|
|
5130
4508
|
// commands/note/RenameNoteCommand.ts
|
|
5131
|
-
import { existsSync as
|
|
4509
|
+
import { existsSync as existsSync9, renameSync, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
5132
4510
|
import { join as join10 } from "path";
|
|
5133
4511
|
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;
|
|
@@ -5165,7 +4542,7 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
5165
4542
|
}
|
|
5166
4543
|
const newDirName = `${noteIndex}. ${newTitle.trim()}`;
|
|
5167
4544
|
const newPath = join10(NOTES_PATH, newDirName);
|
|
5168
|
-
if (
|
|
4545
|
+
if (existsSync9(newPath)) {
|
|
5169
4546
|
throw new Error(`\u76EE\u6807\u6587\u4EF6\u5939\u5DF2\u5B58\u5728: ${newDirName}`);
|
|
5170
4547
|
}
|
|
5171
4548
|
const watcher = FileWatcherService.getInstance();
|
|
@@ -5185,8 +4562,8 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
5185
4562
|
try {
|
|
5186
4563
|
this.logger.info("\u6B63\u5728\u66F4\u65B0\u7B14\u8BB0\u5185\u90E8\u6807\u9898...");
|
|
5187
4564
|
const readmePath = join10(newPath, "README.md");
|
|
5188
|
-
if (
|
|
5189
|
-
const content =
|
|
4565
|
+
if (existsSync9(readmePath)) {
|
|
4566
|
+
const content = readFileSync8(readmePath, "utf-8");
|
|
5190
4567
|
const lines = content.split("\n");
|
|
5191
4568
|
let h1Index = -1;
|
|
5192
4569
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -5202,7 +4579,7 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
5202
4579
|
REPO_NOTES_URL
|
|
5203
4580
|
);
|
|
5204
4581
|
lines[h1Index] = newH1;
|
|
5205
|
-
|
|
4582
|
+
writeFileSync6(readmePath, lines.join("\n"), "utf-8");
|
|
5206
4583
|
this.logger.success("\u2705 \u7B14\u8BB0\u6807\u9898\u5DF2\u66F4\u65B0");
|
|
5207
4584
|
} else {
|
|
5208
4585
|
this.logger.warn(
|
|
@@ -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
|
|
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);
|
|
@@ -5226,19 +4602,17 @@ var RenameNoteCommand = class extends BaseCommand {
|
|
|
5226
4602
|
|
|
5227
4603
|
export {
|
|
5228
4604
|
ROOT_DIR_PATH,
|
|
5229
|
-
ROOT_README_PATH,
|
|
5230
4605
|
ROOT_TOC_PATH,
|
|
5231
4606
|
ROOT_CONFIG_PATH,
|
|
4607
|
+
reconcileTocFromFiles,
|
|
5232
4608
|
handleError,
|
|
5233
4609
|
logger,
|
|
5234
4610
|
createLogger,
|
|
5235
4611
|
parseArgs,
|
|
5236
4612
|
parseReadmeCompletedNotes,
|
|
5237
|
-
migrateReadmeToToc,
|
|
5238
4613
|
runCommand,
|
|
5239
4614
|
NoteManager,
|
|
5240
4615
|
NoteIndexCache,
|
|
5241
|
-
TocService,
|
|
5242
4616
|
NoteService,
|
|
5243
4617
|
ReadmeService,
|
|
5244
4618
|
FileWatcherService,
|