@tnotesjs/core 0.4.2 → 0.6.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 (48) hide show
  1. package/dist/chunk-3R7QSABB.cjs +502 -0
  2. package/dist/chunk-CZXRQPFJ.js +11 -0
  3. package/dist/{chunk-XCWFZLER.js → chunk-DUTS75WQ.js} +27 -493
  4. package/dist/chunk-HXED7KVT.cjs +11 -0
  5. package/dist/chunk-IHFUHB4J.cjs +176 -0
  6. package/dist/chunk-MZIXIY2Y.cjs +216 -0
  7. package/dist/chunk-TJ4527KA.cjs +5264 -0
  8. package/dist/chunk-U6IBLREL.js +502 -0
  9. package/dist/chunk-UNFSW5C2.js +176 -0
  10. package/dist/cli/index.cjs +936 -0
  11. package/dist/cli/index.d.cts +2 -0
  12. package/dist/cli/index.d.ts +2 -0
  13. package/dist/cli/index.js +9 -6
  14. package/dist/index.cjs +8 -0
  15. package/dist/index.d.cts +56 -0
  16. package/dist/index.d.ts +56 -0
  17. package/dist/markdown/index.cjs +82 -0
  18. package/dist/markdown/index.d.cts +36 -0
  19. package/dist/markdown/index.d.ts +36 -0
  20. package/dist/markdown/index.js +82 -0
  21. package/dist/note-oGm44Rw8.d.cts +84 -0
  22. package/dist/note-oGm44Rw8.d.ts +84 -0
  23. package/dist/types-CpsEy8lA.d.cts +221 -0
  24. package/dist/types-CwBWwNVv.d.ts +221 -0
  25. package/dist/vitepress/config/index.cjs +1669 -0
  26. package/dist/vitepress/config/index.d.cts +12 -0
  27. package/dist/vitepress/config/index.d.ts +12 -0
  28. package/dist/vitepress/config/index.js +26 -192
  29. package/dist/workspace/index.cjs +1207 -0
  30. package/dist/workspace/index.d.cts +14 -0
  31. package/dist/workspace/index.d.ts +14 -0
  32. package/dist/workspace/index.js +1207 -0
  33. package/package.json +16 -3
  34. package/vitepress/components/MindmapPreview/MindmapPreview.vue +189 -34
  35. package/vitepress/components/MindmapPreview/MindmapViewIcon.vue +41 -0
  36. package/vitepress/components/MindmapPreview/markdown.test.ts +67 -0
  37. package/vitepress/components/MindmapPreview/markdown.ts +83 -0
  38. package/vitepress/components/MindmapPreview/wheelInteraction.test.ts +23 -0
  39. package/vitepress/components/MindmapPreview/wheelInteraction.ts +7 -0
  40. package/vitepress/components/Settings/Settings.vue +3 -3
  41. package/vitepress/components/SidebarCard/SidebarCard.vue +3 -3
  42. package/vitepress/components/constants.ts +0 -5
  43. package/vitepress/configs/markdown.config.ts +6 -199
  44. package/vitepress/plugins/localSearchReindexLogic.ts +11 -3
  45. package/vitepress/theme/index.ts +0 -2
  46. package/vitepress/components/MindmapPreview/compat.test.ts +0 -93
  47. package/vitepress/components/MindmapPreview/compat.ts +0 -128
  48. package/vitepress/components/MindmapPreview/legacyCorpus.test.ts +0 -57
@@ -13,9 +13,9 @@ import path from 'path'
13
13
  import { generateAnchor } from '../../utils'
14
14
  import {
15
15
  normalizeMindmapMarkdown,
16
- parseMarkmapFence,
16
+ parseMindmapFence,
17
17
  parseMindmapReference,
18
- } from '../components/MindmapPreview/compat'
18
+ } from '../components/MindmapPreview/markdown'
19
19
 
20
20
  import type MarkdownIt from 'markdown-it'
21
21
  import type { MarkdownOptions } from 'vitepress'
