wendang 0.1.0 → 0.2.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 (98) hide show
  1. package/LICENSE +43 -2
  2. package/NOTICE.md +1 -1
  3. package/README.md +120 -52
  4. package/dist/cjs/document.js +1 -19
  5. package/dist/cjs/index.js +1 -25
  6. package/dist/cjs/mindmap.js +1 -22
  7. package/dist/cjs/schema.js +1 -11
  8. package/dist/cjs/shared/anchor.js +1 -201
  9. package/dist/cjs/shared/callout.js +1 -64
  10. package/dist/cjs/shared/code-block.js +1 -33
  11. package/dist/cjs/shared/code-languages.js +1 -51
  12. package/dist/cjs/shared/doc-compatibility.js +1 -23
  13. package/dist/cjs/shared/doc-text.js +1 -52
  14. package/dist/cjs/shared/document-colors.js +1 -61
  15. package/dist/cjs/shared/headings.js +1 -0
  16. package/dist/cjs/shared/inline-comments.js +1 -179
  17. package/dist/cjs/shared/mind-map-folding.js +1 -14
  18. package/dist/cjs/shared/mind-map-phase2.js +1 -133
  19. package/dist/cjs/shared/mind-map.js +1 -445
  20. package/dist/cjs/shared/mind-topic-text.js +1 -57
  21. package/dist/cjs/shared/node-catalog.js +1 -62
  22. package/dist/cjs/shared/presence.js +1 -141
  23. package/dist/cjs/shared/safe-href.js +1 -36
  24. package/dist/cjs/shared/table-cell-format.js +1 -18
  25. package/dist/cjs/shared/table-layout.js +1 -28
  26. package/dist/cjs/shared/types.js +1 -2
  27. package/dist/cjs/shared/video-embed.js +1 -102
  28. package/dist/cjs/shared/workbook.js +1 -152
  29. package/dist/cjs/spreadsheet.js +1 -17
  30. package/dist/cjs/types.js +1 -17
  31. package/dist/cjs/validation/callout.js +1 -17
  32. package/dist/cjs/validation/doc-node-whitelist.js +1 -71
  33. package/dist/cjs/validation/error.js +1 -17
  34. package/dist/cjs/validation/mind-map.js +1 -129
  35. package/dist/cjs/validation/schema-version.js +1 -5
  36. package/dist/cjs/validation/schema.js +1 -376
  37. package/dist/cjs/validation/video-embed.js +1 -17
  38. package/dist/cjs/validation/workbook.js +1 -17
  39. package/dist/esm/document.js +1 -3
  40. package/dist/esm/index.js +1 -3
  41. package/dist/esm/mindmap.js +1 -4
  42. package/dist/esm/schema.js +1 -3
  43. package/dist/esm/shared/anchor.js +1 -192
  44. package/dist/esm/shared/callout.js +1 -56
  45. package/dist/esm/shared/code-block.js +1 -25
  46. package/dist/esm/shared/code-languages.js +1 -46
  47. package/dist/esm/shared/doc-compatibility.js +1 -20
  48. package/dist/esm/shared/doc-text.js +1 -48
  49. package/dist/esm/shared/document-colors.js +1 -56
  50. package/dist/esm/shared/headings.js +1 -0
  51. package/dist/esm/shared/inline-comments.js +1 -173
  52. package/dist/esm/shared/mind-map-folding.js +1 -10
  53. package/dist/esm/shared/mind-map-phase2.js +1 -121
  54. package/dist/esm/shared/mind-map.js +1 -427
  55. package/dist/esm/shared/mind-topic-text.js +1 -49
  56. package/dist/esm/shared/node-catalog.js +1 -57
  57. package/dist/esm/shared/presence.js +1 -130
  58. package/dist/esm/shared/safe-href.js +1 -32
  59. package/dist/esm/shared/table-cell-format.js +1 -14
  60. package/dist/esm/shared/table-layout.js +1 -23
  61. package/dist/esm/shared/types.js +1 -1
  62. package/dist/esm/shared/video-embed.js +1 -96
  63. package/dist/esm/shared/workbook.js +1 -144
  64. package/dist/esm/spreadsheet.js +1 -1
  65. package/dist/esm/types.js +1 -1
  66. package/dist/esm/validation/callout.js +1 -1
  67. package/dist/esm/validation/doc-node-whitelist.js +1 -68
  68. package/dist/esm/validation/error.js +1 -13
  69. package/dist/esm/validation/mind-map.js +1 -125
  70. package/dist/esm/validation/schema-version.js +1 -2
  71. package/dist/esm/validation/schema.js +1 -372
  72. package/dist/esm/validation/video-embed.js +1 -1
  73. package/dist/esm/validation/workbook.js +1 -1
  74. package/dist/types/document.d.ts +1 -0
  75. package/dist/types/shared/anchor.d.ts +0 -55
  76. package/dist/types/shared/callout.d.ts +39 -2
  77. package/dist/types/shared/code-block.d.ts +0 -1
  78. package/dist/types/shared/code-languages.d.ts +0 -19
  79. package/dist/types/shared/doc-text.d.ts +0 -14
  80. package/dist/types/shared/document-colors.d.ts +0 -4
  81. package/dist/types/shared/headings.d.ts +7 -0
  82. package/dist/types/shared/inline-comments.d.ts +0 -42
  83. package/dist/types/shared/mind-map-folding.d.ts +0 -2
  84. package/dist/types/shared/mind-map.d.ts +0 -4
  85. package/dist/types/shared/mind-topic-text.d.ts +0 -2
  86. package/dist/types/shared/node-catalog.d.ts +0 -17
  87. package/dist/types/shared/presence.d.ts +0 -51
  88. package/dist/types/shared/safe-href.d.ts +0 -1
  89. package/dist/types/shared/table-cell-format.d.ts +0 -1
  90. package/dist/types/shared/table-layout.d.ts +0 -3
  91. package/dist/types/shared/types.d.ts +0 -4
  92. package/dist/types/shared/video-embed.d.ts +9 -2
  93. package/dist/types/shared/workbook.d.ts +0 -2
  94. package/dist/types/validation/doc-node-whitelist.d.ts +0 -19
  95. package/dist/types/validation/mind-map.d.ts +0 -2
  96. package/dist/types/validation/schema-version.d.ts +0 -1
  97. package/dist/types/validation/schema.d.ts +0 -34
  98. package/package.json +9 -9
