@tnotesjs/core 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/commands/BaseCommand.ts +58 -0
  2. package/commands/build/BuildCommand.ts +25 -0
  3. package/commands/build/PreviewCommand.ts +29 -0
  4. package/commands/build/index.ts +8 -0
  5. package/commands/dev/DevCommand.ts +75 -0
  6. package/commands/dev/index.ts +7 -0
  7. package/commands/git/PullCommand.ts +25 -0
  8. package/commands/git/PushCommand.ts +64 -0
  9. package/commands/git/index.ts +8 -0
  10. package/commands/index.ts +11 -0
  11. package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
  12. package/commands/init-sub-repo/index.ts +1 -0
  13. package/commands/misc/HelpCommand.ts +104 -0
  14. package/commands/misc/index.ts +7 -0
  15. package/commands/models.ts +87 -0
  16. package/commands/note/CreateNoteCommand.ts +160 -0
  17. package/commands/note/RenameNoteCommand.ts +147 -0
  18. package/commands/note/UpdateNoteConfigCommand.ts +78 -0
  19. package/commands/note/index.ts +9 -0
  20. package/commands/registry.ts +47 -0
  21. package/commands/update/UpdateCommand.ts +219 -0
  22. package/commands/update/index.ts +7 -0
  23. package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
  24. package/commands/update-completed-count/index.ts +5 -0
  25. package/config/templates.ts +0 -5
  26. package/dist/{chunk-5F5RJ2HV.cjs → chunk-7FEANCT6.cjs} +16 -7
  27. package/dist/chunk-AGVER5MX.cjs +1746 -0
  28. package/dist/{chunk-TJ4527KA.cjs → chunk-BL22KD4V.cjs} +324 -950
  29. package/dist/{chunk-56PKXCOX.js → chunk-GWG75A5M.js} +13 -4
  30. package/dist/{chunk-DUTS75WQ.js → chunk-XNO3PCEH.js} +374 -1000
  31. package/dist/chunk-ZEQS64PE.js +1746 -0
  32. package/dist/cli/index.cjs +76 -155
  33. package/dist/cli/index.js +10 -89
  34. package/dist/markdown/index.cjs +8 -12
  35. package/dist/markdown/index.d.cts +1 -1
  36. package/dist/markdown/index.d.ts +1 -1
  37. package/dist/markdown/index.js +7 -11
  38. package/dist/{types-CwBWwNVv.d.ts → types-C_d0fFeD.d.ts} +8 -1
  39. package/dist/{types-CpsEy8lA.d.cts → types-MRBGgJTX.d.cts} +8 -1
  40. package/dist/vitepress/config/index.cjs +402 -178
  41. package/dist/vitepress/config/index.js +392 -168
  42. package/dist/workspace/index.cjs +4 -1202
  43. package/dist/workspace/index.d.cts +2 -2
  44. package/dist/workspace/index.d.ts +2 -2
  45. package/dist/workspace/index.js +4 -1202
  46. package/markdown/components.ts +86 -0
  47. package/markdown/index.ts +17 -0
  48. package/markdown/noteFormatter.test.ts +44 -0
  49. package/markdown/noteFormatter.ts +237 -0
  50. package/package.json +7 -3
  51. package/services/file-watcher/folderChangeHandler.ts +19 -15
  52. package/services/file-watcher/globalUpdateCoordinator.ts +21 -27
  53. package/services/file-watcher/service.ts +1 -6
  54. package/services/index.ts +0 -1
  55. package/services/note/service.ts +4 -6
  56. package/services/readme/service.ts +11 -18
  57. package/services/reconcileToc.ts +20 -0
  58. package/utils/index.ts +0 -9
  59. package/{services/toc → utils}/moveTocInside.test.ts +1 -1
  60. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
  61. package/vitepress/components/EnWordList/EnWordList.vue +16 -662
  62. package/vitepress/components/Footprints/Footprints.vue +15 -537
  63. package/vitepress/components/Mermaid/Mermaid.vue +13 -588
  64. package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
  65. package/vitepress/components/MindmapPreview/markdown.ts +1 -1
  66. package/vitepress/components/NotesTable/NotesTable.vue +11 -130
  67. package/vitepress/components/constants.ts +2 -2
  68. package/vitepress/components/sidebar.data.ts +8 -5
  69. package/vitepress/configs/markdown.config.ts +170 -26
  70. package/vitepress/plugins/sidebarStructurePlugin.ts +242 -145
  71. package/vitepress/theme/index.ts +9 -13
  72. package/vitepress/theme/styles/base.scss +15 -0
  73. package/workspace/atomic.ts +113 -0
  74. package/workspace/errors.ts +27 -0
  75. package/workspace/index.ts +40 -0
  76. package/workspace/mutationQueue.ts +28 -0
  77. package/workspace/paths.ts +64 -0
  78. package/workspace/reconcile.test.ts +300 -0
  79. package/workspace/reconcile.ts +95 -0
  80. package/workspace/scanner.ts +292 -0
  81. package/workspace/types.ts +224 -0
  82. package/workspace/workspace.test.ts +333 -0
  83. package/workspace/workspace.ts +1020 -0
  84. package/dist/chunk-3R7QSABB.cjs +0 -502
  85. package/dist/chunk-CZXRQPFJ.js +0 -11
  86. package/dist/chunk-HXED7KVT.cjs +0 -11
  87. package/dist/chunk-U6IBLREL.js +0 -502
  88. package/services/toc/index.ts +0 -5
  89. package/services/toc/service.ts +0 -759
  90. package/utils/migrateReadmeToToc.test.ts +0 -111
  91. package/utils/migrateReadmeToToc.ts +0 -135
  92. package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
