@tnotesjs/core 0.2.2 → 0.4.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 (47) hide show
  1. package/README.md +0 -1
  2. package/config/defaultConfig.ts +33 -4
  3. package/config/index.ts +6 -0
  4. package/core/NoteIndexCache.ts +7 -1
  5. package/core/NoteManager.ts +18 -30
  6. package/dist/{chunk-5Y5IYS6C.js → chunk-UJG3UU5X.js} +21 -5
  7. package/dist/{chunk-5QN44ES5.js → chunk-XCWFZLER.js} +48 -368
  8. package/dist/cli/index.js +26 -78
  9. package/dist/index.js +1 -1
  10. package/dist/vitepress/config/index.js +159 -18
  11. package/package.json +3 -6
  12. package/services/index.ts +0 -1
  13. package/services/init-sub-repo/initSubRepoLogic.ts +0 -1
  14. package/services/note/service.ts +0 -1
  15. package/types/config.ts +0 -3
  16. package/types/note.ts +0 -2
  17. package/vitepress/assets/icons/icon__cursor.svg +4 -0
  18. package/vitepress/assets/icons/index.ts +1 -0
  19. package/vitepress/components/Layout/AboutPanel.vue +0 -24
  20. package/vitepress/components/Layout/DocBeforeControls.vue +6 -14
  21. package/vitepress/components/Layout/DocFooter.vue +3 -3
  22. package/vitepress/components/Layout/Layout.vue +0 -30
  23. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +20 -11
  24. package/vitepress/components/Layout/homeReadme.data.ts +0 -48
  25. package/vitepress/components/MindmapPreview/InlineRuns.ts +25 -0
  26. package/vitepress/components/MindmapPreview/MindmapOutlineNode.vue +62 -0
  27. package/vitepress/components/MindmapPreview/MindmapPreview.vue +287 -0
  28. package/vitepress/components/MindmapPreview/compat.test.ts +93 -0
  29. package/vitepress/components/MindmapPreview/compat.ts +128 -0
  30. package/vitepress/components/MindmapPreview/expandLevel.test.ts +40 -0
  31. package/vitepress/components/MindmapPreview/expandLevel.ts +28 -0
  32. package/vitepress/components/MindmapPreview/legacyCorpus.test.ts +57 -0
  33. package/vitepress/components/Settings/Settings.vue +76 -73
  34. package/vitepress/components/SidebarCard/FolderTreeItems.vue +16 -6
  35. package/vitepress/components/SidebarCard/SidebarCard.vue +21 -27
  36. package/vitepress/components/composables/useLocalIde.ts +83 -0
  37. package/vitepress/components/constants.ts +5 -6
  38. package/vitepress/components/utils/vscodePaths.test.ts +22 -0
  39. package/vitepress/components/utils/vscodePaths.ts +24 -2
  40. package/vitepress/configs/markdown.config.ts +77 -20
  41. package/vitepress/theme/index.ts +4 -2
  42. package/vitepress/theme/styles/doc-layout.scss +4 -4
  43. package/vitepress/theme/styles/index.scss +0 -1
  44. package/services/timestamp/index.ts +0 -7
  45. package/services/timestamp/service.ts +0 -465
  46. package/vitepress/components/MarkMap/MarkMap.vue +0 -625
  47. package/vitepress/theme/styles/components/markmap.scss +0 -122
package/dist/cli/index.js CHANGED
@@ -19,7 +19,6 @@ import {
19
19
  ROOT_TOC_PATH,
20
20
  ReadmeService,
21
21
  RenameNoteCommand,
22
- TimestampService,
23
22
  TocService,
24
23
  UpdateNoteConfigCommand,
25
24
  VitepressService,
@@ -42,11 +41,12 @@ import {
42
41
  runCommand,
43
42
  validatePort,
44
43
  validateTopic
45
- } from "../chunk-5QN44ES5.js";
44
+ } from "../chunk-XCWFZLER.js";
46
45
  import {
47
46
  ConfigManager,
48
- getConfigManager
49
- } from "../chunk-5Y5IYS6C.js";
47
+ getConfigManager,
48
+ stripDeprecatedRootItemFields
49
+ } from "../chunk-UJG3UU5X.js";
50
50
 
51
51
  // commands/update/UpdateCommand.ts
52
52
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -121,6 +121,9 @@ var UpdateCommand = class extends BaseCommand {
121
121
  completed_notes_count: fixed
122
122
  };
123
123
  delete config.root_item.completed_notes_count_last_month;
