wendang 0.0.1 → 0.1.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 +3 -0
- package/NOTICE.md +7 -0
- package/README.md +101 -2
- package/dist/cjs/document.js +19 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/mindmap.js +22 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/schema.js +11 -0
- package/dist/cjs/shared/anchor.js +201 -0
- package/dist/cjs/shared/callout.js +64 -0
- package/dist/cjs/shared/code-block.js +33 -0
- package/dist/cjs/shared/code-languages.js +51 -0
- package/dist/cjs/shared/doc-compatibility.js +23 -0
- package/dist/cjs/shared/doc-text.js +52 -0
- package/dist/cjs/shared/document-colors.js +61 -0
- package/dist/cjs/shared/inline-comments.js +179 -0
- package/dist/cjs/shared/mind-map-folding.js +14 -0
- package/dist/cjs/shared/mind-map-phase2.js +133 -0
- package/dist/cjs/shared/mind-map.js +445 -0
- package/dist/cjs/shared/mind-topic-text.js +57 -0
- package/dist/cjs/shared/node-catalog.js +62 -0
- package/dist/cjs/shared/presence.js +141 -0
- package/dist/cjs/shared/safe-href.js +36 -0
- package/dist/cjs/shared/table-cell-format.js +18 -0
- package/dist/cjs/shared/table-layout.js +28 -0
- package/dist/cjs/shared/types.js +2 -0
- package/dist/cjs/shared/video-embed.js +102 -0
- package/dist/cjs/shared/workbook.js +152 -0
- package/dist/cjs/spreadsheet.js +17 -0
- package/dist/cjs/types.js +17 -0
- package/dist/cjs/validation/callout.js +17 -0
- package/dist/cjs/validation/doc-node-whitelist.js +71 -0
- package/dist/cjs/validation/error.js +17 -0
- package/dist/cjs/validation/mind-map.js +129 -0
- package/dist/cjs/validation/schema-version.js +5 -0
- package/dist/cjs/validation/schema.js +376 -0
- package/dist/cjs/validation/video-embed.js +17 -0
- package/dist/cjs/validation/workbook.js +17 -0
- package/dist/esm/document.js +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/mindmap.js +4 -0
- package/dist/esm/schema.js +3 -0
- package/dist/esm/shared/anchor.js +192 -0
- package/dist/esm/shared/callout.js +56 -0
- package/dist/esm/shared/code-block.js +25 -0
- package/dist/esm/shared/code-languages.js +46 -0
- package/dist/esm/shared/doc-compatibility.js +20 -0
- package/dist/esm/shared/doc-text.js +48 -0
- package/dist/esm/shared/document-colors.js +56 -0
- package/dist/esm/shared/inline-comments.js +173 -0
- package/dist/esm/shared/mind-map-folding.js +10 -0
- package/dist/esm/shared/mind-map-phase2.js +121 -0
- package/dist/esm/shared/mind-map.js +427 -0
- package/dist/esm/shared/mind-topic-text.js +49 -0
- package/dist/esm/shared/node-catalog.js +57 -0
- package/dist/esm/shared/presence.js +130 -0
- package/dist/esm/shared/safe-href.js +32 -0
- package/dist/esm/shared/table-cell-format.js +14 -0
- package/dist/esm/shared/table-layout.js +23 -0
- package/dist/esm/shared/types.js +1 -0
- package/dist/esm/shared/video-embed.js +96 -0
- package/dist/esm/shared/workbook.js +144 -0
- package/dist/esm/spreadsheet.js +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/validation/callout.js +1 -0
- package/dist/esm/validation/doc-node-whitelist.js +68 -0
- package/dist/esm/validation/error.js +13 -0
- package/dist/esm/validation/mind-map.js +125 -0
- package/dist/esm/validation/schema-version.js +2 -0
- package/dist/esm/validation/schema.js +372 -0
- package/dist/esm/validation/video-embed.js +1 -0
- package/dist/esm/validation/workbook.js +1 -0
- package/dist/types/document.d.ts +4 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/mindmap.d.ts +4 -0
- package/dist/types/schema.d.ts +4 -0
- package/dist/types/shared/anchor.d.ts +87 -0
- package/dist/types/shared/callout.d.ts +27 -0
- package/dist/types/shared/code-block.d.ts +26 -0
- package/dist/types/shared/code-languages.d.ts +26 -0
- package/dist/types/shared/doc-compatibility.d.ts +1 -0
- package/dist/types/shared/doc-text.d.ts +21 -0
- package/dist/types/shared/document-colors.d.ts +106 -0
- package/dist/types/shared/inline-comments.d.ts +71 -0
- package/dist/types/shared/mind-map-folding.d.ts +5 -0
- package/dist/types/shared/mind-map-phase2.d.ts +19 -0
- package/dist/types/shared/mind-map.d.ts +134 -0
- package/dist/types/shared/mind-topic-text.d.ts +18 -0
- package/dist/types/shared/node-catalog.d.ts +30 -0
- package/dist/types/shared/presence.d.ts +69 -0
- package/dist/types/shared/safe-href.d.ts +3 -0
- package/dist/types/shared/table-cell-format.d.ts +24 -0
- package/dist/types/shared/table-layout.d.ts +7 -0
- package/dist/types/shared/types.d.ts +44 -0
- package/dist/types/shared/video-embed.d.ts +11 -0
- package/dist/types/shared/workbook.d.ts +56 -0
- package/dist/types/spreadsheet.d.ts +1 -0
- package/dist/types/types.d.ts +3 -0
- package/dist/types/validation/callout.d.ts +1 -0
- package/dist/types/validation/doc-node-whitelist.d.ts +21 -0
- package/dist/types/validation/error.d.ts +7 -0
- package/dist/types/validation/mind-map.d.ts +22 -0
- package/dist/types/validation/schema-version.d.ts +2 -0
- package/dist/types/validation/schema.d.ts +49 -0
- package/dist/types/validation/video-embed.d.ts +1 -0
- package/dist/types/validation/workbook.d.ts +1 -0
- package/package.json +87 -5
|
@@ -0,0 +1,445 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MIND_TOPIC_FONT = exports.MIND_TOPIC_PADDING = exports.mindTopicLineHeight = exports.MIND_TOPIC_LINE_HEIGHT = void 0;
|
|
4
|
+
exports.setMindTextMeasureFactory = setMindTextMeasureFactory;
|
|
5
|
+
exports.mindTextMeasure = mindTextMeasure;
|
|
6
|
+
exports.wrapMindText = wrapMindText;
|
|
7
|
+
exports.mindTopicTextSize = mindTopicTextSize;
|
|
8
|
+
exports.MIND_TOPIC_LINE_HEIGHT = 22;
|
|
9
|
+
const mindTopicLineHeight = (fontSize = 14) => Math.max(exports.MIND_TOPIC_LINE_HEIGHT, Math.ceil(fontSize * 1.4));
|
|
10
|
+
exports.mindTopicLineHeight = mindTopicLineHeight;
|
|
11
|
+
exports.MIND_TOPIC_PADDING = 34; // 16px * 2 padding + 1px * 2 borders.
|
|
12
|
+
exports.MIND_TOPIC_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif';
|
|
13
|
+
const sizeCache = new Map();
|
|
14
|
+
let factory;
|
|
15
|
+
function setMindTextMeasureFactory(value) { factory = value; sizeCache.clear(); }
|
|
16
|
+
/** Real browser font metrics; non-DOM exports/tests keep a conservative text-only fallback. */
|
|
17
|
+
function mindTextMeasure(fontSize = 14, weight = 400) {
|
|
18
|
+
const measured = factory?.(fontSize, weight);
|
|
19
|
+
if (measured)
|
|
20
|
+
return measured;
|
|
21
|
+
return text => [...text].reduce((sum, ch) => sum + (/[\u0000-\u00ff]/.test(ch) ? 9 : 14) * fontSize / 14, 0);
|
|
22
|
+
}
|
|
23
|
+
/** Keep explicit empty lines and emoji clusters; presentation wrapping never edits the source. */
|
|
24
|
+
function wrapMindText(text, width, measure) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
const segmenter = typeof Intl.Segmenter === 'function' ? new Intl.Segmenter(undefined, { granularity: 'grapheme' }) : null;
|
|
27
|
+
for (const line of text.split('\n')) {
|
|
28
|
+
let part = '';
|
|
29
|
+
const segments = segmenter ? [...segmenter.segment(line)].map(s => s.segment) : [...line];
|
|
30
|
+
for (const ch of segments) {
|
|
31
|
+
if (part && measure(part + ch) > width) {
|
|
32
|
+
lines.push(part);
|
|
33
|
+
part = '';
|
|
34
|
+
}
|
|
35
|
+
part += ch;
|
|
36
|
+
}
|
|
37
|
+
lines.push(part);
|
|
38
|
+
}
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
function mindTopicTextSize(text, fontSize = 14, root = false, description = '', bold = false, minimumWidth = 0) {
|
|
42
|
+
const key = JSON.stringify([text, fontSize, root, description, bold, minimumWidth]);
|
|
43
|
+
const cached = sizeCache.get(key);
|
|
44
|
+
if (cached)
|
|
45
|
+
return cached;
|
|
46
|
+
const measure = mindTextMeasure(fontSize, bold ? 700 : root ? 600 : 400), descriptionMeasure = mindTextMeasure(12);
|
|
47
|
+
const source = text || '未命名主题';
|
|
48
|
+
const longest = Math.max(...source.split('\n').map(measure), ...description.split('\n').map(descriptionMeasure));
|
|
49
|
+
const width = Math.min(240, Math.max(root ? 128 : 100, minimumWidth, Math.ceil(longest + exports.MIND_TOPIC_PADDING + 2)));
|
|
50
|
+
const lines = wrapMindText(source, width - exports.MIND_TOPIC_PADDING, measure);
|
|
51
|
+
const descriptionLines = description ? wrapMindText(description, width - exports.MIND_TOPIC_PADDING, descriptionMeasure) : [];
|
|
52
|
+
const result = { width, lines, descriptionLines, titleHeight: lines.length * (0, exports.mindTopicLineHeight)(fontSize) };
|
|
53
|
+
if (sizeCache.size >= 512)
|
|
54
|
+
sizeCache.delete(sizeCache.keys().next().value);
|
|
55
|
+
sizeCache.set(key, result);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 节点目录:编辑器的 nodeView 与只读渲染规则**共用的单一真源**
|
|
4
|
+
* (对齐《Wiki好用技术方案》1.1)。
|
|
5
|
+
*
|
|
6
|
+
* 这里只放纯数据(无 Vue、无 Tiptap 依赖),因此阅读端可以只引它 + Renderer,
|
|
7
|
+
* 而完全不加载编辑器代码;编辑器端再基于同一份目录生成扩展。
|
|
8
|
+
*
|
|
9
|
+
* 新增节点时必须同时更新:
|
|
10
|
+
* 1) 本目录 2) 服务端 doc-schema.service.ts 白名单 3) 提升 schema 版本
|
|
11
|
+
* 否则旧客户端会因「遇到不支持节点」而被服务端拒写(这是预期行为,避免静默丢内容)。
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.UNKNOWN_NODE_LABEL = exports.NODE_CATALOG = void 0;
|
|
15
|
+
exports.nodeMeta = nodeMeta;
|
|
16
|
+
exports.headingTag = headingTag;
|
|
17
|
+
exports.NODE_CATALOG = [
|
|
18
|
+
{ type: 'doc', kind: 'block', label: '文档', topLevel: true },
|
|
19
|
+
{ type: 'paragraph', kind: 'block', label: '段落', topLevel: true },
|
|
20
|
+
{ type: 'heading', kind: 'block', label: '标题', topLevel: true },
|
|
21
|
+
{ type: 'bulletList', kind: 'block', label: '无序列表', topLevel: true },
|
|
22
|
+
{ type: 'orderedList', kind: 'block', label: '有序列表', topLevel: true },
|
|
23
|
+
{ type: 'taskList', kind: 'block', label: '待办列表', topLevel: true },
|
|
24
|
+
{ type: 'listItem', kind: 'block', label: '列表项' },
|
|
25
|
+
{ type: 'taskItem', kind: 'block', label: '待办项' },
|
|
26
|
+
{ type: 'blockquote', kind: 'block', label: '引用块', topLevel: true },
|
|
27
|
+
{ type: 'codeBlock', kind: 'block', label: '代码块', topLevel: true },
|
|
28
|
+
{ type: 'horizontalRule', kind: 'atom', atom: true, label: '分割线', topLevel: true },
|
|
29
|
+
{ type: 'table', kind: 'block', label: '表格', topLevel: true },
|
|
30
|
+
{ type: 'tableRow', kind: 'block', label: '表格行' },
|
|
31
|
+
{ type: 'tableHeader', kind: 'block', label: '表头单元格' },
|
|
32
|
+
{ type: 'tableCell', kind: 'block', label: '单元格' },
|
|
33
|
+
{ type: 'image', kind: 'atom', atom: true, label: '图片', topLevel: true },
|
|
34
|
+
{ type: 'fileAttachment', kind: 'atom', atom: true, label: '视频或文件', topLevel: true },
|
|
35
|
+
{ type: 'videoEmbed', kind: 'atom', atom: true, label: '嵌入视频', topLevel: true },
|
|
36
|
+
{ type: 'mindMap', kind: 'block', label: '思维导图', topLevel: true },
|
|
37
|
+
{ type: 'mindMapDescription', kind: 'inline', label: '主题描述' },
|
|
38
|
+
{ type: 'mindMapNote', kind: 'inline', label: '主题备注' },
|
|
39
|
+
{ type: 'mindMapRelation', kind: 'block', label: '主题关系' },
|
|
40
|
+
{ type: 'mindMapTopic', kind: 'block', label: '导图主题' },
|
|
41
|
+
{ type: 'callout', kind: 'block', label: '高亮块', topLevel: true },
|
|
42
|
+
// 分栏:columns 是顶层行,column 只在其内部(故 column 无 topLevel)
|
|
43
|
+
{ type: 'columns', kind: 'block', label: '分栏', topLevel: true },
|
|
44
|
+
{ type: 'column', kind: 'block', label: '栏' },
|
|
45
|
+
{ type: 'pageLink', kind: 'atom', atom: true, label: '页面引用' },
|
|
46
|
+
{ type: 'mention', kind: 'atom', atom: true, label: '提及用户' },
|
|
47
|
+
{ type: 'mathInline', kind: 'atom', atom: true, label: '行内公式' },
|
|
48
|
+
{ type: 'mathBlock', kind: 'atom', atom: true, label: '公式块', topLevel: true },
|
|
49
|
+
{ type: 'text', kind: 'text', label: '文本' },
|
|
50
|
+
{ type: 'hardBreak', kind: 'inline', atom: true, label: '换行' },
|
|
51
|
+
];
|
|
52
|
+
const META_BY_TYPE = new Map(exports.NODE_CATALOG.map((n) => [n.type, n]));
|
|
53
|
+
function nodeMeta(type) {
|
|
54
|
+
return type ? META_BY_TYPE.get(type) : undefined;
|
|
55
|
+
}
|
|
56
|
+
/** 只读渲染遇到未知节点时使用的降级提示(不静默丢弃,向用户明示)。 */
|
|
57
|
+
exports.UNKNOWN_NODE_LABEL = '此内容由更高版本的编辑器创建,当前版本无法显示';
|
|
58
|
+
/** 标题级别 → HTML 标签 */
|
|
59
|
+
function headingTag(level) {
|
|
60
|
+
const n = typeof level === 'number' ? level : 1;
|
|
61
|
+
return `h${Math.min(Math.max(n, 1), 6)}`;
|
|
62
|
+
}
|