@@ -4,18 +4,21 @@
4
4
  * Markdown 配置
5
5
  */
6
6
 
7
- import fs from 'fs'
8
7
  import markdownItContainer from 'markdown-it-container'
9
8
  import mila from 'markdown-it-link-attributes'
10
9
  import markdownItTaskLists from 'markdown-it-task-lists'
11
- import path from 'path'
12
10
 
13
11
  import { generateAnchor } from '../../utils'
14
12
  import {
15
13
  normalizeMindmapMarkdown,
16
14
  parseMindmapFence,
17
- parseMindmapReference,
18
15
  } from '../components/MindmapPreview/markdown'
16
+ // Pure helper only — do not import `@tnotesjs/ui` root from Node config
17
+ // (package entry is .ts; Node cannot strip types under node_modules).
18
+ import {
19
+ parseFootprintsDatetime,
20
+ parseFootprintsSource,
21
+ } from '@tnotesjs/ui/footprints-parse'
19
22
 
20
23
  import type MarkdownIt from 'markdown-it'
21
24
  import type { MarkdownOptions } from 'vitepress'
@@ -47,15 +50,18 @@ const simpleMermaidMarkdown = (md: MarkdownIt) => {
47
50
 
48
51
  md.renderer.rules.fence = (tokens, index, options, env, slf) => {
49
52
  const token = tokens[index]
53
+ const parts = token.info.trim().split(/\s+/).filter(Boolean)
50
54
 
51
- // 检查是否为 mermaid 代码块
52
- if (token.info.trim() === 'mermaid') {
55
+ // `mermaid` or `mermaid center`
56
+ if (parts[0] === 'mermaid') {
53
57
  try {
58
+ const centered = parts.slice(1).some((part) => part.toLowerCase() === 'center')
54
59
  const key = `mermaid-${Date.now()}-${Math.random()
55
60
  .toString(36)
56
61
  .substr(2, 9)}`
57
62
  const content = token.content
58
- return `<Mermaid id="${key}" graph="${encodeURIComponent(content)}" />`
63
+ const centerAttr = centered ? ' :center="true"' : ''
64
+ return `<Mermaid id="${key}" graph="${encodeURIComponent(content)}"${centerAttr} />`
59
65
  } catch (err) {
60
66
  return `<pre>${err}</pre>`
61
67
  }
@@ -81,25 +87,10 @@ function configureMindmapFence(md: MarkdownIt) {
81
87
  const info = token.info.trim()
82
88
  const fenceOptions = parseMindmapFence(info)
83
89
  if (!fenceOptions) return fence(tokens, index, options, env, slf)
84
- let content = token.content
85
- const firstNonEmptyLine = content.split('\n').find((line) => line.trim()) ?? ''
86
- const reference = parseMindmapReference(firstNonEmptyLine)
87
-
88
- if (reference) {
89
- const possibleRel = env?.relativePath || env?.path || env?.filePath || env?.file || ''
90
- const refFullPath = path.isAbsolute(reference.path)
91
- ? reference.path
92
- : path.resolve(process.cwd(), possibleRel ? path.dirname(possibleRel) : '', reference.path)
93
- try {
94
- content = fs.readFileSync(refFullPath, 'utf8')
95
- } catch (error) {
96
- const message = error instanceof Error ? error.message : String(error)
97
- content = `- Failed to load referenced file: ${reference.path}\n - Error: ${message}`
98
- }
99
- }
100
-
101
- content = normalizeMindmapMarkdown(content, {
102
- title: fenceOptions.title || reference?.title,
90
+ // Mindmap nodes must live in the fence body. External `<<<` includes are
91
+ // no longer resolved here (body-level VitePress snippets remain separate).
92
+ const content = normalizeMindmapMarkdown(token.content, {
93
+ title: fenceOptions.title,
103
94
  })
104
95
  const props = [
105
96
  `content="${encodeURIComponent(content.trim())}"`,
@@ -107,7 +98,7 @@ function configureMindmapFence(md: MarkdownIt) {
107
98
  ? ''
108
99
  : `:initialExpandLevel="${fenceOptions.initialExpandLevel}"`,
109
100
  ].filter(Boolean).join(' ')
110
- return `<MindmapPreview ${props}></MindmapPreview>\n`
101
+ return `<Mindmap ${props}></Mindmap>\n`
111
102
  }
112
103
  }
113
104
 
@@ -187,6 +178,156 @@ function configureSwiperContainer(md: MarkdownIt) {
187
178
  })
188
179
  }
189
180
 
181
+ /** Escape text for HTML element bodies (not attributes). */
182
+ function escapeHtmlText(s: string) {
183
+ return s.replace(
184
+ /[&<>"']/g,
185
+ (ch) =>
186
+ ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch] as string
187
+ )
188
+ }
189
+
190
+ /**
191
+ * `::: footprints 2025-01-22 23:47` → Footprints Vue block.
192
+ * Prefer token-based extraction (source line maps are unreliable in VitePress).
193
+ * Text goes through encoded props; images use a slot so Vite rewrites asset URLs.
194
+ */
195
+ function extractFootprintsPayloadFromTokens(tokens: any[], idx: number) {
196
+ const meta = String(tokens[idx].info || '')
197
+ .trim()
198
+ .replace(/^footprints\s*/i, '')
199
+ const times = parseFootprintsDatetime(meta)
200
+ const paragraphs: string[] = []
201
+ const images: string[] = []
202
+ let otherInfo = ''
203
+ let inOther = false
204
+
205
+ for (let i = idx + 1; i < tokens.length; i++) {
206
+ const t = tokens[i]
207
+ if (t.type === 'container_footprints_close') break
208
+ if (t.type !== 'inline') continue
209
+
210
+ const childImgs: string[] = []
211
+ if (Array.isArray(t.children)) {
212
+ for (const c of t.children) {
213
+ if (c.type === 'image') {
214
+ const src = c.attrGet?.('src') || c.attrs?.find((a: string[]) => a[0] === 'src')?.[1]
215
+ if (src) childImgs.push(src)
216
+ }
217
+ }
218
+ }
219
+
220
+ const content = String(t.content || '').trim()
221
+ if (content === '---') {
222
+ inOther = true
223
+ continue
224
+ }
225
+ if (childImgs.length) {
226
+ // Treat as image block when the inline is image-only (ignore bare alt text).
227
+ const withoutImgs = content.replace(/!\[[^\]]*\]\([^)]+\)/g, '').trim()
228
+ if (!withoutImgs) {
229
+ images.push(...childImgs)
230
+ continue
231
+ }
232
+ }
233
+ const imgOnly = content.match(/^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)$/)
234
+ if (imgOnly) {
235
+ images.push(imgOnly[2])
236
+ continue
237
+ }
238
+ if (!content) continue
239
+ if (inOther) otherInfo = otherInfo ? `${otherInfo}\n${content}` : content
240
+ else paragraphs.push(content)
241
+ }
242
+
243
+ return { times, paragraphs, images, otherInfo }
244
+ }
245
+
246
+ function configureFootprintsContainer(md: MarkdownIt) {
247
+ md.use(markdownItContainer, 'footprints', {
248
+ validate: (params: string) => /^footprints(\s|$)/i.test(params.trim()),
249
+ render: (tokens: any[], idx: number, _opts: unknown, env: any) => {
250
+ if (tokens[idx].nesting !== 1) return ''
251
+
252
+ // Extract BEFORE hiding/clearing tokens.
253
+ let payload = extractFootprintsPayloadFromTokens(tokens, idx)
254
+
255
+ // Optional enrichment from source slice when tokens missed content.
256
+ const startLine = tokens[idx].map?.[0] ?? 0
257
+ let endLine = startLine
258
+ for (let i = idx + 1; i < tokens.length; i++) {
259
+ if (tokens[i].type === 'container_footprints_close') {
260
+ endLine = tokens[i].map?.[0] ?? endLine
261
+ break
262
+ }
263
+ }
264
+ const raw = String(env?.src ?? env?.source ?? '')
265
+ if (raw && endLine > startLine) {
266
+ const slice = raw.split(/\r?\n/).slice(startLine, endLine + 1).join('\n')
267
+ const fromSource = parseFootprintsSource(
268
+ slice.includes(':::') ? slice : `::: ${tokens[idx].info}\n${slice}\n:::`
269
+ )
270
+ if (!payload.paragraphs.length && fromSource.paragraphs.length) {
271
+ payload = { ...payload, paragraphs: fromSource.paragraphs }
272
+ }
273
+ if (!payload.images.length && fromSource.images.length) {
274
+ payload = { ...payload, images: fromSource.images }
275
+ }
276
+ if (!payload.otherInfo && fromSource.otherInfo) {
277
+ payload = { ...payload, otherInfo: fromSource.otherInfo }
278
+ }
279
+ if (!payload.times.length && fromSource.times.length) {
280
+ payload = { ...payload, times: fromSource.times }
281
+ }
282
+ }
283
+
284
+ for (let i = idx + 1; i < tokens.length; i++) {
285
+ if (tokens[i].type === 'container_footprints_close') break
286
+ tokens[i].hidden = true
287
+ tokens[i].content = ''
288
+ if (Array.isArray(tokens[i].children)) {
289
+ for (const child of tokens[i].children) {
290
+ child.hidden = true
291
+ child.content = ''
292
+ }
293
+ tokens[i].children = []
294
+ }
295
+ }
296
+
297
+ const enc = (value: unknown) =>
298
+ encodeURIComponent(JSON.stringify(value)).replace(/'/g, '%27')
299
+ const bindExpr = (value: unknown) =>
300
+ "JSON.parse(decodeURIComponent('" + enc(value) + "'))"
301
+ const imageSlot = payload.images
302
+ .map((src, i) => {
303
+ return (
304
+ '<img src="' +
305
+ escapeHtmlText(src) +
306
+ '" @click="openModal(' +
307
+ String(i) +
308
+ ')" />'
309
+ )
310
+ })
311
+ .join('\n')
312
+ const openTag =
313
+ '<Footprints :times="' +
314
+ bindExpr(payload.times) +
315
+ '" :paragraphs="' +
316
+ bindExpr(payload.paragraphs) +
317
+ '" :other-info="' +
318
+ bindExpr(payload.otherInfo) +
319
+ '">'
320
+ if (!payload.images.length) return openTag + '</Footprints>\n'
321
+ return (
322
+ openTag +
323
+ '\n<template #image-list="{ openModal }">\n' +
324
+ imageSlot +
325
+ '\n</template>\n</Footprints>\n'
326
+ )
327
+ },
328
+ })
329
+ }
330
+
190
331
  /**
191
332
  * Markdown 配置
192
333
  */
@@ -220,6 +361,9 @@ export function getMarkdownConfig(): MarkdownOptions {
220
361
 
221
362
  // 添加 Swiper 支持
222
363
  configureSwiperContainer(md)
364
+
365
+ // Footprints 容器(Type A)
366
+ configureFootprintsContainer(md)
223
367
  },
224
368
  anchor: {
225
369
  slugify: generateAnchor,