@@ -1,133 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MIND_BACKGROUNDS = exports.MIND_COLORS = exports.MIND_ICONS = void 0;
4
- exports.topicTitle = topicTitle;
5
- exports.topicField = topicField;
6
- exports.branchModel = branchModel;
7
- exports.ancestorIds = ancestorIds;
8
- exports.searchMindMap = searchMindMap;
9
- exports.parseMindMapImport = parseMindMapImport;
10
- exports.mindMapTemplate = mindMapTemplate;
11
- exports.importBranch = importBranch;
12
- exports.parseMindBranch = parseMindBranch;
13
- const mind_map_js_1 = require("./mind-map.js");
14
- exports.MIND_ICONS = ['', '💡', '🎯', '📌', '🚩', '⭐', '✅', '⚠️', '💬', '📅', '🔎'];
15
- exports.MIND_COLORS = ['', '#18243a', '#ffffff', '#3864ef', '#087f8c', '#7950c7', '#a66b08', '#d94848'];
16
- exports.MIND_BACKGROUNDS = ['', '#f3f5fb', '#dce7ff', '#d3f4e9', '#ece0ff', '#fff0c2', '#ffe1e1', '#233044'];
17
- function topicTitle(node) {
18
- return (node.content ?? []).filter(c => c.type === 'text').map(c => c.text ?? '').join('');
19
- }
20
- function topicField(node, field) {
21
- return (node.content?.find(c => c.type === (field === 'description' ? 'mindMapDescription' : 'mindMapNote'))?.content ?? [])
22
- .map(c => c.text ?? '').join('');
23
- }
24
- function branchModel(model, id) {
25
- const ids = (0, mind_map_js_1.mindMapDescendants)(model, id);
26
- return { ...model, rootId: id, topics: (0, mind_map_js_1.mindMapTree)(model).filter(t => ids.has(t.id))
27
- .map(t => t.id === id ? { ...t, parentId: '' } : t),
28
- relations: model.relations?.filter(r => ids.has(r.source) && ids.has(r.target)) };
29
- }
30
- function ancestorIds(model, id) {
31
- const byId = new Map((0, mind_map_js_1.mindMapTree)(model).map(t => [t.id, t])), result = [];
32
- let current = byId.get(id);
33
- while (current?.parentId && result.length < 500) {
34
- result.push(current.parentId);
35
- current = byId.get(current.parentId);
36
- }
37
- return result;
38
- }
39
- function searchMindMap(model, query, details = true) {
40
- if (!query)
41
- return [];
42
- const needle = query.toLocaleLowerCase(), hits = [];
43
- for (const topic of (0, mind_map_js_1.mindMapOutline)(model)) {
44
- for (const field of (details ? ['title', 'description', 'note'] : ['title'])) {
45
- const value = (field === 'title' ? topic.text : topic[field] ?? '').toLocaleLowerCase();
46
- for (let at = 0; (at = value.indexOf(needle, at)) >= 0; at += needle.length) {
47
- hits.push({ id: topic.id, field, from: at, to: at + needle.length });
48
- if (hits.length > 5000)
49
- return hits;
50
- }
51
- }
52
- }
53
- return hits;
54
- }
55
- function parseMindMapImport(raw) {
56
- if (new TextEncoder().encode(raw).length > 2_000_000)
57
- throw new Error('导入内容过大(最多 2 MB)');
58
- if (raw.trimStart().startsWith('{')) {
59
- const data = JSON.parse(raw);
60
- const model = data.type === 'mindMap' ? (0, mind_map_js_1.readMindMap)(data) :
61
- data.format === 'ywiki-mind-map' && [1, 2, mind_map_js_1.MIND_MAP_VERSION].includes(data.version) && Array.isArray(data.topics)
62
- ? (0, mind_map_js_1.readMindMap)((0, mind_map_js_1.mindMapToJson)(data)) : null;
63
- if (!model)
64
- throw new Error('导图结构或版本无效');
65
- if (model.topics.some(t => t.id !== model.rootId && !model.topics.some(p => p.id === t.parentId)))
66
- throw new Error('导入结构包含不存在的父主题');
67
- if (model.relations?.some(r => !model.topics.some(t => t.id === r.source) || !model.topics.some(t => t.id === r.target)))
68
- throw new Error('导入关系的端点不存在');
69
- for (const topic of model.topics) {
70
- const seen = new Set();
71
- let current = topic;
72
- while (current?.parentId) {
73
- if (seen.has(current.id))
74
- throw new Error('导入结构存在循环关系');
75
- seen.add(current.id);
76
- current = model.topics.find(t => t.id === current.parentId);
77
- }
78
- }
79
- return (0, mind_map_js_1.readMindMap)((0, mind_map_js_1.mindMapToJson)(model, true));
80
- }
81
- const lines = raw.replace(/\r/g, '').split('\n').filter(l => l.trim());
82
- if (!lines.length)
83
- throw new Error('请输入缩进列表或导图 JSON');
84
- if (lines.length > 499)
85
- throw new Error('导入列表最多 499 行,加中心主题后最多 500 个主题');
86
- const model = (0, mind_map_js_1.readMindMap)((0, mind_map_js_1.createMindMap)());
87
- model.topics = [model.topics[0]];
88
- model.topics[0].text = '导入的主题';
89
- const stack = [{ indent: -1, id: model.rootId }];
90
- for (let i = 0; i < lines.length; i++) {
91
- const line = lines[i], white = line.match(/^\s*/)?.[0] ?? '', indent = white.replace(/\t/g, ' ').length;
92
- const text = line.trim().replace(/^(?:[-*+]\s+|\d+[.)]\s+)/, '');
93
- if (text.length > 2000)
94
- throw new Error(`第 ${i + 1} 行超过 2000 字符`);
95
- while (stack.length > 1 && stack.at(-1).indent >= indent)
96
- stack.pop();
97
- if (stack.length > 50)
98
- throw new Error(`第 ${i + 1} 行嵌套超过 50 层`);
99
- const id = (0, mind_map_js_1.mindMapId)();
100
- model.topics.push({ id, parentId: stack.at(-1).id, order: i, text });
101
- stack.push({ indent, id });
102
- }
103
- return model;
104
- }
105
- function mindMapTemplate(kind) {
106
- const data = {
107
- project: ['项目拆解', '目标与范围', '需求梳理', '研发实现', '测试验收', '上线与复盘'],
108
- meeting: ['会议讨论', '议题', '背景信息', '讨论结论', '待办事项', '负责人'],
109
- knowledge: ['知识目录', '基础概念', '核心原理', '实践案例', '常见问题', '参考资料'],
110
- }[kind];
111
- const m = (0, mind_map_js_1.readMindMap)((0, mind_map_js_1.createMindMap)('both'));
112
- m.topics = data.map((text, i) => ({ id: i ? (0, mind_map_js_1.mindMapId)() : m.rootId, parentId: i ? m.rootId : '', order: i, text }));
113
- return m;
114
- }
115
- function importBranch(model) {
116
- return (0, mind_map_js_1.mindMapToJson)(model, true).content ?? [];
117
- }
118
- function parseMindBranch(raw) {
119
- if (!raw.startsWith('YWIKI_MINDMAP_BRANCH_V1\n') || raw.length > 2_000_000)
120
- throw new Error('不是有效的导图分支');
121
- const content = JSON.parse(raw.slice(raw.indexOf('\n') + 1));
122
- if (!Array.isArray(content) || content.length > 1500)
123
- throw new Error('分支内容过大');
124
- const topics = content.filter(t => t?.type === 'mindMapTopic');
125
- if (!topics.length || !topics[0].attrs)
126
- throw new Error('分支缺少主题');
127
- const normalized = content.map(t => t === topics[0] ? { ...t, attrs: { ...t.attrs, parentId: '' } } : t);
128
- const model = (0, mind_map_js_1.readMindMap)({ type: 'mindMap', attrs: { id: 'clipboard', rootId: topics[0].attrs.id,
129
- formatVersion: mind_map_js_1.MIND_MAP_VERSION, layout: 'right', theme: 'blue', height: 380 }, content: normalized });
130
- if (!model)
131
- throw new Error('分支结构不合法');
132
- return importBranch(parseMindMapImport(JSON.stringify((0, mind_map_js_1.mindMapToJson)(model))));
133
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.MIND_BACKGROUNDS=exports.MIND_COLORS=exports.MIND_ICONS=void 0,exports.topicTitle=function topicTitle(t){return(t.content??[]).filter(t=>"text"===t.type).map(t=>t.text??"").join("")},exports.topicField=function topicField(t,e){return(t.content?.find(t=>t.type===("description"===e?"mindMapDescription":"mindMapNote"))?.content??[]).map(t=>t.text??"").join("")},exports.branchModel=function branchModel(e,r){const n=(0,t.mindMapDescendants)(e,r);return{...e,rootId:r,topics:(0,t.mindMapTree)(e).filter(t=>n.has(t.id)).map(t=>t.id===r?{...t,parentId:""}:t),relations:e.relations?.filter(t=>n.has(t.source)&&n.has(t.target))}},exports.ancestorIds=function ancestorIds(e,r){const n=new Map((0,t.mindMapTree)(e).map(t=>[t.id,t])),o=[];let i=n.get(r);for(;i?.parentId&&o.length<500;)o.push(i.parentId),i=n.get(i.parentId);return o},exports.searchMindMap=function searchMindMap(e,r,n=!0){if(!r)return[];const o=r.toLocaleLowerCase(),i=[];for(const r of(0,t.mindMapOutline)(e))for(const t of n?["title","description","note"]:["title"]){const e=("title"===t?r.text:r[t]??"").toLocaleLowerCase();for(let n=0;(n=e.indexOf(o,n))>=0;n+=o.length)if(i.push({id:r.id,field:t,from:n,to:n+o.length}),i.length>5e3)return i}return i},exports.parseMindMapImport=parseMindMapImport,exports.mindMapTemplate=function mindMapTemplate(e){const r={project:["项目拆解","目标与范围","需求梳理","研发实现","测试验收","上线与复盘"],meeting:["会议讨论","议题","背景信息","讨论结论","待办事项","负责人"],knowledge:["知识目录","基础概念","核心原理","实践案例","常见问题","参考资料"]}[e],n=(0,t.readMindMap)((0,t.createMindMap)("both"));return n.topics=r.map((e,r)=>({id:r?(0,t.mindMapId)():n.rootId,parentId:r?n.rootId:"",order:r,text:e})),n},exports.importBranch=importBranch,exports.parseMindBranch=function parseMindBranch(e){if(!e.startsWith("YWIKI_MINDMAP_BRANCH_V1\n")||e.length>2e6)throw new Error("不是有效的导图分支");const r=JSON.parse(e.slice(e.indexOf("\n")+1));if(!Array.isArray(r)||r.length>1500)throw new Error("分支内容过大");const n=r.filter(t=>"mindMapTopic"===t?.type);if(!n.length||!n[0].attrs)throw new Error("分支缺少主题");const o=r.map(t=>t===n[0]?{...t,attrs:{...t.attrs,parentId:""}}:t),i=(0,t.readMindMap)({type:"mindMap",attrs:{id:"clipboard",rootId:n[0].attrs.id,formatVersion:t.MIND_MAP_VERSION,layout:"right",theme:"blue",height:380},content:o});if(!i)throw new Error("分支结构不合法");return importBranch(parseMindMapImport(JSON.stringify((0,t.mindMapToJson)(i))))};const t=require("./mind-map.js");function parseMindMapImport(e){if((new TextEncoder).encode(e).length>2e6)throw new Error("导入内容过大(最多 2 MB)");if(e.trimStart().startsWith("{")){const r=JSON.parse(e),n="mindMap"===r.type?(0,t.readMindMap)(r):"ywiki-mind-map"===r.format&&[1,2,t.MIND_MAP_VERSION].includes(r.version)&&Array.isArray(r.topics)?(0,t.readMindMap)((0,t.mindMapToJson)(r)):null;if(!n)throw new Error("导图结构或版本无效");if(n.topics.some(t=>t.id!==n.rootId&&!n.topics.some(e=>e.id===t.parentId)))throw new Error("导入结构包含不存在的父主题");if(n.relations?.some(t=>!n.topics.some(e=>e.id===t.source)||!n.topics.some(e=>e.id===t.target)))throw new Error("导入关系的端点不存在");for(const t of n.topics){const e=new Set;let r=t;for(;r?.parentId;){if(e.has(r.id))throw new Error("导入结构存在循环关系");e.add(r.id),r=n.topics.find(t=>t.id===r.parentId)}}return(0,t.readMindMap)((0,t.mindMapToJson)(n,!0))}const r=e.replace(/\r/g,"").split("\n").filter(t=>t.trim());if(!r.length)throw new Error("请输入缩进列表或导图 JSON");if(r.length>499)throw new Error("导入列表最多 499 行,加中心主题后最多 500 个主题");const n=(0,t.readMindMap)((0,t.createMindMap)());n.topics=[n.topics[0]],n.topics[0].text="导入的主题";const o=[{indent:-1,id:n.rootId}];for(let e=0;e<r.length;e++){const i=r[e],p=(i.match(/^\s*/)?.[0]??"").replace(/\t/g," ").length,a=i.trim().replace(/^(?:[-*+]\s+|\d+[.)]\s+)/,"");if(a.length>2e3)throw new Error(`第 ${e+1} 行超过 2000 字符`);for(;o.length>1&&o.at(-1).indent>=p;)o.pop();if(o.length>50)throw new Error(`第 ${e+1} 行嵌套超过 50 层`);const s=(0,t.mindMapId)();n.topics.push({id:s,parentId:o.at(-1).id,order:e,text:a}),o.push({indent:p,id:s})}return n}function importBranch(e){return(0,t.mindMapToJson)(e,!0).content??[]}exports.MIND_ICONS=["","💡","🎯","📌","🚩","⭐","✅","⚠️","💬","📅","🔎"],exports.MIND_COLORS=["","#18243a","#ffffff","#3864ef","#087f8c","#7950c7","#a66b08","#d94848"],exports.MIND_BACKGROUNDS=["","#f3f5fb","#dce7ff","#d3f4e9","#ece0ff","#fff0c2","#ffe1e1","#233044"];
@@ -1,445 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MIND_MAP_PALETTE = exports.mindMapId = exports.MIND_MAP_THEMES = exports.MIND_MAP_LAYOUTS = exports.MIND_MAP_MAX_TEXT = exports.MIND_MAP_MAX_TOPICS = exports.MIND_MAP_VERSION = void 0;
4
- exports.createMindMap = createMindMap;
5
- exports.readMindMap = readMindMap;
6
- exports.mindMapTree = mindMapTree;
7
- exports.mindMapDescendants = mindMapDescendants;
8
- exports.canMoveMindTopic = canMoveMindTopic;
9
- exports.mindMapBranchSides = mindMapBranchSides;
10
- exports.projectMindSideMove = projectMindSideMove;
11
- exports.textDifference = textDifference;
12
- exports.layoutMindMap = layoutMindMap;
13
- exports.mindMapSvg = mindMapSvg;
14
- exports.mindMapOutline = mindMapOutline;
15
- exports.mindMapMarkdown = mindMapMarkdown;
16
- exports.mindRelationPaths = mindRelationPaths;
17
- exports.mindMapToJson = mindMapToJson;
18
- const safe_href_js_1 = require("./safe-href.js");
19
- const presence_js_1 = require("./presence.js");
20
- const mind_topic_text_js_1 = require("./mind-topic-text.js");
21
- /** Engine-independent document format. Topic text remains ordinary ProseMirror text. */
22
- exports.MIND_MAP_VERSION = 3;
23
- exports.MIND_MAP_MAX_TOPICS = 500;
24
- exports.MIND_MAP_MAX_TEXT = 2000;
25
- exports.MIND_MAP_LAYOUTS = ['right', 'left', 'both', 'down'];
26
- exports.MIND_MAP_THEMES = ['blue', 'teal', 'violet', 'amber'];
27
- const mindMapId = () => crypto.randomUUID();
28
- exports.mindMapId = mindMapId;
29
- const validId = (id) => typeof id === 'string' && /^[\w-]{1,80}$/.test(id);
30
- function createMindMap(layout = 'right') {
31
- const id = (0, exports.mindMapId)(), rootId = (0, exports.mindMapId)();
32
- return {
33
- type: 'mindMap', attrs: { id, rootId, formatVersion: exports.MIND_MAP_VERSION, layout, theme: 'blue', height: 380 },
34
- content: [
35
- { type: 'mindMapTopic', attrs: { id: rootId, parentId: '', order: 0 }, content: [{ type: 'text', text: '中心主题' }] },
36
- ...['分支主题 1', '分支主题 2', '分支主题 3'].map((text, order) => ({
37
- type: 'mindMapTopic', attrs: { id: (0, exports.mindMapId)(), parentId: rootId, order },
38
- content: [{ type: 'text', text }],
39
- })),
40
- ],
41
- };
42
- }
43
- function readMindMap(node) {
44
- const a = node?.attrs;
45
- if (node?.type !== 'mindMap' || !a || ![1, 2, exports.MIND_MAP_VERSION].includes(Number(a.formatVersion)) ||
46
- !validId(a.id) || !validId(a.rootId) || !Array.isArray(node.content) ||
47
- node.content.length < 1 || node.content.filter(t => t?.type === 'mindMapTopic').length > exports.MIND_MAP_MAX_TOPICS || node.content.length > 1500)
48
- return null;
49
- const topics = [];
50
- const ids = new Set();
51
- for (const topic of node.content.filter(t => t?.type !== 'mindMapRelation')) {
52
- if (!topic || typeof topic !== 'object')
53
- return null;
54
- const t = topic.attrs;
55
- if (topic.type !== 'mindMapTopic' || !t || !validId(t.id) || ids.has(t.id) ||
56
- typeof t.parentId !== 'string' || (t.parentId !== '' && !validId(t.parentId)) ||
57
- typeof t.order !== 'number' || !Number.isFinite(t.order) ||
58
- Math.abs(t.order) > 1e12 || (topic.content !== undefined && !Array.isArray(topic.content)) ||
59
- topic.content?.some(c => !c || !['text', 'mindMapDescription', 'mindMapNote'].includes(c.type ?? '') || (c.type === 'text' && typeof c.text !== 'string')))
60
- return null;
61
- const text = (topic.content ?? []).filter(c => c.type === 'text').map(c => c.text ?? '').join('');
62
- if (text.length > exports.MIND_MAP_MAX_TEXT)
63
- return null;
64
- ids.add(t.id);
65
- const extra = {};
66
- if (t.color && !/^#[0-9a-f]{6}$/i.test(String(t.color)))
67
- return null;
68
- if (t.background && !/^#[0-9a-f]{6}$/i.test(String(t.background)))
69
- return null;
70
- for (const key of ['color', 'background'])
71
- if (t[key] && !['#18243a', '#ffffff', '#3864ef', '#087f8c', '#7950c7', '#a66b08', '#d94848', '#f3f5fb', '#dce7ff', '#d3f4e9', '#ece0ff', '#fff0c2', '#ffe1e1', '#233044'].includes(String(t[key])))
72
- return null;
73
- if (t.shape && !['round', 'pill', 'text'].includes(String(t.shape)))
74
- return null;
75
- if (t.fontSize && ![12, 14, 16, 18, 20].includes(Number(t.fontSize)))
76
- return null;
77
- if (t.align && !['left', 'center', 'right'].includes(String(t.align)))
78
- return null;
79
- if (t.done !== undefined && t.done !== null && typeof t.done !== 'boolean')
80
- return null;
81
- if (t.side !== undefined && t.side !== null && t.side !== -1 && t.side !== 1)
82
- return null;
83
- if (t.icon && !['💡', '🎯', '📌', '🚩', '⭐', '✅', '⚠️', '💬', '📅', '🔎'].includes(String(t.icon)))
84
- return null;
85
- if (t.attachmentId && !(typeof t.attachmentId === 'string' && /^[0-9a-f-]{36}$/i.test(t.attachmentId)))
86
- return null;
87
- const fields = new Set();
88
- let fieldsStarted = false, noteStarted = false;
89
- for (const child of topic.content ?? []) {
90
- if (child.marks !== undefined && (!Array.isArray(child.marks) || child.marks.some(m => !m || !['bold', 'italic', 'strike', 'textStyle', 'highlight', 'link'].includes(m.type))))
91
- return null;
92
- if (child.type !== 'text' && ((child.content !== undefined && !Array.isArray(child.content)) || child.content?.some(n => n.type !== 'text' || typeof n.text !== 'string') || (child.content ?? []).reduce((sum, n) => sum + (n.text?.length ?? 0), 0) > 4000))
93
- return null;
94
- if (child.type === 'text' && fieldsStarted)
95
- return null;
96
- if (child.type !== 'text') {
97
- if (fields.has(child.type) || (child.type === 'mindMapDescription' && noteStarted))
98
- return null;
99
- fields.add(child.type);
100
- fieldsStarted = true;
101
- if (child.type === 'mindMapNote')
102
- noteStarted = true;
103
- }
104
- }
105
- for (const key of ['shape', 'color', 'background', 'fontSize', 'align', 'icon', 'done', 'attachmentId', 'side'])
106
- if (t[key] !== undefined && t[key] !== null && t[key] !== '')
107
- Object.assign(extra, { [key]: t[key] });
108
- const runs = (topic.content ?? []).filter(c => c.type === 'text').map(c => ({ text: c.text ?? '', ...(c.marks?.length ? { marks: c.marks } : {}) }));
109
- if (runs.some(r => r.marks?.length))
110
- extra.runs = runs;
111
- for (const [type, key] of [['mindMapDescription', 'description'], ['mindMapNote', 'note']]) {
112
- const child = topic.content?.find(c => c.type === type);
113
- if (child)
114
- extra[key] = (child.content ?? []).map(c => c.text ?? '').join('');
115
- }
116
- topics.push({ id: t.id, parentId: t.parentId, order: t.order, text, ...extra });
117
- }
118
- if (!ids.has(a.rootId))
119
- return null;
120
- const relations = node.content.filter(c => c.type === 'mindMapRelation');
121
- const relationIds = new Set();
122
- for (const r of relations) {
123
- const p = r.attrs;
124
- if (!p || !validId(p.id) || ids.has(p.id) || relationIds.has(p.id) || !validId(p.source) || !validId(p.target) ||
125
- p.source === p.target || !['related', 'depends', 'reference'].includes(String(p.kind)) ||
126
- typeof p.label !== 'string' || p.label.length > 200)
127
- return null;
128
- relationIds.add(p.id);
129
- }
130
- return {
131
- id: a.id, rootId: a.rootId, topics,
132
- ...(a.branchStyle ? { branchStyle: a.branchStyle } : {}),
133
- ...(node.content.some(c => c.type === 'mindMapRelation') ? { relations: node.content.filter(c => c.type === 'mindMapRelation').map(c => ({ ...c.attrs })) } : {}),
134
- layout: exports.MIND_MAP_LAYOUTS.includes(a.layout) ? a.layout : 'right',
135
- theme: exports.MIND_MAP_THEMES.includes(a.theme) ? a.theme : 'blue',
136
- height: typeof a.height === 'number' && Number.isFinite(a.height) ? Math.min(800, Math.max(240, a.height)) : 380,
137
- };
138
- }
139
- /** Missing parents and concurrent cycles keep every surviving topic, deterministically. */
140
- function mindMapTree(model) {
141
- const topics = model.topics.map(t => ({ ...t }));
142
- const byId = new Map(topics.map(t => [t.id, t]));
143
- for (const t of topics) {
144
- if (t.id === model.rootId)
145
- t.parentId = '';
146
- else if (!byId.has(t.parentId) || t.parentId === t.id)
147
- t.parentId = model.rootId;
148
- }
149
- for (const start of [...topics].sort((a, b) => a.id.localeCompare(b.id))) {
150
- const path = [], seen = new Set();
151
- let current = start;
152
- while (current && current.id !== model.rootId) {
153
- if (seen.has(current.id)) {
154
- const cycle = path.slice(path.indexOf(current.id)).sort();
155
- byId.get(cycle[0]).parentId = model.rootId;
156
- break;
157
- }
158
- seen.add(current.id);
159
- path.push(current.id);
160
- current = byId.get(current.parentId);
161
- }
162
- }
163
- return topics.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
164
- }
165
- function mindMapDescendants(model, id) {
166
- const tree = mindMapTree(model), ids = new Set([id]);
167
- for (let changed = true; changed;) {
168
- changed = false;
169
- for (const t of tree)
170
- if (ids.has(t.parentId) && !ids.has(t.id)) {
171
- ids.add(t.id);
172
- changed = true;
173
- }
174
- }
175
- return ids;
176
- }
177
- function canMoveMindTopic(model, id, parentId) {
178
- return id !== model.rootId && model.topics.some(t => t.id === id) &&
179
- model.topics.some(t => t.id === parentId) && !mindMapDescendants(model, id).has(parentId);
180
- }
181
- /** Only first-level topics own a side; all descendants inherit their branch direction. */
182
- function mindMapBranchSides(model) {
183
- return new Map(mindMapTree(model).filter(t => t.parentId === model.rootId).map((t, i) => [t.id, model.layout === 'left' ? -1 : model.layout === 'both' ? t.side ?? (i % 2 ? -1 : 1) : 1]));
184
- }
185
- /** Shared by the live drop preview and the single document transaction on release. */
186
- function projectMindSideMove(model, id, side, beforeId) {
187
- if (model.layout === 'down' || (side !== -1 && side !== 1) || !canMoveMindTopic(model, id, model.rootId))
188
- return null;
189
- const sides = mindMapBranchSides(model);
190
- const siblings = mindMapTree(model).filter(t => t.parentId === model.rootId && t.id !== id && sides.get(t.id) === side);
191
- const index = beforeId ? siblings.findIndex(t => t.id === beforeId) : siblings.length;
192
- if (index < 0)
193
- return null;
194
- const order = siblings.map(t => t.id);
195
- order.splice(index, 0, id);
196
- const orders = new Map(order.map((key, i) => [key, i * 1024]));
197
- return { ...model, layout: 'both', topics: model.topics.map(t => t.id === id
198
- ? { ...t, parentId: model.rootId, side, order: orders.get(id) }
199
- : t.parentId === model.rootId ? { ...t, side: sides.get(t.id), order: orders.get(t.id) ?? t.order } : t) };
200
- }
201
- function textDifference(before, after) {
202
- let from = 0, tail = 0;
203
- while (from < before.length && from < after.length && before[from] === after[from])
204
- from++;
205
- // Do not split surrogate pairs when inserting/removing emoji.
206
- if (from > 0 && /[\uD800-\uDBFF]/.test(before[from - 1]))
207
- from--;
208
- while (tail < before.length - from && tail < after.length - from &&
209
- before[before.length - tail - 1] === after[after.length - tail - 1])
210
- tail++;
211
- if (tail > 0 && /[\uDC00-\uDFFF]/.test(before[before.length - tail]))
212
- tail--;
213
- return { from, to: before.length - tail, text: after.slice(from, after.length - tail) };
214
- }
215
- function wrapTopic(text, maxUnits = 14) {
216
- const lines = [];
217
- for (const line of (text || '未命名主题').split('\n')) {
218
- let part = '', units = 0;
219
- for (const ch of line) {
220
- const size = /[\u0000-\u00ff]/.test(ch) ? 0.6 : 1;
221
- if (units + size > maxUnits) {
222
- lines.push(part);
223
- part = '';
224
- units = 0;
225
- }
226
- part += ch;
227
- units += size;
228
- }
229
- lines.push(part || ' ');
230
- }
231
- return lines;
232
- }
233
- function layoutMindMap(model, collapsed = new Set()) {
234
- const tree = mindMapTree(model), byParent = new Map();
235
- for (const t of tree) {
236
- const list = byParent.get(t.parentId) ?? [];
237
- list.push(t);
238
- byParent.set(t.parentId, list);
239
- }
240
- const root = tree.find(t => t.id === model.rootId);
241
- const dimensions = new Map(tree.map(t => {
242
- const measured = (0, mind_topic_text_js_1.mindTopicTextSize)(t.text, t.fontSize || 14, t.id === root.id, t.description || '', !!t.runs?.some(r => r.marks?.some(m => m.type === 'bold')), t.attachmentId ? 180 : 0);
243
- return [t.id, { lines: measured.lines, width: measured.width,
244
- height: Math.max(44, measured.titleHeight + 22) + (t.description ? measured.descriptionLines.length * 18 + 6 : 0) +
245
- (t.attachmentId ? 104 : 0) + (t.icon || t.done ? 20 : 0) + (t.note ? 20 : 0) }];
246
- }));
247
- const kids = (id) => collapsed.has(id) ? [] : byParent.get(id) ?? [];
248
- const nodes = [];
249
- const span = (t) => Math.max(model.layout === 'down' ? dimensions.get(t.id).width : dimensions.get(t.id).height, kids(t.id).reduce((n, c) => n + span(c) + 20, -20));
250
- const place = (t, x, y, side) => {
251
- const d = dimensions.get(t.id);
252
- nodes.push({ ...t, ...d, x, y, side });
253
- const children = kids(t.id), total = children.reduce((n, c) => n + span(c) + 20, -20);
254
- let cursor = (model.layout === 'down' ? x + d.width / 2 : y + d.height / 2) - total / 2;
255
- for (const c of children) {
256
- const cd = dimensions.get(c.id), s = span(c);
257
- if (model.layout === 'down')
258
- place(c, cursor + s / 2 - cd.width / 2, y + d.height + 60, side);
259
- else
260
- place(c, side === 1 ? x + d.width + 64 : x - cd.width - 64, cursor + s / 2 - cd.height / 2, side);
261
- cursor += s + 20;
262
- }
263
- };
264
- if (model.layout === 'both') {
265
- const d = dimensions.get(root.id);
266
- nodes.push({ ...root, ...d, x: 0, y: 0, side: 1 });
267
- const children = kids(root.id);
268
- const sides = mindMapBranchSides(model);
269
- for (const side of [-1, 1]) {
270
- const branches = children.filter(c => sides.get(c.id) === side);
271
- let cursor = d.height / 2 - branches.reduce((n, c) => n + span(c) + 20, -20) / 2;
272
- for (const c of branches) {
273
- const cd = dimensions.get(c.id), s = span(c);
274
- place(c, side === 1 ? d.width + 64 : -cd.width - 64, cursor + s / 2 - cd.height / 2, side);
275
- cursor += s + 20;
276
- }
277
- }
278
- }
279
- else
280
- place(root, 0, 0, model.layout === 'left' ? -1 : 1);
281
- const placed = new Map(nodes.map(n => [n.id, n]));
282
- const edges = nodes.filter(n => n.id !== root.id).map(n => {
283
- const p = placed.get(n.parentId);
284
- let x1, y1, x2, y2, path;
285
- if (model.layout === 'down') {
286
- x1 = p.x + p.width / 2;
287
- y1 = p.y + p.height;
288
- x2 = n.x + n.width / 2;
289
- y2 = n.y;
290
- path = `M${x1},${y1} C${x1},${(y1 + y2) / 2} ${x2},${(y1 + y2) / 2} ${x2},${y2}`;
291
- }
292
- else {
293
- x1 = p.x + (n.side === 1 ? p.width : 0);
294
- y1 = p.y + p.height / 2;
295
- x2 = n.x + (n.side === 1 ? 0 : n.width);
296
- y2 = n.y + n.height / 2;
297
- path = `M${x1},${y1} C${(x1 + x2) / 2},${y1} ${(x1 + x2) / 2},${y2} ${x2},${y2}`;
298
- }
299
- if (model.branchStyle === 'straight')
300
- path = `M${x1},${y1} L${x2},${y2}`;
301
- if (model.branchStyle === 'step')
302
- path = model.layout === 'down'
303
- ? `M${x1},${y1} V${(y1 + y2) / 2} H${x2} V${y2}` : `M${x1},${y1} H${(x1 + x2) / 2} V${y2} H${x2}`;
304
- return { id: `${p.id}-${n.id}`, source: p.id, target: n.id, path };
305
- });
306
- const relationBounds = (model.relations ?? []).flatMap(r => {
307
- const a = placed.get(r.source), b = placed.get(r.target);
308
- if (!a || !b)
309
- return [];
310
- const center = (a.x + a.width / 2 + b.x + b.width / 2) / 2, half = [...r.label].length * 7 / 2;
311
- return [{ left: center - half, right: center + half, top: Math.min(a.y, b.y) - 68 }];
312
- });
313
- const x = Math.min(...nodes.map(n => n.x), ...relationBounds.map(r => r.left)) - 32, y = Math.min(...nodes.map(n => n.y), ...relationBounds.map(r => r.top)) - 32;
314
- return { nodes, edges, bounds: { x, y,
315
- width: Math.max(...nodes.map(n => n.x + n.width), ...relationBounds.map(r => r.right)) - x + 32,
316
- height: Math.max(...nodes.map(n => n.y + n.height)) - y + 32 } };
317
- }
318
- exports.MIND_MAP_PALETTE = {
319
- blue: '#3864ef', teal: '#087f8c', violet: '#7950c7', amber: '#a66b08',
320
- };
321
- const escapeXml = (s) => s.replace(/[&<>"']/g, ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' })[ch]);
322
- function svgRichLine(topic, line, offset) {
323
- if (!topic.runs?.length)
324
- return escapeXml(line);
325
- const chars = [];
326
- let position = offset;
327
- for (const ch of line) {
328
- let cursor = 0;
329
- const run = topic.runs.find(r => { const start = cursor; cursor += r.text.length; return position >= start && position < cursor; });
330
- const marks = run?.marks ?? [], style = [];
331
- for (const m of marks) {
332
- if (m.type === 'bold')
333
- style.push('font-weight:700');
334
- if (m.type === 'italic')
335
- style.push('font-style:italic');
336
- if (m.type === 'strike')
337
- style.push('text-decoration:line-through');
338
- if (m.type === 'link')
339
- style.push('text-decoration:underline');
340
- if (m.type === 'textStyle' && /^#[0-9a-f]{6}$/i.test(String(m.attrs?.color)))
341
- style.push(`fill:${m.attrs.color}`);
342
- }
343
- chars.push(`<tspan${style.length ? ` style="${style.join(';')}"` : ''}>${escapeXml(ch)}</tspan>`);
344
- position += ch.length;
345
- }
346
- return chars.join('');
347
- }
348
- function svgHighlights(topic, line, offset, x, y) {
349
- if (!topic.runs?.some(r => r.marks?.some(m => m.type === 'highlight')))
350
- return '';
351
- const font = topic.fontSize || 14;
352
- const width = (ch) => (/[\u0000-\u00ff]/.test(ch) ? 8 : 14) * font / 14;
353
- const total = [...line].reduce((sum, ch) => sum + width(ch), 0);
354
- let left = topic.align === 'left' ? x + 16 : topic.align === 'right' ? x + topic.width - total - 16 : x + (topic.width - total) / 2;
355
- let position = offset;
356
- const rects = [];
357
- for (const ch of line) {
358
- let cursor = 0;
359
- const run = topic.runs.find(r => { const start = cursor; cursor += r.text.length; return position >= start && position < cursor; });
360
- const color = run?.marks?.find(m => m.type === 'highlight')?.attrs?.color;
361
- const w = width(ch);
362
- if (/^#[0-9a-f]{6}$/i.test(String(color)))
363
- rects.push(`<rect x="${left}" y="${y - font}" width="${w}" height="${font + 4}" rx="2" fill="${color}"/>`);
364
- left += w;
365
- position += ch.length;
366
- }
367
- return rects.join('');
368
- }
369
- function mindMapSvg(model, options = {}) {
370
- const drawing = layoutMindMap(model), b = drawing.bounds, color = exports.MIND_MAP_PALETTE[model.theme];
371
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${b.x} ${b.y} ${b.width} ${b.height}" width="${Math.ceil(b.width)}" height="${Math.ceil(b.height)}">` +
372
- (options.transparent ? '' : `<rect x="${b.x}" y="${b.y}" width="${b.width}" height="${b.height}" fill="#fff"/>`) +
373
- drawing.edges.map(e => `<path d="${e.path}" fill="none" stroke="${color}" stroke-width="2"/>`).join('') +
374
- drawing.nodes.map(n => {
375
- const lineHeight = (0, mind_topic_text_js_1.mindTopicLineHeight)(n.fontSize || 14), baseline = 11 + (lineHeight - (n.fontSize || 14)) / 2 + (n.fontSize || 14) * .8;
376
- const fill = n.background || (n.id === model.rootId ? color : '#f3f5fb');
377
- const ink = n.color || (n.shape === 'text' ? '#25314a' : n.background ? (0, presence_js_1.readableTextColor)(n.background) : n.id === model.rootId ? '#fff' : '#25314a');
378
- const description = n.description ? wrapTopic(n.description) : [];
379
- const image = n.attachmentId && options.images?.[n.attachmentId];
380
- return `<g>` + (n.shape === 'text' ? '' : `<rect x="${n.x}" y="${n.y}" width="${n.width}" height="${n.height}" rx="${n.shape === 'pill' ? 24 : 9}" fill="${escapeXml(fill)}" stroke="${color}" stroke-opacity=".3"/>`) +
381
- (n.icon || n.done ? `<text x="${n.x + n.width / 2}" y="${n.y + 22}" text-anchor="middle" font-size="16">${n.done ? '☑ ' : ''}${escapeXml(n.icon || '')}</text>` : '') +
382
- n.lines.map((line, i) => svgHighlights(n, line, n.lines.slice(0, i).reduce((sum, l) => sum + l.length, 0), n.x, n.y + baseline + i * lineHeight + (n.icon || n.done ? 20 : 0))).join('') +
383
- n.lines.map((line, i) => `<text x="${n.x + (n.align === 'left' ? 16 : n.align === 'right' ? n.width - 16 : n.width / 2)}" y="${n.y + baseline + i * lineHeight + (n.icon || n.done ? 20 : 0)}" text-anchor="${n.align === 'left' ? 'start' : n.align === 'right' ? 'end' : 'middle'}" font-family="${escapeXml(mind_topic_text_js_1.MIND_TOPIC_FONT)}" font-size="${n.fontSize || 14}" fill="${escapeXml(ink)}">${svgRichLine(n, line, n.lines.slice(0, i).reduce((sum, l) => sum + l.length, 0))}</text>`).join('') +
384
- description.map((line, i) => `<text x="${n.x + n.width / 2}" y="${n.y + 28 + n.lines.length * lineHeight + i * 18 + (n.icon || n.done ? 20 : 0)}" text-anchor="middle" font-size="12" fill="#677184">${escapeXml(line)}</text>`).join('') +
385
- (n.note ? `<text x="${n.x + n.width / 2}" y="${n.y + n.height - (n.attachmentId ? 112 : 10)}" text-anchor="middle" font-size="10" fill="#677184">▤ 备注</text>` : '') +
386
- (image && /^data:image\/(png|jpeg|webp|gif);base64,/.test(image) ? `<image x="${n.x + 10}" y="${n.y + n.height - 105}" width="${n.width - 20}" height="95" href="${image}" preserveAspectRatio="xMidYMid meet"/>` : '') + '</g>';
387
- }).join('') + mindRelationPaths(model, drawing).map(r => `<path d="${r.path}" fill="none" stroke="#8b5fc7" stroke-width="2" stroke-dasharray="6 4"/><text x="${r.x}" y="${r.y}" font-size="12" fill="#7950c7">${escapeXml(r.label || ({ related: '相关', depends: '依赖', reference: '参考' }[r.kind]))}</text>`).join('') + '</svg>';
388
- }
389
- function mindMapOutline(model) {
390
- const tree = mindMapTree(model), result = [];
391
- const visit = (id, depth) => {
392
- const topic = tree.find(t => t.id === id);
393
- if (!topic)
394
- return;
395
- result.push({ ...topic, depth });
396
- for (const child of tree.filter(t => t.parentId === id))
397
- visit(child.id, depth + 1);
398
- };
399
- visit(model.rootId, 0);
400
- return result;
401
- }
402
- function mindMapMarkdown(model) {
403
- const escape = (value) => value.replace(/([\\`*_[\]<>])/g, '\\$1').replace(/\n/g, ' ');
404
- return mindMapOutline(model).map(t => {
405
- const title = (t.runs ?? [{ text: t.text }]).map(run => {
406
- let value = escape(run.text);
407
- for (const mark of run.marks ?? []) {
408
- if (mark.type === 'bold')
409
- value = `**${value}**`;
410
- if (mark.type === 'italic')
411
- value = `*${value}*`;
412
- if (mark.type === 'strike')
413
- value = `~~${value}~~`;
414
- if (mark.type === 'link' && (0, safe_href_js_1.isSafeHref)(mark.attrs?.href))
415
- value = `[${value}](${String(mark.attrs.href).replace(/[()]/g, ch => encodeURIComponent(ch))})`;
416
- }
417
- return value;
418
- }).join('') || '未命名主题';
419
- return `${' '.repeat(t.depth)}- ${t.done ? '[x] ' : ''}${t.icon ? t.icon + ' ' : ''}${title}${t.description ? ' — ' + escape(t.description) : ''}${t.note ? '(备注:' + escape(t.note) + ')' : ''}${t.attachmentId ? ' [图片附件:' + t.attachmentId + ']' : ''}`;
420
- }).join('\n') + ((model.relations?.length ?? 0) > 0 ? '\n\n关系:\n' + model.relations.map(r => `- ${escape(model.topics.find(t => t.id === r.source)?.text || '已移除主题')} → ${escape(model.topics.find(t => t.id === r.target)?.text || '已移除主题')}:${escape(r.label || r.kind)}`).join('\n') : '');
421
- }
422
- function mindRelationPaths(model, drawing) {
423
- const nodes = new Map(drawing.nodes.map(n => [n.id, n]));
424
- return (model.relations ?? []).flatMap(r => {
425
- const a = nodes.get(r.source), b = nodes.get(r.target);
426
- if (!a || !b || a === b)
427
- return [];
428
- const x1 = a.x + a.width / 2, y1 = a.y, x2 = b.x + b.width / 2, y2 = b.y;
429
- const bend = Math.min(y1, y2) - 48;
430
- return [{ ...r, path: `M${x1},${y1} C${x1},${bend} ${x2},${bend} ${x2},${y2}`, x: (x1 + x2) / 2, y: bend + 8 }];
431
- });
432
- }
433
- function mindMapToJson(model, fresh = false) {
434
- const ids = new Map(model.topics.map(t => [t.id, fresh ? (0, exports.mindMapId)() : t.id]));
435
- return { type: 'mindMap', attrs: { id: fresh ? (0, exports.mindMapId)() : model.id, rootId: ids.get(model.rootId),
436
- formatVersion: exports.MIND_MAP_VERSION, layout: model.layout, theme: model.theme, height: model.height, branchStyle: model.branchStyle || 'curve' },
437
- content: [...model.topics.map(t => {
438
- const { text, runs, description, note, ...attrs } = t;
439
- return { type: 'mindMapTopic', attrs: { ...attrs, id: ids.get(t.id), parentId: ids.get(t.parentId) ?? '' },
440
- content: [...(runs ?? (text ? [{ text }] : [])).map(r => ({ type: 'text', ...r })),
441
- ...(description ? [{ type: 'mindMapDescription', content: [{ type: 'text', text: description }] }] : []),
442
- ...(note ? [{ type: 'mindMapNote', content: [{ type: 'text', text: note }] }] : [])] };
443
- }), ...(model.relations ?? []).map(r => ({ type: 'mindMapRelation', attrs: { ...r,
444
- id: fresh ? (0, exports.mindMapId)() : r.id, source: ids.get(r.source), target: ids.get(r.target) } }))] };
445
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.MIND_MAP_PALETTE=exports.mindMapId=exports.MIND_MAP_THEMES=exports.MIND_MAP_LAYOUTS=exports.MIND_MAP_MAX_TEXT=exports.MIND_MAP_MAX_TOPICS=exports.MIND_MAP_VERSION=void 0,exports.createMindMap=function createMindMap(t="right"){const e=(0,exports.mindMapId)(),n=(0,exports.mindMapId)();return{type:"mindMap",attrs:{id:e,rootId:n,formatVersion:exports.MIND_MAP_VERSION,layout:t,theme:"blue",height:380},content:[{type:"mindMapTopic",attrs:{id:n,parentId:"",order:0},content:[{type:"text",text:"中心主题"}]},...["分支主题 1","分支主题 2","分支主题 3"].map((t,e)=>({type:"mindMapTopic",attrs:{id:(0,exports.mindMapId)(),parentId:n,order:e},content:[{type:"text",text:t}]}))]}},exports.readMindMap=function readMindMap(t){const e=t?.attrs;if("mindMap"!==t?.type||!e||![1,2,exports.MIND_MAP_VERSION].includes(Number(e.formatVersion))||!validId(e.id)||!validId(e.rootId)||!Array.isArray(t.content)||t.content.length<1||t.content.filter(t=>"mindMapTopic"===t?.type).length>exports.MIND_MAP_MAX_TOPICS||t.content.length>1500)return null;const n=[],i=new Set;for(const e of t.content.filter(t=>"mindMapRelation"!==t?.type)){if(!e||"object"!=typeof e)return null;const t=e.attrs;if("mindMapTopic"!==e.type||!t||!validId(t.id)||i.has(t.id)||"string"!=typeof t.parentId||""!==t.parentId&&!validId(t.parentId)||"number"!=typeof t.order||!Number.isFinite(t.order)||Math.abs(t.order)>1e12||void 0!==e.content&&!Array.isArray(e.content)||e.content?.some(t=>!t||!["text","mindMapDescription","mindMapNote"].includes(t.type??"")||"text"===t.type&&"string"!=typeof t.text))return null;const o=(e.content??[]).filter(t=>"text"===t.type).map(t=>t.text??"").join("");if(o.length>exports.MIND_MAP_MAX_TEXT)return null;i.add(t.id);const r={};if(t.color&&!/^#[0-9a-f]{6}$/i.test(String(t.color)))return null;if(t.background&&!/^#[0-9a-f]{6}$/i.test(String(t.background)))return null;for(const e of["color","background"])if(t[e]&&!["#18243a","#ffffff","#3864ef","#087f8c","#7950c7","#a66b08","#d94848","#f3f5fb","#dce7ff","#d3f4e9","#ece0ff","#fff0c2","#ffe1e1","#233044"].includes(String(t[e])))return null;if(t.shape&&!["round","pill","text"].includes(String(t.shape)))return null;if(t.fontSize&&![12,14,16,18,20].includes(Number(t.fontSize)))return null;if(t.align&&!["left","center","right"].includes(String(t.align)))return null;if(void 0!==t.done&&null!==t.done&&"boolean"!=typeof t.done)return null;if(void 0!==t.side&&null!==t.side&&-1!==t.side&&1!==t.side)return null;if(t.icon&&!["💡","🎯","📌","🚩","⭐","✅","⚠️","💬","📅","🔎"].includes(String(t.icon)))return null;if(t.attachmentId&&("string"!=typeof t.attachmentId||!/^[0-9a-f-]{36}$/i.test(t.attachmentId)))return null;const d=new Set;let a=!1,s=!1;for(const t of e.content??[]){if(void 0!==t.marks&&(!Array.isArray(t.marks)||t.marks.some(t=>!t||!["bold","italic","strike","textStyle","highlight","link"].includes(t.type))))return null;if("text"!==t.type&&(void 0!==t.content&&!Array.isArray(t.content)||t.content?.some(t=>"text"!==t.type||"string"!=typeof t.text)||(t.content??[]).reduce((t,e)=>t+(e.text?.length??0),0)>4e3))return null;if("text"===t.type&&a)return null;if("text"!==t.type){if(d.has(t.type)||"mindMapDescription"===t.type&&s)return null;d.add(t.type),a=!0,"mindMapNote"===t.type&&(s=!0)}}for(const e of["shape","color","background","fontSize","align","icon","done","attachmentId","side"])void 0!==t[e]&&null!==t[e]&&""!==t[e]&&Object.assign(r,{[e]:t[e]});const p=(e.content??[]).filter(t=>"text"===t.type).map(t=>({text:t.text??"",...t.marks?.length?{marks:t.marks}:{}}));p.some(t=>t.marks?.length)&&(r.runs=p);for(const[t,n]of[["mindMapDescription","description"],["mindMapNote","note"]]){const i=e.content?.find(e=>e.type===t);i&&(r[n]=(i.content??[]).map(t=>t.text??"").join(""))}n.push({id:t.id,parentId:t.parentId,order:t.order,text:o,...r})}if(!i.has(e.rootId))return null;const o=t.content.filter(t=>"mindMapRelation"===t.type),r=new Set;for(const t of o){const e=t.attrs;if(!e||!validId(e.id)||i.has(e.id)||r.has(e.id)||!validId(e.source)||!validId(e.target)||e.source===e.target||!["related","depends","reference"].includes(String(e.kind))||"string"!=typeof e.label||e.label.length>200)return null;r.add(e.id)}return{id:e.id,rootId:e.rootId,topics:n,...e.branchStyle?{branchStyle:e.branchStyle}:{},...t.content.some(t=>"mindMapRelation"===t.type)?{relations:t.content.filter(t=>"mindMapRelation"===t.type).map(t=>({...t.attrs}))}:{},layout:exports.MIND_MAP_LAYOUTS.includes(e.layout)?e.layout:"right",theme:exports.MIND_MAP_THEMES.includes(e.theme)?e.theme:"blue",height:"number"==typeof e.height&&Number.isFinite(e.height)?Math.min(800,Math.max(240,e.height)):380}},exports.mindMapTree=mindMapTree,exports.mindMapDescendants=mindMapDescendants,exports.canMoveMindTopic=canMoveMindTopic,exports.mindMapBranchSides=mindMapBranchSides,exports.projectMindSideMove=function projectMindSideMove(t,e,n,i){if("down"===t.layout||-1!==n&&1!==n||!canMoveMindTopic(t,e,t.rootId))return null;const o=mindMapBranchSides(t),r=mindMapTree(t).filter(i=>i.parentId===t.rootId&&i.id!==e&&o.get(i.id)===n),d=i?r.findIndex(t=>t.id===i):r.length;if(d<0)return null;const a=r.map(t=>t.id);a.splice(d,0,e);const s=new Map(a.map((t,e)=>[t,1024*e]));return{...t,layout:"both",topics:t.topics.map(i=>i.id===e?{...i,parentId:t.rootId,side:n,order:s.get(e)}:i.parentId===t.rootId?{...i,side:o.get(i.id),order:s.get(i.id)??i.order}:i)}},exports.textDifference=function textDifference(t,e){let n=0,i=0;for(;n<t.length&&n<e.length&&t[n]===e[n];)n++;for(n>0&&/[\uD800-\uDBFF]/.test(t[n-1])&&n--;i<t.length-n&&i<e.length-n&&t[t.length-i-1]===e[e.length-i-1];)i++;return i>0&&/[\uDC00-\uDFFF]/.test(t[t.length-i])&&i--,{from:n,to:t.length-i,text:e.slice(n,e.length-i)}},exports.layoutMindMap=layoutMindMap,exports.mindMapSvg=function mindMapSvg(t,i={}){const o=layoutMindMap(t),r=o.bounds,d=exports.MIND_MAP_PALETTE[t.theme];return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${r.x} ${r.y} ${r.width} ${r.height}" width="${Math.ceil(r.width)}" height="${Math.ceil(r.height)}">`+(i.transparent?"":`<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="#fff"/>`)+o.edges.map(t=>`<path d="${t.path}" fill="none" stroke="${d}" stroke-width="2"/>`).join("")+o.nodes.map(o=>{const r=(0,n.mindTopicLineHeight)(o.fontSize||14),a=11+(r-(o.fontSize||14))/2+.8*(o.fontSize||14),s=o.background||(o.id===t.rootId?d:"#f3f5fb"),p=o.color||("text"===o.shape?"#25314a":o.background?(0,e.readableTextColor)(o.background):o.id===t.rootId?"#fff":"#25314a"),c=o.description?function wrapTopic(t,e=14){const n=[];for(const i of(t||"未命名主题").split("\n")){let t="",o=0;for(const r of i){const i=/[\u0000-\u00ff]/.test(r)?.6:1;o+i>e&&(n.push(t),t="",o=0),t+=r,o+=i}n.push(t||" ")}return n}(o.description):[],l=o.attachmentId&&i.images?.[o.attachmentId];return"<g>"+("text"===o.shape?"":`<rect x="${o.x}" y="${o.y}" width="${o.width}" height="${o.height}" rx="${"pill"===o.shape?24:9}" fill="${escapeXml(s)}" stroke="${d}" stroke-opacity=".3"/>`)+(o.icon||o.done?`<text x="${o.x+o.width/2}" y="${o.y+22}" text-anchor="middle" font-size="16">${o.done?"☑ ":""}${escapeXml(o.icon||"")}</text>`:"")+o.lines.map((t,e)=>function svgHighlights(t,e,n,i,o){if(!t.runs?.some(t=>t.marks?.some(t=>"highlight"===t.type)))return"";const r=t.fontSize||14,width=t=>(/[\u0000-\u00ff]/.test(t)?8:14)*r/14,d=[...e].reduce((t,e)=>t+width(e),0);let a="left"===t.align?i+16:"right"===t.align?i+t.width-d-16:i+(t.width-d)/2,s=n;const p=[];for(const n of e){let e=0;const i=t.runs.find(t=>{const n=e;return e+=t.text.length,s>=n&&s<e}),d=i?.marks?.find(t=>"highlight"===t.type)?.attrs?.color,c=width(n);/^#[0-9a-f]{6}$/i.test(String(d))&&p.push(`<rect x="${a}" y="${o-r}" width="${c}" height="${r+4}" rx="2" fill="${d}"/>`),a+=c,s+=n.length}return p.join("")}(o,t,o.lines.slice(0,e).reduce((t,e)=>t+e.length,0),o.x,o.y+a+e*r+(o.icon||o.done?20:0))).join("")+o.lines.map((t,e)=>`<text x="${o.x+("left"===o.align?16:"right"===o.align?o.width-16:o.width/2)}" y="${o.y+a+e*r+(o.icon||o.done?20:0)}" text-anchor="${"left"===o.align?"start":"right"===o.align?"end":"middle"}" font-family="${escapeXml(n.MIND_TOPIC_FONT)}" font-size="${o.fontSize||14}" fill="${escapeXml(p)}">${function svgRichLine(t,e,n){if(!t.runs?.length)return escapeXml(e);const i=[];let o=n;for(const n of e){let e=0;const r=t.runs.find(t=>{const n=e;return e+=t.text.length,o>=n&&o<e}),d=r?.marks??[],a=[];for(const t of d)"bold"===t.type&&a.push("font-weight:700"),"italic"===t.type&&a.push("font-style:italic"),"strike"===t.type&&a.push("text-decoration:line-through"),"link"===t.type&&a.push("text-decoration:underline"),"textStyle"===t.type&&/^#[0-9a-f]{6}$/i.test(String(t.attrs?.color))&&a.push(`fill:${t.attrs.color}`);i.push(`<tspan${a.length?` style="${a.join(";")}"`:""}>${escapeXml(n)}</tspan>`),o+=n.length}return i.join("")}(o,t,o.lines.slice(0,e).reduce((t,e)=>t+e.length,0))}</text>`).join("")+c.map((t,e)=>`<text x="${o.x+o.width/2}" y="${o.y+28+o.lines.length*r+18*e+(o.icon||o.done?20:0)}" text-anchor="middle" font-size="12" fill="#677184">${escapeXml(t)}</text>`).join("")+(o.note?`<text x="${o.x+o.width/2}" y="${o.y+o.height-(o.attachmentId?112:10)}" text-anchor="middle" font-size="10" fill="#677184">▤ 备注</text>`:"")+(l&&/^data:image\/(png|jpeg|webp|gif);base64,/.test(l)?`<image x="${o.x+10}" y="${o.y+o.height-105}" width="${o.width-20}" height="95" href="${l}" preserveAspectRatio="xMidYMid meet"/>`:"")+"</g>"}).join("")+mindRelationPaths(t,o).map(t=>`<path d="${t.path}" fill="none" stroke="#8b5fc7" stroke-width="2" stroke-dasharray="6 4"/><text x="${t.x}" y="${t.y}" font-size="12" fill="#7950c7">${escapeXml(t.label||{related:"相关",depends:"依赖",reference:"参考"}[t.kind])}</text>`).join("")+"</svg>"},exports.mindMapOutline=mindMapOutline,exports.mindMapMarkdown=function mindMapMarkdown(e){const escape=t=>t.replace(/([\\`*_[\]<>])/g,"\\$1").replace(/\n/g," ");return mindMapOutline(e).map(e=>{const n=(e.runs??[{text:e.text}]).map(e=>{let n=escape(e.text);for(const i of e.marks??[])"bold"===i.type&&(n=`**${n}**`),"italic"===i.type&&(n=`*${n}*`),"strike"===i.type&&(n=`~~${n}~~`),"link"===i.type&&(0,t.isSafeHref)(i.attrs?.href)&&(n=`[${n}](${String(i.attrs.href).replace(/[()]/g,t=>encodeURIComponent(t))})`);return n}).join("")||"未命名主题";return`${" ".repeat(e.depth)}- ${e.done?"[x] ":""}${e.icon?e.icon+" ":""}${n}${e.description?" — "+escape(e.description):""}${e.note?"(备注:"+escape(e.note)+")":""}${e.attachmentId?" [图片附件:"+e.attachmentId+"]":""}`}).join("\n")+((e.relations?.length??0)>0?"\n\n关系:\n"+e.relations.map(t=>`- ${escape(e.topics.find(e=>e.id===t.source)?.text||"已移除主题")} → ${escape(e.topics.find(e=>e.id===t.target)?.text||"已移除主题")}:${escape(t.label||t.kind)}`).join("\n"):"")},exports.mindRelationPaths=mindRelationPaths,exports.mindMapToJson=function mindMapToJson(t,e=!1){const n=new Map(t.topics.map(t=>[t.id,e?(0,exports.mindMapId)():t.id]));return{type:"mindMap",attrs:{id:e?(0,exports.mindMapId)():t.id,rootId:n.get(t.rootId),formatVersion:exports.MIND_MAP_VERSION,layout:t.layout,theme:t.theme,height:t.height,branchStyle:t.branchStyle||"curve"},content:[...t.topics.map(t=>{const{text:e,runs:i,description:o,note:r,...d}=t;return{type:"mindMapTopic",attrs:{...d,id:n.get(t.id),parentId:n.get(t.parentId)??""},content:[...(i??(e?[{text:e}]:[])).map(t=>({type:"text",...t})),...o?[{type:"mindMapDescription",content:[{type:"text",text:o}]}]:[],...r?[{type:"mindMapNote",content:[{type:"text",text:r}]}]:[]]}}),...(t.relations??[]).map(t=>({type:"mindMapRelation",attrs:{...t,id:e?(0,exports.mindMapId)():t.id,source:n.get(t.source),target:n.get(t.target)}}))]}};const t=require("./safe-href.js"),e=require("./presence.js"),n=require("./mind-topic-text.js");exports.MIND_MAP_VERSION=3,exports.MIND_MAP_MAX_TOPICS=500,exports.MIND_MAP_MAX_TEXT=2e3,exports.MIND_MAP_LAYOUTS=["right","left","both","down"],exports.MIND_MAP_THEMES=["blue","teal","violet","amber"],exports.mindMapId=()=>crypto.randomUUID();const validId=t=>"string"==typeof t&&/^[\w-]{1,80}$/.test(t);function mindMapTree(t){const e=t.topics.map(t=>({...t})),n=new Map(e.map(t=>[t.id,t]));for(const i of e)i.id===t.rootId?i.parentId="":n.has(i.parentId)&&i.parentId!==i.id||(i.parentId=t.rootId);for(const i of[...e].sort((t,e)=>t.id.localeCompare(e.id))){const e=[],o=new Set;let r=i;for(;r&&r.id!==t.rootId;){if(o.has(r.id)){const i=e.slice(e.indexOf(r.id)).sort();n.get(i[0]).parentId=t.rootId;break}o.add(r.id),e.push(r.id),r=n.get(r.parentId)}}return e.sort((t,e)=>t.order-e.order||t.id.localeCompare(e.id))}function mindMapDescendants(t,e){const n=mindMapTree(t),i=new Set([e]);for(let t=!0;t;){t=!1;for(const e of n)i.has(e.parentId)&&!i.has(e.id)&&(i.add(e.id),t=!0)}return i}function canMoveMindTopic(t,e,n){return e!==t.rootId&&t.topics.some(t=>t.id===e)&&t.topics.some(t=>t.id===n)&&!mindMapDescendants(t,e).has(n)}function mindMapBranchSides(t){return new Map(mindMapTree(t).filter(e=>e.parentId===t.rootId).map((e,n)=>[e.id,"left"===t.layout?-1:"both"===t.layout?e.side??(n%2?-1:1):1]))}function layoutMindMap(t,e=new Set){const i=mindMapTree(t),o=new Map;for(const t of i){const e=o.get(t.parentId)??[];e.push(t),o.set(t.parentId,e)}const r=i.find(e=>e.id===t.rootId),d=new Map(i.map(t=>{const e=(0,n.mindTopicTextSize)(t.text,t.fontSize||14,t.id===r.id,t.description||"",!!t.runs?.some(t=>t.marks?.some(t=>"bold"===t.type)),t.attachmentId?180:0);return[t.id,{lines:e.lines,width:e.width,height:Math.max(44,e.titleHeight+22)+(t.description?18*e.descriptionLines.length+6:0)+(t.attachmentId?104:0)+(t.icon||t.done?20:0)+(t.note?20:0)}]})),kids=t=>e.has(t)?[]:o.get(t)??[],a=[],span=e=>Math.max("down"===t.layout?d.get(e.id).width:d.get(e.id).height,kids(e.id).reduce((t,e)=>t+span(e)+20,-20)),place=(e,n,i,o)=>{const r=d.get(e.id);a.push({...e,...r,x:n,y:i,side:o});const s=kids(e.id),p=s.reduce((t,e)=>t+span(e)+20,-20);let c=("down"===t.layout?n+r.width/2:i+r.height/2)-p/2;for(const e of s){const a=d.get(e.id),s=span(e);"down"===t.layout?place(e,c+s/2-a.width/2,i+r.height+60,o):place(e,1===o?n+r.width+64:n-a.width-64,c+s/2-a.height/2,o),c+=s+20}};if("both"===t.layout){const e=d.get(r.id);a.push({...r,...e,x:0,y:0,side:1});const n=kids(r.id),i=mindMapBranchSides(t);for(const t of[-1,1]){const o=n.filter(e=>i.get(e.id)===t);let r=e.height/2-o.reduce((t,e)=>t+span(e)+20,-20)/2;for(const n of o){const i=d.get(n.id),o=span(n);place(n,1===t?e.width+64:-i.width-64,r+o/2-i.height/2,t),r+=o+20}}}else place(r,0,0,"left"===t.layout?-1:1);const s=new Map(a.map(t=>[t.id,t])),p=a.filter(t=>t.id!==r.id).map(e=>{const n=s.get(e.parentId);let i,o,r,d,a;return"down"===t.layout?(i=n.x+n.width/2,o=n.y+n.height,r=e.x+e.width/2,d=e.y,a=`M${i},${o} C${i},${(o+d)/2} ${r},${(o+d)/2} ${r},${d}`):(i=n.x+(1===e.side?n.width:0),o=n.y+n.height/2,r=e.x+(1===e.side?0:e.width),d=e.y+e.height/2,a=`M${i},${o} C${(i+r)/2},${o} ${(i+r)/2},${d} ${r},${d}`),"straight"===t.branchStyle&&(a=`M${i},${o} L${r},${d}`),"step"===t.branchStyle&&(a="down"===t.layout?`M${i},${o} V${(o+d)/2} H${r} V${d}`:`M${i},${o} H${(i+r)/2} V${d} H${r}`),{id:`${n.id}-${e.id}`,source:n.id,target:e.id,path:a}}),c=(t.relations??[]).flatMap(t=>{const e=s.get(t.source),n=s.get(t.target);if(!e||!n)return[];const i=(e.x+e.width/2+n.x+n.width/2)/2,o=7*[...t.label].length/2;return[{left:i-o,right:i+o,top:Math.min(e.y,n.y)-68}]}),l=Math.min(...a.map(t=>t.x),...c.map(t=>t.left))-32,h=Math.min(...a.map(t=>t.y),...c.map(t=>t.top))-32;return{nodes:a,edges:p,bounds:{x:l,y:h,width:Math.max(...a.map(t=>t.x+t.width),...c.map(t=>t.right))-l+32,height:Math.max(...a.map(t=>t.y+t.height))-h+32}}}exports.MIND_MAP_PALETTE={blue:"#3864ef",teal:"#087f8c",violet:"#7950c7",amber:"#a66b08"};const escapeXml=t=>t.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"}[t]));function mindMapOutline(t){const e=mindMapTree(t),n=[],visit=(t,i)=>{const o=e.find(e=>e.id===t);if(o){n.push({...o,depth:i});for(const n of e.filter(e=>e.parentId===t))visit(n.id,i+1)}};return visit(t.rootId,0),n}function mindRelationPaths(t,e){const n=new Map(e.nodes.map(t=>[t.id,t]));return(t.relations??[]).flatMap(t=>{const e=n.get(t.source),i=n.get(t.target);if(!e||!i||e===i)return[];const o=e.x+e.width/2,r=e.y,d=i.x+i.width/2,a=i.y,s=Math.min(r,a)-48;return[{...t,path:`M${o},${r} C${o},${s} ${d},${s} ${d},${a}`,x:(o+d)/2,y:s+8}]})}