@@ -70,196 +70,7 @@ const simpleMermaidMarkdown = (md: MarkdownIt) => {
70
70
  }
71
71
  }
72
72
 
73
- /**
74
- * Mindmap 容器配置(兼容旧的 markmap 围栏名)
75
- */
76
- function configureMindmapContainer(md: MarkdownIt) {
77
- md.use(markdownItContainer, 'markmap', {
78
- marker: '`',
79
- validate(params: string) {
80
- return (params || '').trim().startsWith('markmap')
81
- },
82
- render() {
83
- return ''
84
- },
85
- })
86
-
87
- // 在 core 阶段把整个 container 区间替换成一个 html_block(MarkMap 组件标签)
88
- // 这样渲染时就只输出 <MarkMap ...>,中间的列表 token 已被移除
89
- md.core.ruler.after('block', 'tn_replace_markmap_container', (state) => {
90
- const src = state.env.source || ''
91
- const lines = src.split('\n')
92
- const tokens = state.tokens
93
-
94
- for (let i = 0; i < tokens.length; i++) {
95
- const t = tokens[i]
96
- if (t.type === 'container_markmap_open') {
97
- const containerName = 'markmap'
98
- const closeType = 'container_markmap_close'
99
- // 找到对应的 close token
100
- let j = i + 1
101
- while (
102
- j < tokens.length &&
103
- tokens[j].type !== closeType
104
- )
105
- j++
106
- if (j >= tokens.length) continue // safety
107
-
108
- // 使用 token.map 提取源文件对应行(open.token.map 存着 container 起止行)
109
- const open = t
110
- const startLine = open.map ? open.map[0] + 1 : null
111
- const endLine = open.map ? open.map[1] - 1 : null
112
-
113
- // 1) 从开头 fence 行解析参数(支持 `{a=1 b="x"}`、`a=1 b="x"`,并支持单个数字 shorthand)
114
- const params: { [key: string]: any; initialExpandLevel?: number } = {}
115
- let explicitTitle: string | undefined
116
-
117
- if (open.map && typeof open.map[0] === 'number') {
118
- const openLine = (lines[open.map[0]] || '').trim()
119
- const fenceOptions = parseMarkmapFence(openLine)
120
- explicitTitle = fenceOptions.title
121
- let paramPart = ''
122
-
123
- // 优先匹配大括号形式 ```markmap{...}
124
- const braceMatch = openLine.match(/\{([^}]*)\}/)
125
- if (braceMatch) {
126
- paramPart = braceMatch[1].trim()
127
- } else {
128
- // 否则尝试去掉前缀 ``` 和 markmap,剩下的作为参数部分
129
- const after = openLine.replace(/^`+\s*/, '')
130
- if (after.startsWith(containerName)) {
131
- paramPart = after.slice(containerName.length).trim()
132
- }
133
- }
134
- if (fenceOptions.initialExpandLevel !== undefined) {
135
- params.initialExpandLevel = fenceOptions.initialExpandLevel
136
- }
137
-
138
- if (paramPart) {
139
- // 使用正则按 token 切分:保持用引号包裹的片段为单个 token(支持包含空格)
140
- const tokenArr = paramPart.match(/"[^"]*"|'[^']*'|\S+/g) || []
141
-
142
- // 如果第一个 token 是纯数字,把它当作 initialExpandLevel
143
- let startIdx = 0
144
- if (tokenArr.length > 0 && /^\d+$/.test(tokenArr[0] as string)) {
145
- params.initialExpandLevel = Number(tokenArr[0])
146
- startIdx = 1
147
- }
148
-
149
- // 解析剩余 token 为 key=value(支持 key=val 或 key:val)
150
- for (let k = startIdx; k < tokenArr.length; k++) {
151
- const pair = tokenArr[k]
152
- if (!pair) continue
153
- const m = pair.match(/^([^=:\s]+)\s*(=|:)\s*(.+)$/)
154
- if (m) {
155
- const key = m[1]
156
- let val = m[3]
157
-
158
- // 去除外层引号(若存在)
159
- if (
160
- (/^".*"$/.test(val) && val.length >= 2) ||
161
- (/^'.*'$/.test(val) && val.length >= 2)
162
- ) {
163
- val = val.slice(1, -1)
164
- } else if (/^\d+$/.test(val)) {
165
- // 纯数字转字符串
166
- val = String(Number(val))
167
- }
168
-
169
- params[key] = val
170
- }
171
- }
172
- }
173
- }
174
-
175
- // 2) 提取内容(支持文件引用语法 `<<< ./path/to/file.md`)
176
- let content = ''
177
- if (startLine !== null && endLine !== null) {
178
- for (let k = startLine; k <= endLine && k < lines.length; k++) {
179
- content += lines[k] + '\n'
180
- }
181
- } else {
182
- // 回退:如果没有 map 信息,尝试用中间 tokens 拼接文本
183
- for (let k = i + 1; k < j; k++) {
184
- content += tokens[k].content || ''
185
- }
186
- }
187
-
188
- // --- 检查第一非空行是否为引用语法 ---
189
- const firstNonEmptyLine =
190
- (content || '').split('\n').find((ln) => ln.trim() !== '') || ''
191
- const reference = parseMindmapReference(firstNonEmptyLine)
192
- let referencedTitle: string | undefined
193
- if (reference) {
194
- const refRaw = reference.path
195
- referencedTitle = reference.title
196
-
197
- // 尝试同步读取文件内容(兼容常见 Node 环境)
198
- try {
199
- // 尝试根据当前 markdown 文件位置解析相对路径
200
- const env = state.env || {}
201
- const possibleRel =
202
- env.relativePath || env.path || env.filePath || env.file || ''
203
- let refFullPath = refRaw
204
-
205
- if (!path.isAbsolute(refRaw)) {
206
- if (possibleRel) {
207
- // 将 relativePath 视作相对于项目根的路径(例如 'notes/foo/bar.md'),取其目录
208
- const currentDir = path.dirname(possibleRel)
209
- // 解析到 process.cwd()
210
- refFullPath = path.resolve(process.cwd(), currentDir, refRaw)
211
- } else {
212
- // 没有相对文件信息,则相对于项目根解析
213
- refFullPath = path.resolve(process.cwd(), refRaw)
214
- }
215
- } else {
216
- // 绝对路径直接使用(按系统路径)
217
- refFullPath = refRaw
218
- }
219
-
220
- // console.log('refFullPath:', refFullPath)
221
- const fileContent = fs.readFileSync(refFullPath, 'utf-8')
222
- content = fileContent
223
- } catch (err) {
224
- // 读取失败:将错误写入 content 以便排查(不会让流程直接崩溃)
225
- const errorMsg = err instanceof Error ? err.message : String(err)
226
- content = `- Failed to load referenced file: ${esc(String(refRaw))}\n - Error: ${esc(errorMsg)}`
227
- }
228
- }
229
-
230
- content = normalizeMindmapMarkdown(content, {
231
- title: explicitTitle || referencedTitle,
232
- })
233
-
234
- // 3) 构造组件标签并把参数注入为 props
235
- const encodedContent = encodeURIComponent(content.trim())
236
- let propsStr = `content="${encodedContent}"`
237
-
238
- for (const [k, v] of Object.entries(params)) {
239
- if (typeof v === 'number' || /^\d+$/.test(String(v))) {
240
- propsStr += ` :${k}="${v}"`
241
- } else {
242
- const safe = String(v).replace(/"/g, '&quot;')
243
- propsStr += ` ${k}="${safe}"`
244
- }
245
- }
246
-
247
- const html = `<MindmapPreview ${propsStr}></MindmapPreview>\n`
248
-
249
- // 创建 html_block token
250
- const htmlToken = new state.Token('html_block', '', 0)
251
- htmlToken.content = html
252
-
253
- // 用单个 html_token 替换 open..close 区间
254
- tokens.splice(i, j - i + 1, htmlToken as any)
255
- }
256
- }
257
-
258
- return true
259
- })
260
- }
261
-
262
- /** Canonical `mindmap` fence. Legacy `markmap` stays on its historical container path. */
73
+ /** Canonical `mindmap` fence. */
263
74
  function configureMindmapFence(md: MarkdownIt) {
264
75
  const fence = md.renderer.rules.fence
265
76
  ? md.renderer.rules.fence.bind(md.renderer.rules)
@@ -268,11 +79,8 @@ function configureMindmapFence(md: MarkdownIt) {
268
79
  md.renderer.rules.fence = (tokens, index, options, env, slf) => {
269
80
  const token = tokens[index]
270
81
  const info = token.info.trim()
271
- if (!/^mindmap(?=\s|\{|\[|$)/.test(info)) {
272
- return fence(tokens, index, options, env, slf)
273
- }
274
-
275
- const fenceOptions = parseMarkmapFence(info)
82
+ const fenceOptions = parseMindmapFence(info)
83
+ if (!fenceOptions) return fence(tokens, index, options, env, slf)
276
84
  let content = token.content
277
85
  const firstNonEmptyLine = content.split('\n').find((line) => line.trim()) ?? ''
278
86
  const reference = parseMindmapReference(firstNonEmptyLine)
@@ -396,8 +204,7 @@ export function getMarkdownConfig(): MarkdownOptions {
396
204
  // 添加 Mermaid 支持
397
205
  simpleMermaidMarkdown(md)
398
206
 
399
- // 添加 Mindmap 支持,并继续兼容旧 MarkMap 围栏
400
- configureMindmapContainer(md)
207
+ // 添加规范的 Mindmap 围栏支持
401
208
  configureMindmapFence(md)
402
209
 
403
210
  // 添加任务列表支持
@@ -34,11 +34,19 @@ export function getDocIdFromFile(input: {
34
34
  rewrites?: Record<string, string>
35
35
  absoluteOrRelativeFile: string
36
36
  }): string {
37
- const absoluteFile = path.isAbsolute(input.absoluteOrRelativeFile)
37
+ // A knowledge base can be indexed on a different host platform in tests,
38
+ // build workers or cached metadata. Select the path implementation from the
39
+ // value instead of assuming the current Node.js platform.
40
+ const pathApi = /^[a-zA-Z]:[\\/]/.test(
41
+ input.absoluteOrRelativeFile || input.srcDir,
42
+ )
43
+ ? path.win32
44
+ : path
45
+ const absoluteFile = pathApi.isAbsolute(input.absoluteOrRelativeFile)
38
46
  ? input.absoluteOrRelativeFile
39
- : path.join(input.srcDir, input.absoluteOrRelativeFile)
47
+ : pathApi.join(input.srcDir, input.absoluteOrRelativeFile)
40
48
 
41
- let relFile = slash(path.relative(input.srcDir, absoluteFile))
49
+ let relFile = slash(pathApi.relative(input.srcDir, absoluteFile))
42
50
  if (input.rewrites?.[relFile]) {
43
51
  relFile = input.rewrites[relFile]
44
52
  }
@@ -45,8 +45,6 @@ function registerCoreComponents(ctx: EnhanceAppContext) {
45
45
  app.component('Footprints', Footprints)
46
46
  app.component('F', Footprints)
47
47
  app.component('SidebarCard', SidebarCard)
48
- // Keep the historical component name so existing note fences need no migration.
49
- app.component('MarkMap', MindmapPreview)
50
48
  app.component('MindmapPreview', MindmapPreview)
51
49
  app.component('Mermaid', Mermaid)
52
50
  app.component('NotesTable', NotesTable)
@@ -1,93 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import {
4
- normalizeMindmapMarkdown,
5
- parseMarkmapFence,
6
- parseMindmapReference,
7
- } from './compat'
8
-
9
- describe('parseMarkmapFence', () => {
10
- it.each([
11
- ['```markmap', {}],
12
- ['```markmap 2', { initialExpandLevel: 2 }],
13
- ['```markmap {2}', { initialExpandLevel: 2 }],
14
- ['```markmap {initialExpandLevel=3}', { initialExpandLevel: 3 }],
15
- ['```markmap [ESM 模块] 2', { title: 'ESM 模块', initialExpandLevel: 2 }],
16
- ['```markmap 2 [ESM 模块]', { title: 'ESM 模块', initialExpandLevel: 2 }],
17
- ['```mindmap', {}],
18
- ['```mindmap [项目架构] 1', { title: '项目架构', initialExpandLevel: 1 }],
19
- ])('parses %s', (input, expected) => {
20
- expect(parseMarkmapFence(input)).toEqual(expected)
21
- })
22
- })
23
-
24
- describe('parseMindmapReference', () => {
25
- it.each([
26
- ['<<< ./assets/tree.md', { path: './assets/tree.md' }],
27
- ['<<< ./assets/tree.md [项目架构]', { path: './assets/tree.md', title: '项目架构' }],
28
- ['<<< "./assets/tree with spaces.md" [项目架构]', { path: './assets/tree with spaces.md', title: '项目架构' }],
29
- ])('parses %s', (input, expected) => {
30
- expect(parseMindmapReference(input)).toEqual(expected)
31
- })
32
- })
33
-
34
- describe('normalizeMindmapMarkdown', () => {
35
- it('injects the default root for historical unordered-list input', () => {
36
- expect(normalizeMindmapMarkdown('- A\n - B')).toBe('# root\n\n- A\n - B\n')
37
- })
38
-
39
- it('promotes a single legacy root item instead of rendering duplicate roots', () => {
40
- expect(normalizeMindmapMarkdown('- root\n - item1\n - item2')).toBe(
41
- '# root\n\n- item1\n- item2\n',
42
- )
43
- expect(normalizeMindmapMarkdown('- Custom\n - item', { title: 'Custom' })).toBe(
44
- '# Custom\n\n- item\n',
45
- )
46
- })
47
-
48
- it('does not promote a matching root item when it has top-level siblings', () => {
49
- expect(normalizeMindmapMarkdown('- root\n - nested\n- sibling')).toBe(
50
- '# root\n\n- root\n - nested\n- sibling\n',
51
- )
52
- })
53
-
54
- it('preserves an existing H1 and lets explicit fence metadata override it', () => {
55
- const source = '# Existing\n\n- A\n'
56
- expect(normalizeMindmapMarkdown(source)).toBe(source)
57
- expect(normalizeMindmapMarkdown(source, { title: 'Explicit' })).toBe('# Explicit\n\n- A\n')
58
- })
59
-
60
- it('converts legacy H2/H3 sections into a nested unordered-list tree', () => {
61
- const source = [
62
- '# ESM',
63
- '',
64
- '## 基本语法',
65
- '',
66
- '### 导出方式',
67
- '',
68
- '- 命名导出',
69
- ' - 统一导出',
70
- '### 导入方式',
71
- '- 默认导入',
72
- ].join('\n')
73
-
74
- expect(normalizeMindmapMarkdown(source)).toBe([
75
- '# ESM',
76
- '',
77
- '- 基本语法',
78
- '',
79
- ' - 导出方式',
80
- '',
81
- ' - 命名导出',
82
- ' - 统一导出',
83
- ' - 导入方式',
84
- ' - 默认导入',
85
- '',
86
- ].join('\n'))
87
- })
88
-
89
- it('is stable when normalized more than once', () => {
90
- const once = normalizeMindmapMarkdown('- A\n - B')
91
- expect(normalizeMindmapMarkdown(once)).toBe(once)
92
- })
93
- })
@@ -1,128 +0,0 @@
1
- export interface MarkmapFenceOptions {
2
- title?: string
3
- initialExpandLevel?: number
4
- }
5
-
6
- export interface MindmapReference {
7
- path: string
8
- title?: string
9
- }
10
-
11
- function cleanHeadingText(value: string): string {
12
- return value.trim().replace(/\s+#+\s*$/, '').trim()
13
- }
14
-
15
- function promoteLegacyRootList(body: string[], rootTitle: string): string[] {
16
- const firstContentIndex = body.findIndex((line) => line.trim() !== '')
17
- if (firstContentIndex < 0) return body
18
-
19
- const firstItem = body[firstContentIndex].match(/^[-+*]\s+(.+?)\s*$/)
20
- if (!firstItem || cleanHeadingText(firstItem[1]) !== rootTitle) return body
21
-
22
- const descendants = body.slice(firstContentIndex + 1)
23
- if (descendants.some((line) => line.trim() !== '' && !/^\s{2,}/.test(line))) return body
24
-
25
- return [
26
- ...body.slice(0, firstContentIndex),
27
- ...descendants.map((line) => line.replace(/^ {2}/, '')),
28
- ]
29
- }
30
-
31
- /** Parse the opening fence while retaining every legacy expand-level spelling. */
32
- export function parseMarkmapFence(openLine: string): MarkmapFenceOptions {
33
- const fenceBody = openLine.trim().replace(/^`+\s*/, '')
34
- const nameMatch = fenceBody.match(/^(mindmap|markmap)(?=\s|\{|\[|$)/)
35
- if (!nameMatch) return {}
36
- let rest = fenceBody.slice(nameMatch[1].length).trim()
37
- const options: MarkmapFenceOptions = {}
38
-
39
- const titleMatch = rest.match(/\[([^\]]+)\]/)
40
- if (titleMatch) {
41
- options.title = titleMatch[1].trim()
42
- rest = `${rest.slice(0, titleMatch.index)} ${rest.slice((titleMatch.index ?? 0) + titleMatch[0].length)}`.trim()
43
- }
44
-
45
- const braceMatch = rest.match(/\{([^}]*)\}/)
46
- const paramPart = braceMatch ? braceMatch[1].trim() : rest
47
- const tokens = paramPart.match(/"[^"]*"|'[^']*'|\S+/g) ?? []
48
- for (const [index, token] of tokens.entries()) {
49
- if (/^\d+$/.test(token) && index === 0) {
50
- options.initialExpandLevel = Number(token)
51
- continue
52
- }
53
- const pair = token.match(/^([^=:\s]+)\s*(?:=|:)\s*(.+)$/)
54
- if (!pair || pair[1] !== 'initialExpandLevel') continue
55
- const value = pair[2].replace(/^['"]|['"]$/g, '')
56
- if (/^\d+$/.test(value)) options.initialExpandLevel = Number(value)
57
- }
58
- return options
59
- }
60
-
61
- /** Parse `<<< file.md [title]`; the title is optional and paths may be quoted. */
62
- export function parseMindmapReference(line: string): MindmapReference | null {
63
- const match = line.trim().match(/^<<<\s+(.+?)\s*$/)
64
- if (!match) return null
65
-
66
- let rest = match[1].trim()
67
- let title: string | undefined
68
- const titleMatch = rest.match(/\s+\[([^\]]+)\]\s*$/)
69
- if (titleMatch) {
70
- title = cleanHeadingText(titleMatch[1]) || undefined
71
- rest = rest.slice(0, titleMatch.index).trim()
72
- }
73
-
74
- const path = rest.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, '$1$2').trim()
75
- return path ? { path, title } : null
76
- }
77
-
78
- export interface NormalizeMindmapOptions {
79
- title?: string
80
- defaultTitle?: string
81
- }
82
-
83
- /** Convert historical VitePress markmap input into the strict mindmap-core format. */
84
- export function normalizeMindmapMarkdown(
85
- source: string,
86
- options: NormalizeMindmapOptions = {},
87
- ): string {
88
- const lines = source.replace(/\r\n?/g, '\n').split('\n')
89
- let existingTitle = ''
90
- let rootIndex = -1
91
-
92
- for (let index = 0; index < lines.length; index++) {
93
- const match = lines[index].match(/^\s{0,3}#(?!#)\s+(.+?)\s*$/)
94
- if (!match) continue
95
- existingTitle = cleanHeadingText(match[1])
96
- rootIndex = index
97
- break
98
- }
99
-
100
- const rootTitle = cleanHeadingText(options.title || existingTitle || options.defaultTitle || 'root') || 'root'
101
- const body: string[] = []
102
- let headingDepth: number | null = null
103
-
104
- for (let index = 0; index < lines.length; index++) {
105
- if (index === rootIndex) continue
106
- const line = lines[index]
107
- const heading = line.match(/^\s{0,3}(#{2,6})\s+(.+?)\s*$/)
108
- if (heading) {
109
- headingDepth = heading[1].length - 2
110
- body.push(`${' '.repeat(headingDepth)}- ${cleanHeadingText(heading[2])}`)
111
- continue
112
- }
113
-
114
- const listItem = line.match(/^(\s*)([-+*])\s+(.+)$/)
115
- if (listItem && headingDepth !== null) {
116
- body.push(`${' '.repeat(headingDepth + 1)}${line}`)
117
- continue
118
- }
119
- body.push(line)
120
- }
121
-
122
- const normalizedBody = promoteLegacyRootList(body, rootTitle)
123
- while (normalizedBody[0]?.trim() === '') normalizedBody.shift()
124
- while (normalizedBody[normalizedBody.length - 1]?.trim() === '') normalizedBody.pop()
125
- return normalizedBody.length > 0
126
- ? `# ${rootTitle}\n\n${normalizedBody.join('\n')}\n`
127
- : `# ${rootTitle}\n`
128
- }
@@ -1,57 +0,0 @@
1
- import { parseMarkdown } from '@tnotesjs/mindmap-core'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
- import { describe, expect, it } from 'vitest'
6
-
7
- import {
8
- normalizeMindmapMarkdown,
9
- parseMarkmapFence,
10
- parseMindmapReference,
11
- } from './compat'
12
-
13
- const tnotesRoot = path.resolve(fileURLToPath(new URL('../../../../', import.meta.url)))
14
- const noteFiles = [
15
- 'TNotes.canvas/notes/0035. 使用 ctx.drawImage 引入图像/README.md',
16
- 'TNotes.docs/notes/0013. Mindmap/README.md',
17
- 'TNotes.docs/notes/0014. 分仓库模式/README.md',
18
- 'TNotes.javascript/notes/0070. CommonJS/README.md',
19
- 'TNotes.javascript/notes/0071. ESM/README.md',
20
- 'TNotes.sql/notes/0001. MySQL 8 从入门到精通/README.md',
21
- 'TNotes.vite/notes/0014. vite 思维导图/README.md',
22
- ].map((relative) => path.join(tnotesRoot, relative))
23
-
24
- const corpusAvailable = noteFiles.every((file) => fs.existsSync(file))
25
-
26
- describe.skipIf(!corpusAvailable)('legacy TNotes markmap corpus', () => {
27
- it('normalizes every historical block into valid mindmap-core Markdown', () => {
28
- const diagnostics: string[] = []
29
- let blockCount = 0
30
-
31
- for (const noteFile of noteFiles) {
32
- const note = fs.readFileSync(noteFile, 'utf8')
33
- const blocks = note.matchAll(/^(```(?:mindmap|markmap)[^\n]*)\n([\s\S]*?)^```\s*$/gm)
34
- for (const match of blocks) {
35
- blockCount += 1
36
- const options = parseMarkmapFence(match[1])
37
- let source = match[2]
38
- const reference = parseMindmapReference(
39
- source.split('\n').find((line) => line.trim()) ?? '',
40
- )
41
- if (reference) {
42
- source = fs.readFileSync(path.resolve(path.dirname(noteFile), reference.path), 'utf8')
43
- }
44
- const normalized = normalizeMindmapMarkdown(source, {
45
- title: options.title || reference?.title,
46
- })
47
- const result = parseMarkdown(normalized, path.basename(noteFile))
48
- if (!result.valid) {
49
- diagnostics.push(`${noteFile}\n${result.diagnostics.map((item) => item.message).join('\n')}`)
50
- }
51
- }
52
- }
53
-
54
- expect(blockCount).toBe(16)
55
- expect(diagnostics).toEqual([])
56
- })
57
- })