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.
- package/LICENSE +43 -2
- package/NOTICE.md +1 -1
- package/README.md +120 -52
- package/dist/cjs/document.js +1 -19
- package/dist/cjs/index.js +1 -25
- package/dist/cjs/mindmap.js +1 -22
- package/dist/cjs/schema.js +1 -11
- package/dist/cjs/shared/anchor.js +1 -201
- package/dist/cjs/shared/callout.js +1 -64
- package/dist/cjs/shared/code-block.js +1 -33
- package/dist/cjs/shared/code-languages.js +1 -51
- package/dist/cjs/shared/doc-compatibility.js +1 -23
- package/dist/cjs/shared/doc-text.js +1 -52
- package/dist/cjs/shared/document-colors.js +1 -61
- package/dist/cjs/shared/headings.js +1 -0
- package/dist/cjs/shared/inline-comments.js +1 -179
- package/dist/cjs/shared/mind-map-folding.js +1 -14
- package/dist/cjs/shared/mind-map-phase2.js +1 -133
- package/dist/cjs/shared/mind-map.js +1 -445
- package/dist/cjs/shared/mind-topic-text.js +1 -57
- package/dist/cjs/shared/node-catalog.js +1 -62
- package/dist/cjs/shared/presence.js +1 -141
- package/dist/cjs/shared/safe-href.js +1 -36
- package/dist/cjs/shared/table-cell-format.js +1 -18
- package/dist/cjs/shared/table-layout.js +1 -28
- package/dist/cjs/shared/types.js +1 -2
- package/dist/cjs/shared/video-embed.js +1 -102
- package/dist/cjs/shared/workbook.js +1 -152
- package/dist/cjs/spreadsheet.js +1 -17
- package/dist/cjs/types.js +1 -17
- package/dist/cjs/validation/callout.js +1 -17
- package/dist/cjs/validation/doc-node-whitelist.js +1 -71
- package/dist/cjs/validation/error.js +1 -17
- package/dist/cjs/validation/mind-map.js +1 -129
- package/dist/cjs/validation/schema-version.js +1 -5
- package/dist/cjs/validation/schema.js +1 -376
- package/dist/cjs/validation/video-embed.js +1 -17
- package/dist/cjs/validation/workbook.js +1 -17
- package/dist/esm/document.js +1 -3
- package/dist/esm/index.js +1 -3
- package/dist/esm/mindmap.js +1 -4
- package/dist/esm/schema.js +1 -3
- package/dist/esm/shared/anchor.js +1 -192
- package/dist/esm/shared/callout.js +1 -56
- package/dist/esm/shared/code-block.js +1 -25
- package/dist/esm/shared/code-languages.js +1 -46
- package/dist/esm/shared/doc-compatibility.js +1 -20
- package/dist/esm/shared/doc-text.js +1 -48
- package/dist/esm/shared/document-colors.js +1 -56
- package/dist/esm/shared/headings.js +1 -0
- package/dist/esm/shared/inline-comments.js +1 -173
- package/dist/esm/shared/mind-map-folding.js +1 -10
- package/dist/esm/shared/mind-map-phase2.js +1 -121
- package/dist/esm/shared/mind-map.js +1 -427
- package/dist/esm/shared/mind-topic-text.js +1 -49
- package/dist/esm/shared/node-catalog.js +1 -57
- package/dist/esm/shared/presence.js +1 -130
- package/dist/esm/shared/safe-href.js +1 -32
- package/dist/esm/shared/table-cell-format.js +1 -14
- package/dist/esm/shared/table-layout.js +1 -23
- package/dist/esm/shared/types.js +1 -1
- package/dist/esm/shared/video-embed.js +1 -96
- package/dist/esm/shared/workbook.js +1 -144
- package/dist/esm/spreadsheet.js +1 -1
- package/dist/esm/types.js +1 -1
- package/dist/esm/validation/callout.js +1 -1
- package/dist/esm/validation/doc-node-whitelist.js +1 -68
- package/dist/esm/validation/error.js +1 -13
- package/dist/esm/validation/mind-map.js +1 -125
- package/dist/esm/validation/schema-version.js +1 -2
- package/dist/esm/validation/schema.js +1 -372
- package/dist/esm/validation/video-embed.js +1 -1
- package/dist/esm/validation/workbook.js +1 -1
- package/dist/types/document.d.ts +1 -0
- package/dist/types/shared/anchor.d.ts +0 -55
- package/dist/types/shared/callout.d.ts +39 -2
- package/dist/types/shared/code-block.d.ts +0 -1
- package/dist/types/shared/code-languages.d.ts +0 -19
- package/dist/types/shared/doc-text.d.ts +0 -14
- package/dist/types/shared/document-colors.d.ts +0 -4
- package/dist/types/shared/headings.d.ts +7 -0
- package/dist/types/shared/inline-comments.d.ts +0 -42
- package/dist/types/shared/mind-map-folding.d.ts +0 -2
- package/dist/types/shared/mind-map.d.ts +0 -4
- package/dist/types/shared/mind-topic-text.d.ts +0 -2
- package/dist/types/shared/node-catalog.d.ts +0 -17
- package/dist/types/shared/presence.d.ts +0 -51
- package/dist/types/shared/safe-href.d.ts +0 -1
- package/dist/types/shared/table-cell-format.d.ts +0 -1
- package/dist/types/shared/table-layout.d.ts +0 -3
- package/dist/types/shared/types.d.ts +0 -4
- package/dist/types/shared/video-embed.d.ts +9 -2
- package/dist/types/shared/workbook.d.ts +0 -2
- package/dist/types/validation/doc-node-whitelist.d.ts +0 -19
- package/dist/types/validation/mind-map.d.ts +0 -2
- package/dist/types/validation/schema-version.d.ts +0 -1
- package/dist/types/validation/schema.d.ts +0 -34
- 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 => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[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=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[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}]})}
|