124
+ stripDeprecatedRootItemFields(
125
+ config.root_item
126
+ );
124
127
  writeFileSync(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
125
128
  if (!this.quiet) {
126
129
  const filledCount = Object.keys(fixed).length - _countsBeforeFix;
@@ -192,7 +195,6 @@ var PushCommand = class extends BaseCommand {
192
195
  super("push");
193
196
  this.gitManager = new GitManager(ROOT_DIR_PATH);
194
197
  this.gitService = new GitService();
195
- this.timestampService = new TimestampService();
196
198
  }
197
199
  async run() {
198
200
  try {
@@ -214,18 +216,6 @@ var PushCommand = class extends BaseCommand {
214
216
  const commitMessage = this.gitService.generateCommitMessage();
215
217
  await runCommand("git add -A", ROOT_DIR_PATH);
216
218
  await runCommand(`git commit -m "${commitMessage}"`, ROOT_DIR_PATH);
217
- this.logger.info("\u4FEE\u590D\u7B14\u8BB0\u65F6\u95F4\u6233...");
218
- await this.timestampService.fixAllTimestamps(false);
219
- const afterFixStatus = await runCommand(
220
- "git -c core.quotePath=false status --porcelain",
221
- ROOT_DIR_PATH
222
- );
223
- const hasTimestampChanges = afterFixStatus.split(/\r?\n/).filter(Boolean).some((line) => line.includes(".tnotes.json"));
224
- if (hasTimestampChanges) {
225
- this.logger.info("\u5C06\u65F6\u95F4\u6233\u53D8\u66F4\u5408\u5E76\u5230 commit...");
226
- await runCommand("git add -u", ROOT_DIR_PATH);
227
- await runCommand("git commit --amend --no-edit", ROOT_DIR_PATH);
228
- }
229
219
  } else {
230
220
  this.logger.info(`\u68C0\u6D4B\u5230 ${status.ahead} \u4E2A\u672A\u63A8\u9001\u7684\u63D0\u4EA4\uFF0C\u76F4\u63A5\u63A8\u9001...`);
231
221
  }
@@ -539,37 +529,6 @@ var InitTocCommand = class extends BaseCommand {
539
529
  }
540
530
  };
541
531
 
542
- // commands/maintenance/FixTimestampsCommand.ts
543
- var FixTimestampsCommand = class extends BaseCommand {
544
- constructor() {
545
- super("fix-timestamps");
546
- this.timestampService = new TimestampService();
547
- }
548
- async run() {
549
- this.logger.info("\u5F00\u59CB\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233...");
550
- this.logger.info("\u{1F4CC} \u6B64\u64CD\u4F5C\u4F1A\u5C06\u6240\u6709\u65F6\u95F4\u6233\u66F4\u65B0\u4E3A git \u771F\u5B9E\u65F6\u95F4");
551
- this.logger.info("");
552
- const result = await this.timestampService.fixAllTimestamps(true);
553
- this.logger.info("");
554
- this.logger.info("\u{1F4CA} \u4FEE\u590D\u7EDF\u8BA1:");
555
- this.logger.info(
556
- ` - \u6839\u914D\u7F6E\u6587\u4EF6: ${result.rootConfigFixed ? "\u5DF2\u4FEE\u590D" : "\u65E0\u9700\u4FEE\u590D"}`
557
- );
558
- this.logger.info(` - \u603B\u7B14\u8BB0\u6570: ${result.total}`);
559
- this.logger.info(` - \u5DF2\u4FEE\u590D: ${result.fixed}`);
560
- this.logger.info(` - \u8DF3\u8FC7: ${result.skipped}`);
561
- this.logger.info("");
562
- if (result.fixed > 0 || result.rootConfigFixed) {
563
- this.logger.success(
564
- `\u2705 \u6210\u529F\u4FEE\u590D ${result.fixed} \u4E2A\u7B14\u8BB0${result.rootConfigFixed ? " + \u6839\u914D\u7F6E\u6587\u4EF6" : ""}\u7684\u65F6\u95F4\u6233\uFF01`
565
- );
566
- this.logger.info("\u{1F4A1} \u63D0\u793A: \u8FD0\u884C pnpm tn:push \u63D0\u4EA4\u66F4\u6539");
567
- } else {
568
- this.logger.success("\u2705 \u6240\u6709\u65F6\u95F4\u6233\u5747\u5DF2\u6B63\u786E\uFF01");
569
- }
570
- }
571
- };
572
-
573
532
  // commands/misc/HelpCommand.ts
574
533
  var COMMAND_CATEGORIES = {
575
534
  \u5F00\u53D1\u548C\u6784\u5EFA: [COMMAND_NAMES.DEV, COMMAND_NAMES.BUILD, COMMAND_NAMES.PREVIEW],
@@ -581,7 +540,7 @@ var COMMAND_CATEGORIES = {
581
540
  "Git \u64CD\u4F5C": [COMMAND_NAMES.PUSH, COMMAND_NAMES.PULL],
582
541
  "\u8FC1\u79FB\uFF08v0.2.x \u8FC7\u6E21\uFF09": [COMMAND_NAMES.INIT_TOC],
583
542
  \u521D\u59CB\u5316: [COMMAND_NAMES.INIT_SUB_REPO],
584
- \u5176\u4ED6: [COMMAND_NAMES.FIX_TIMESTAMPS, COMMAND_NAMES.HELP]
543
+ \u5176\u4ED6: [COMMAND_NAMES.HELP]
585
544
  };
586
545
  var COMMAND_OPTIONS_INFO = {
587
546
  [COMMAND_OPTIONS.QUIET]: {
@@ -783,13 +742,14 @@ var UpdateCompletedCountCommand = class extends BaseCommand {
783
742
  const configContent = readFileSync4(ROOT_CONFIG_PATH, "utf-8");
784
743
  const config = JSON.parse(configContent);
785
744
  this.logger.info("\u5F00\u59CB\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55...");
786
- const completedNotesCountHistory = await this.getCompletedNotesCountHistory(
787
- config.root_item.created_at ?? Date.now()
788
- );
745
+ const completedNotesCountHistory = await this.getCompletedNotesCountHistory();
789
746
  config.root_item = {
790
747
  ...config.root_item,
791
748
  completed_notes_count: completedNotesCountHistory
792
749
  };
750
+ stripDeprecatedRootItemFields(
751
+ config.root_item
752
+ );
793
753
  writeFileSync3(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
794
754
  const duration = Date.now() - startTime;
795
755
  const monthKeys = Object.keys(completedNotesCountHistory);
@@ -812,10 +772,9 @@ var UpdateCompletedCountCommand = class extends BaseCommand {
812
772
  * 1. 计算最近12个月的范围(当前月份往前推11个月)
813
773
  * 2. 遍历这12个月,从 Git 历史中读取 TOC.md(回退 README.md)
814
774
  * 3. 解析获取完成笔记数量
815
- * 4. 如果知识库创建时间在这12个月内,之前的月份补0
816
- * 5. 返回对象 { '25.01': 0, '25.02': 1, ..., '25.12': 15 }
775
+ * 4. 返回对象 { '25.01': 0, '25.02': 1, ..., '25.12': 15 }
817
776
  */
818
- async getCompletedNotesCountHistory(createdAt) {
777
+ async getCompletedNotesCountHistory() {
819
778
  try {
820
779
  const now = /* @__PURE__ */ new Date();
821
780
  const currentYear = now.getFullYear();
@@ -826,9 +785,6 @@ var UpdateCompletedCountCommand = class extends BaseCommand {
826
785
  firstYear = currentYear - 1;
827
786
  firstMonth = 12 + firstMonth;
828
787
  }
829
- const createdDate = new Date(createdAt);
830
- const createdYear = createdDate.getFullYear();
831
- const createdMonth = createdDate.getMonth();
832
788
  const result = {};
833
789
  let prevCount = 0;
834
790
  for (let i = 0; i < 12; i++) {
@@ -837,25 +793,18 @@ var UpdateCompletedCountCommand = class extends BaseCommand {
837
793
  const yearShort = String(targetYear).slice(-2);
838
794
  const monthStr = String(targetMonth + 1).padStart(2, "0");
839
795
  const key = `${yearShort}.${monthStr}`;
840
- const isBeforeCreation = targetYear < createdYear || targetYear === createdYear && targetMonth < createdMonth;
841
- if (isBeforeCreation) {
842
- result[key] = 0;
843
- prevCount = 0;
844
- this.logger.info(`\u2713 ${key}: 0 \u7BC7\uFF08\u65E9\u4E8E\u521B\u5EFA\u65F6\u95F4\uFF09`);
845
- } else {
846
- try {
847
- const count = await this.getMonthCompletedCount(
848
- targetYear,
849
- targetMonth,
850
- prevCount
851
- );
852
- result[key] = count;
853
- prevCount = count;
854
- this.logger.info(`\u2713 ${key}: ${count} \u7BC7`);
855
- } catch (error) {
856
- result[key] = prevCount;
857
- this.logger.warn(`${key}: \u65E0\u6570\u636E\uFF0C\u4F7F\u7528 ${prevCount}\uFF08\u4E0A\u6708\u503C\uFF09`);
858
- }
796
+ try {
797
+ const count = await this.getMonthCompletedCount(
798
+ targetYear,
799
+ targetMonth,
800
+ prevCount
801
+ );
802
+ result[key] = count;
803
+ prevCount = count;
804
+ this.logger.info(`\u2713 ${key}: ${count} \u7BC7`);
805
+ } catch (error) {
806
+ result[key] = prevCount;
807
+ this.logger.warn(`${key}: \u65E0\u6570\u636E\uFF0C\u4F7F\u7528 ${prevCount}\uFF08\u4E0A\u6708\u503C\uFF09`);
859
808
  }
860
809
  }
861
810
  return result;
@@ -919,7 +868,6 @@ var commandFactories = {
919
868
  "build": () => new BuildCommand(),
920
869
  "create-notes": () => new CreateNoteCommand(),
921
870
  "dev": () => new DevCommand(),
922
- "fix-timestamps": () => new FixTimestampsCommand(),
923
871
  "init-sub-repo": () => new InitSubRepoCommand(),
924
872
  "init-toc": () => new InitTocCommand(),
925
873
  "help": () => new HelpCommand(),
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ConfigManager,
3
3
  getConfigManager
4
- } from "./chunk-5Y5IYS6C.js";
4
+ } from "./chunk-UJG3UU5X.js";
5
5
  export {
6
6
  ConfigManager,
7
7
  getConfigManager
@@ -7,10 +7,10 @@ import {
7
7
  UpdateNoteConfigCommand,
8
8
  generateAnchor,
9
9
  logger
10
- } from "../../chunk-5QN44ES5.js";
10
+ } from "../../chunk-XCWFZLER.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "../../chunk-5Y5IYS6C.js";
13
+ } from "../../chunk-UJG3UU5X.js";
14
14
 
15
15
  // vitepress/config/index.ts
16
16
  import fs3 from "fs";
@@ -49,6 +49,103 @@ import markdownItContainer from "markdown-it-container";
49
49
  import mila from "markdown-it-link-attributes";
50
50
  import markdownItTaskLists from "markdown-it-task-lists";
51
51
  import path from "path";
52
+
53
+ // vitepress/components/MindmapPreview/compat.ts
54
+ function cleanHeadingText(value) {
55
+ return value.trim().replace(/\s+#+\s*$/, "").trim();
56
+ }
57
+ function promoteLegacyRootList(body, rootTitle) {
58
+ const firstContentIndex = body.findIndex((line) => line.trim() !== "");
59
+ if (firstContentIndex < 0) return body;
60
+ const firstItem = body[firstContentIndex].match(/^[-+*]\s+(.+?)\s*$/);
61
+ if (!firstItem || cleanHeadingText(firstItem[1]) !== rootTitle) return body;
62
+ const descendants = body.slice(firstContentIndex + 1);
63
+ if (descendants.some((line) => line.trim() !== "" && !/^\s{2,}/.test(line))) return body;
64
+ return [
65
+ ...body.slice(0, firstContentIndex),
66
+ ...descendants.map((line) => line.replace(/^ {2}/, ""))
67
+ ];
68
+ }
69
+ function parseMarkmapFence(openLine) {
70
+ const fenceBody = openLine.trim().replace(/^`+\s*/, "");
71
+ const nameMatch = fenceBody.match(/^(mindmap|markmap)(?=\s|\{|\[|$)/);
72
+ if (!nameMatch) return {};
73
+ let rest = fenceBody.slice(nameMatch[1].length).trim();
74
+ const options = {};
75
+ const titleMatch = rest.match(/\[([^\]]+)\]/);
76
+ if (titleMatch) {
77
+ options.title = titleMatch[1].trim();
78
+ rest = `${rest.slice(0, titleMatch.index)} ${rest.slice((titleMatch.index ?? 0) + titleMatch[0].length)}`.trim();
79
+ }
80
+ const braceMatch = rest.match(/\{([^}]*)\}/);
81
+ const paramPart = braceMatch ? braceMatch[1].trim() : rest;
82
+ const tokens = paramPart.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
83
+ for (const [index, token] of tokens.entries()) {
84
+ if (/^\d+$/.test(token) && index === 0) {
85
+ options.initialExpandLevel = Number(token);
86
+ continue;
87
+ }
88
+ const pair = token.match(/^([^=:\s]+)\s*(?:=|:)\s*(.+)$/);
89
+ if (!pair || pair[1] !== "initialExpandLevel") continue;
90
+ const value = pair[2].replace(/^['"]|['"]$/g, "");
91
+ if (/^\d+$/.test(value)) options.initialExpandLevel = Number(value);
92
+ }
93
+ return options;
94
+ }
95
+ function parseMindmapReference(line) {
96
+ const match = line.trim().match(/^<<<\s+(.+?)\s*$/);
97
+ if (!match) return null;
98
+ let rest = match[1].trim();
99
+ let title;
100
+ const titleMatch = rest.match(/\s+\[([^\]]+)\]\s*$/);
101
+ if (titleMatch) {
102
+ title = cleanHeadingText(titleMatch[1]) || void 0;
103
+ rest = rest.slice(0, titleMatch.index).trim();
104
+ }
105
+ const path6 = rest.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, "$1$2").trim();
106
+ return path6 ? { path: path6, title } : null;
107
+ }
108
+ function normalizeMindmapMarkdown(source, options = {}) {
109
+ const lines = source.replace(/\r\n?/g, "\n").split("\n");
110
+ let existingTitle = "";
111
+ let rootIndex = -1;
112
+ for (let index = 0; index < lines.length; index++) {
113
+ const match = lines[index].match(/^\s{0,3}#(?!#)\s+(.+?)\s*$/);
114
+ if (!match) continue;
115
+ existingTitle = cleanHeadingText(match[1]);
116
+ rootIndex = index;
117
+ break;
118
+ }
119
+ const rootTitle = cleanHeadingText(options.title || existingTitle || options.defaultTitle || "root") || "root";
120
+ const body = [];
121
+ let headingDepth = null;
122
+ for (let index = 0; index < lines.length; index++) {
123
+ if (index === rootIndex) continue;
124
+ const line = lines[index];
125
+ const heading = line.match(/^\s{0,3}(#{2,6})\s+(.+?)\s*$/);
126
+ if (heading) {
127
+ headingDepth = heading[1].length - 2;
128
+ body.push(`${" ".repeat(headingDepth)}- ${cleanHeadingText(heading[2])}`);
129
+ continue;
130
+ }
131
+ const listItem = line.match(/^(\s*)([-+*])\s+(.+)$/);
132
+ if (listItem && headingDepth !== null) {
133
+ body.push(`${" ".repeat(headingDepth + 1)}${line}`);
134
+ continue;
135
+ }
136
+ body.push(line);
137
+ }
138
+ const normalizedBody = promoteLegacyRootList(body, rootTitle);
139
+ while (normalizedBody[0]?.trim() === "") normalizedBody.shift();
140
+ while (normalizedBody[normalizedBody.length - 1]?.trim() === "") normalizedBody.pop();
141
+ return normalizedBody.length > 0 ? `# ${rootTitle}
142
+
143
+ ${normalizedBody.join("\n")}
144
+ ` : `# ${rootTitle}
145
+ `;
146
+ }
147
+
148
+ // vitepress/configs/markdown.config.ts
52
149
  function esc(s = "") {
53
150
  return s.replace(
54
151
  /[&<>"']/g,
@@ -80,12 +177,11 @@ var simpleMermaidMarkdown = (md) => {
80
177
  return fence(tokens, index, options, env, slf);
81
178
  };
82
179
  };
83
- function configureMarkMapContainer(md) {
180
+ function configureMindmapContainer(md) {
84
181
  md.use(markdownItContainer, "markmap", {
85
182
  marker: "`",
86
183
  validate(params) {
87
- const p = (params || "").trim();
88
- return p.startsWith("markmap");
184
+ return (params || "").trim().startsWith("markmap");
89
185
  },
90
186
  render() {
91
187
  return "";
@@ -98,26 +194,34 @@ function configureMarkMapContainer(md) {
98
194
  for (let i = 0; i < tokens.length; i++) {
99
195
  const t = tokens[i];
100
196
  if (t.type === "container_markmap_open") {
197
+ const containerName = "markmap";
198
+ const closeType = "container_markmap_close";
101
199
  let j = i + 1;
102
- while (j < tokens.length && tokens[j].type !== "container_markmap_close")
200
+ while (j < tokens.length && tokens[j].type !== closeType)
103
201
  j++;
104
202
  if (j >= tokens.length) continue;
105
203
  const open = t;
106
204
  const startLine = open.map ? open.map[0] + 1 : null;
107
205
  const endLine = open.map ? open.map[1] - 1 : null;
108
206
  const params = {};
207
+ let explicitTitle;
109
208
  if (open.map && typeof open.map[0] === "number") {
110
209
  const openLine = (lines[open.map[0]] || "").trim();
210
+ const fenceOptions = parseMarkmapFence(openLine);
211
+ explicitTitle = fenceOptions.title;
111
212
  let paramPart = "";
112
213
  const braceMatch = openLine.match(/\{([^}]*)\}/);
113
214
  if (braceMatch) {
114
215
  paramPart = braceMatch[1].trim();
115
216
  } else {
116
217
  const after = openLine.replace(/^`+\s*/, "");
117
- if (after.startsWith("markmap")) {
118
- paramPart = after.slice("markmap".length).trim();
218
+ if (after.startsWith(containerName)) {
219
+ paramPart = after.slice(containerName.length).trim();
119
220
  }
120
221
  }
222
+ if (fenceOptions.initialExpandLevel !== void 0) {
223
+ params.initialExpandLevel = fenceOptions.initialExpandLevel;
224
+ }
121
225
  if (paramPart) {
122
226
  const tokenArr = paramPart.match(/"[^"]*"|'[^']*'|\S+/g) || [];
123
227
  let startIdx = 0;
@@ -153,9 +257,11 @@ function configureMarkMapContainer(md) {
153
257
  }
154
258
  }
155
259
  const firstNonEmptyLine = (content || "").split("\n").find((ln) => ln.trim() !== "") || "";
156
- const refMatch = firstNonEmptyLine.trim().match(/^<<<\s*(.+)$/);
157
- if (refMatch) {
158
- const refRaw = refMatch[1].trim().replace(/^['"]|['"]$/g, "");
260
+ const reference = parseMindmapReference(firstNonEmptyLine);
261
+ let referencedTitle;
262
+ if (reference) {
263
+ const refRaw = reference.path;
264
+ referencedTitle = reference.title;
159
265
  try {
160
266
  const env = state.env || {};
161
267
  const possibleRel = env.relativePath || env.path || env.filePath || env.file || "";
@@ -174,13 +280,13 @@ function configureMarkMapContainer(md) {
174
280
  content = fileContent;
175
281
  } catch (err) {
176
282
  const errorMsg = err instanceof Error ? err.message : String(err);
177
- content = `Failed to load referenced file: ${esc(
178
- String(refRaw)
179
- )}
180
-
181
- Error: ${esc(errorMsg)}`;
283
+ content = `- Failed to load referenced file: ${esc(String(refRaw))}
284
+ - Error: ${esc(errorMsg)}`;
182
285
  }
183
286
  }
287
+ content = normalizeMindmapMarkdown(content, {
288
+ title: explicitTitle || referencedTitle
289
+ });
184
290
  const encodedContent = encodeURIComponent(content.trim());
185
291
  let propsStr = `content="${encodedContent}"`;
186
292
  for (const [k, v] of Object.entries(params)) {
@@ -191,7 +297,7 @@ Error: ${esc(errorMsg)}`;
191
297
  propsStr += ` ${k}="${safe}"`;
192
298
  }
193
299
  }
194
- const html = `<MarkMap ${propsStr}></MarkMap>
300
+ const html = `<MindmapPreview ${propsStr}></MindmapPreview>
195
301
  `;
196
302
  const htmlToken = new state.Token("html_block", "", 0);
197
303
  htmlToken.content = html;
@@ -201,6 +307,40 @@ Error: ${esc(errorMsg)}`;
201
307
  return true;
202
308
  });
203
309
  }
310
+ function configureMindmapFence(md) {
311
+ const fence = md.renderer.rules.fence ? md.renderer.rules.fence.bind(md.renderer.rules) : () => "";
312
+ md.renderer.rules.fence = (tokens, index, options, env, slf) => {
313
+ const token = tokens[index];
314
+ const info = token.info.trim();
315
+ if (!/^mindmap(?=\s|\{|\[|$)/.test(info)) {
316
+ return fence(tokens, index, options, env, slf);
317
+ }
318
+ const fenceOptions = parseMarkmapFence(info);
319
+ let content = token.content;
320
+ const firstNonEmptyLine = content.split("\n").find((line) => line.trim()) ?? "";
321
+ const reference = parseMindmapReference(firstNonEmptyLine);
322
+ if (reference) {
323
+ const possibleRel = env?.relativePath || env?.path || env?.filePath || env?.file || "";
324
+ const refFullPath = path.isAbsolute(reference.path) ? reference.path : path.resolve(process.cwd(), possibleRel ? path.dirname(possibleRel) : "", reference.path);
325
+ try {
326
+ content = fs.readFileSync(refFullPath, "utf8");
327
+ } catch (error) {
328
+ const message = error instanceof Error ? error.message : String(error);
329
+ content = `- Failed to load referenced file: ${reference.path}
330
+ - Error: ${message}`;
331
+ }
332
+ }
333
+ content = normalizeMindmapMarkdown(content, {
334
+ title: fenceOptions.title || reference?.title
335
+ });
336
+ const props = [
337
+ `content="${encodeURIComponent(content.trim())}"`,
338
+ fenceOptions.initialExpandLevel === void 0 ? "" : `:initialExpandLevel="${fenceOptions.initialExpandLevel}"`
339
+ ].filter(Boolean).join(" ");
340
+ return `<MindmapPreview ${props}></MindmapPreview>
341
+ `;
342
+ };
343
+ }
204
344
  function configureSwiperContainer(md) {
205
345
  let __tn_swiper_uid = 0;
206
346
  let __tn_rules_stack = [];
@@ -269,7 +409,8 @@ function getMarkdownConfig() {
269
409
  return true;
270
410
  });
271
411
  simpleMermaidMarkdown(md);
272
- configureMarkMapContainer(md);
412
+ configureMindmapContainer(md);
413
+ configureMindmapFence(md);
273
414
  md.use(markdownItTaskLists);
274
415
  md.use(mila, {
275
416
  attrs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tnotesjs/core",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "TNotes 知识库核心框架 —— 基于 VitePress 的笔记管理系统",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.17.1",
@@ -49,7 +49,7 @@
49
49
  "vue": "^3.5.0"
50
50
  },
51
51
  "dependencies": {
52
- "d3": "^7.9.0",
52
+ "@tnotesjs/mindmap-core": "^0.2.0",
53
53
  "echarts": "^6.0.0",
54
54
  "github-slugger": "^2.0.0",
55
55
  "markdown-it-container": "^4.0.0",
@@ -57,11 +57,8 @@
57
57
  "markdown-it-mathjax3": "^4.3.2",
58
58
  "markdown-it-task-lists": "^2.1.1",
59
59
  "marked": "^15.0.11",
60
- "minisearch": "^7.2.0",
61
- "markmap-lib": "^0.18.12",
62
- "markmap-toolbar": "^0.18.12",
63
- "markmap-view": "^0.18.12",
64
60
  "mermaid": "^11.5.0",
61
+ "minisearch": "^7.2.0",
65
62
  "sass-embedded": "^1.90.0",
66
63
  "swiper": "^11.2.1",
67
64
  "uuid": "^11.1.0",
package/services/index.ts CHANGED
@@ -9,7 +9,6 @@ export { GitService } from './git'
9
9
  export { NoteService } from './note'
10
10
  export { ReadmeService } from './readme'
11
11
  export { TocService } from './toc'
12
- export { TimestampService } from './timestamp'
13
12
  export { VitepressService } from './vitepress'
14
13
  export { InitSubRepoService } from './init-sub-repo'
15
14
  export * from './init-sub-repo/initSubRepoLogic'
@@ -189,7 +189,6 @@ export function buildPackageJsonTemplate(
189
189
  'tn:build': 'tnotes --build',
190
190
  'tn:create-notes': 'tnotes --create-notes',
191
191
  'tn:dev': 'tnotes --dev',
192
- 'tn:fix-timestamps': 'tnotes --fix-timestamps',
193
192
  'tn:help': 'tnotes --help',
194
193
  'tn:init-sub-repo': 'tnotes --init-sub-repo',
195
194
  'tn:preview': 'tnotes --preview',
@@ -133,7 +133,6 @@ export class NoteService {
133
133
  done: false,
134
134
  category,
135
135
  enableDiscussions,
136
- // created_at / updated_at 由 tn:push 时 fix-timestamps 自动写入
137
136
  }
138
137
  this.noteManager.writeNoteConfig(configPath, config)
139
138
 
package/types/config.ts CHANGED
@@ -54,7 +54,4 @@ export interface RootItem {
54
54
  completed_notes_count: Record<string, number>
55
55
  details: string
56
56
  link: string
57
- created_at?: number
58
- updated_at?: number
59
- days_since_birth?: number
60
57
  }
package/types/note.ts CHANGED
@@ -16,8 +16,6 @@ export interface NoteConfig {
16
16
  category?: string
17
17
  enableDiscussions: boolean
18
18
  description?: string // 笔记简介(一句话描述)
19
- created_at?: number
20
- updated_at?: number
21
19
  }
22
20
 
23
21
  /**
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 128 128">
2
+ <path d="M0 0h128v128H0z" fill="none" />
3
+ <path fill="#656cff" fill-rule="evenodd" d="M117.9 30.289L66.664.713a5.32 5.32 0 0 0-5.323 0L10.09 30.29a4.48 4.48 0 0 0-2.234 3.872v59.663c0 1.6.853 3.077 2.24 3.878l51.24 29.586a5.33 5.33 0 0 0 5.324 0l51.246-29.586a4.48 4.48 0 0 0 2.24-3.878V34.166a4.48 4.48 0 0 0-2.24-3.872zm-3.216 6.272l-49.47 85.681c-.337.576-1.217.341-1.217-.325V65.81a3.15 3.15 0 0 0-1.573-2.72l-48.59-28.055c-.571-.331-.336-1.216.33-1.216h98.94c1.409 0 2.284 1.525 1.58 2.741" />
4
+ </svg>
@@ -14,6 +14,7 @@ export { default as icon__close } from './icon__close.svg'
14
14
  export { default as icon__collapse } from './icon__collapse.svg'
15
15
  export { default as icon__confirm } from './icon__confirm.svg'
16
16
  export { default as icon__copy } from './icon__copy.svg'
17
+ export { default as icon__cursor } from './icon__cursor.svg'
17
18
  export { default as icon__focus } from './icon__focus.svg'
18
19
  export { default as icon__fold } from './icon__fold.svg'
19
20
  export { default as icon__folder } from './icon__folder.svg'
@@ -107,22 +107,6 @@ vitepress/components/Layout/AboutPanel.vue
107
107
  </div>
108
108
  </div>
109
109
 
110
- <!-- 首次提交时间 -->
111
- <div class="timeLine" title="首次提交时间">
112
- <div class="timeLabel"><strong>⌛️ 首次提交</strong></div>
113
- <div class="timeValue">
114
- {{ formatOptionalDate(modalCreatedAt) }}
115
- </div>
116
- </div>
117
-
118
- <!-- 最近提交时间 -->
119
- <div class="timeLine" title="最近提交时间">
120
- <div class="timeLabel"><strong>⌛️ 最近提交</strong></div>
121
- <div class="timeValue">
122
- {{ formatOptionalDate(modalUpdatedAt) }}
123
- </div>
124
- </div>
125
-
126
110
  <!-- GitHub -->
127
111
  <div
128
112
  class="timeLine"
@@ -196,8 +180,6 @@ vitepress/components/Layout/AboutPanel.vue
196
180
  <script setup lang="ts">
197
181
  import { computed } from 'vue'
198
182
 
199
- import { formatDate } from '../utils'
200
-
201
183
  const props = defineProps<{
202
184
  isHomeReadme: boolean
203
185
  currentNoteId: string | null
@@ -207,8 +189,6 @@ const props = defineProps<{
207
189
  editableNoteStatus: boolean
208
190
  editableDiscussionsEnabled: boolean
209
191
  titleError: string
210
- modalCreatedAt: number | undefined
211
- modalUpdatedAt: number | undefined
212
192
  modalGithubUrl: string
213
193
  modalGithubPageUrl: string
214
194
  completionPercentage: number | null
@@ -263,10 +243,6 @@ function onDescriptionInput() {
263
243
  function onConfigChange() {
264
244
  emit('configChange')
265
245
  }
266
-
267
- function formatOptionalDate(timestamp: number | undefined) {
268
- return timestamp === undefined ? '-' : formatDate(timestamp)
269
- }
270
246
  </script>
271
247
 
272
248
  <style scoped lang="scss">
@@ -14,10 +14,10 @@ vitepress/components/Layout/DocBeforeControls.vue
14
14
  <div class="vscodeBox pcOnly" v-show="vscodeNotesDir">
15
15
  <a
16
16
  :href="vscodeNotesDir"
17
- title="open in vscode"
17
+ :title="openCurrentTitle"
18
18
  target="_blank"
19
19
  >
20
- <img :src="icon__vscode" alt="open in vscode" />
20
+ <img :src="localIdeIcon" :alt="openCurrentTitle" />
21
21
  </a>
22
22
  </div>
23
23
  <div class="contentToggleBox pcOnly">
@@ -65,13 +65,7 @@ vitepress/components/Layout/DocBeforeControls.vue
65
65
  </button>
66
66
  </div>
67
67
  <!-- 单个图标,点击打开 modal,只在有笔记数据的页面显示 -->
68
- <div
69
- class="aboutBtn"
70
- v-show="
71
- (currentNoteId && created_at && updated_at) ||
72
- (isHomeReadme && homeReadmeCreatedAt && homeReadmeUpdatedAt)
73
- "
74
- >
68
+ <div class="aboutBtn" v-show="currentNoteId || isHomeReadme">
75
69
  <button
76
70
  class="aboutIconButton"
77
71
  @click="$emit('open-time-modal')"
@@ -93,24 +87,22 @@ import ToggleFullContent from './ToggleFullContent.vue'
93
87
  import ToggleSidebar from './ToggleSidebar.vue'
94
88
  import {
95
89
  icon__github,
96
- icon__vscode,
97
90
  icon__fold,
98
91
  icon__clipboard,
99
92
  } from '../../assets/icons'
93
+ import { useLocalIde } from '../composables/useLocalIde'
100
94
 
101
95
  const props = defineProps<{
102
96
  isFullContentMode: boolean
103
97
  vscodeNotesDir: string
104
98
  isHomeReadme: boolean
105
99
  currentNoteId: string | null
106
- created_at: string | undefined
107
- updated_at: string | undefined
108
- homeReadmeCreatedAt: string | undefined
109
- homeReadmeUpdatedAt: string | undefined
110
100
  timeModalOpen: boolean
111
101
  allCollapsed: boolean
112
102
  }>()
113
103
 
104
+ const { icon: localIdeIcon, openCurrentTitle } = useLocalIde()
105
+
114
106
  const showCopyToast = ref(false)
115
107
  let copyToastTimer: ReturnType<typeof setTimeout> | null = null
116
108