@scrider/formatter 1.0.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 +21 -0
- package/NOTICE +52 -0
- package/README.md +123 -0
- package/dist/index.cjs +4946 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1658 -0
- package/dist/index.d.ts +1658 -0
- package/dist/index.js +4844 -0
- package/dist/index.js.map +1 -0
- package/package.json +109 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4844 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
export * from "@scrider/delta";
|
|
10
|
+
|
|
11
|
+
// src/schema/Registry.ts
|
|
12
|
+
var Registry = class {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.formats = /* @__PURE__ */ new Map();
|
|
15
|
+
}
|
|
16
|
+
register(formatOrFormats) {
|
|
17
|
+
const formats = Array.isArray(formatOrFormats) ? formatOrFormats : [formatOrFormats];
|
|
18
|
+
for (const format of formats) {
|
|
19
|
+
if (this.formats.has(format.name)) {
|
|
20
|
+
throw new Error(`Format "${format.name}" is already registered`);
|
|
21
|
+
}
|
|
22
|
+
this.formats.set(format.name, format);
|
|
23
|
+
}
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get a format by name
|
|
28
|
+
*
|
|
29
|
+
* @param name - Format name
|
|
30
|
+
* @returns Format or undefined if not found
|
|
31
|
+
*/
|
|
32
|
+
get(name) {
|
|
33
|
+
return this.formats.get(name);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if a format is registered
|
|
37
|
+
*
|
|
38
|
+
* @param name - Format name
|
|
39
|
+
* @returns true if format exists
|
|
40
|
+
*/
|
|
41
|
+
has(name) {
|
|
42
|
+
return this.formats.has(name);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get all formats with a specific scope
|
|
46
|
+
*
|
|
47
|
+
* @param scope - Format scope to filter by
|
|
48
|
+
* @returns Array of formats with the given scope
|
|
49
|
+
*/
|
|
50
|
+
getByScope(scope) {
|
|
51
|
+
const result = [];
|
|
52
|
+
for (const format of this.formats.values()) {
|
|
53
|
+
if (format.scope === scope) {
|
|
54
|
+
result.push(format);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get all registered format names
|
|
61
|
+
*
|
|
62
|
+
* @returns Array of format names
|
|
63
|
+
*/
|
|
64
|
+
getNames() {
|
|
65
|
+
return Array.from(this.formats.keys());
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Normalize all attributes using registered formats
|
|
69
|
+
*
|
|
70
|
+
* Applies normalize() for each attribute that has a registered format.
|
|
71
|
+
* Unknown attributes are passed through unchanged.
|
|
72
|
+
*
|
|
73
|
+
* @param attributes - Attributes to normalize
|
|
74
|
+
* @returns New object with normalized attributes
|
|
75
|
+
*/
|
|
76
|
+
normalize(attributes) {
|
|
77
|
+
if (!attributes) return void 0;
|
|
78
|
+
const result = {};
|
|
79
|
+
let hasChanges = false;
|
|
80
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
81
|
+
const format = this.formats.get(key);
|
|
82
|
+
if (format?.normalize) {
|
|
83
|
+
const normalized = format.normalize(value);
|
|
84
|
+
result[key] = normalized;
|
|
85
|
+
if (normalized !== value) {
|
|
86
|
+
hasChanges = true;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
result[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return hasChanges ? result : attributes;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate all attributes
|
|
96
|
+
*
|
|
97
|
+
* @param attributes - Attributes to validate
|
|
98
|
+
* @returns true if all known attributes are valid
|
|
99
|
+
*/
|
|
100
|
+
validate(attributes) {
|
|
101
|
+
if (!attributes) return true;
|
|
102
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
103
|
+
const format = this.formats.get(key);
|
|
104
|
+
if (format?.validate && !format.validate(value)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sanitize attributes by removing invalid values
|
|
112
|
+
*
|
|
113
|
+
* Removes attributes that:
|
|
114
|
+
* - Are not registered (unknown formats)
|
|
115
|
+
* - Fail validation
|
|
116
|
+
*
|
|
117
|
+
* @param attributes - Attributes to sanitize
|
|
118
|
+
* @param removeUnknown - If true, remove unregistered attributes (default: true)
|
|
119
|
+
* @returns New object with only valid attributes
|
|
120
|
+
*/
|
|
121
|
+
sanitize(attributes, removeUnknown = true) {
|
|
122
|
+
if (!attributes) return void 0;
|
|
123
|
+
const result = {};
|
|
124
|
+
let hasKeys = false;
|
|
125
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
126
|
+
const format = this.formats.get(key);
|
|
127
|
+
if (!format && removeUnknown) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (format?.validate && !format.validate(value)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (format?.normalize) {
|
|
134
|
+
result[key] = format.normalize(value);
|
|
135
|
+
} else {
|
|
136
|
+
result[key] = value;
|
|
137
|
+
}
|
|
138
|
+
hasKeys = true;
|
|
139
|
+
}
|
|
140
|
+
return hasKeys ? result : void 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the number of registered formats
|
|
144
|
+
*/
|
|
145
|
+
get size() {
|
|
146
|
+
return this.formats.size;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/schema/BlockHandlerRegistry.ts
|
|
151
|
+
var BlockHandlerRegistry = class {
|
|
152
|
+
constructor() {
|
|
153
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Register a block handler
|
|
157
|
+
*
|
|
158
|
+
* @param handler - BlockHandler to register
|
|
159
|
+
* @returns this for chaining
|
|
160
|
+
* @throws Error if handler with same type is already registered
|
|
161
|
+
*/
|
|
162
|
+
register(handler) {
|
|
163
|
+
if (this.handlers.has(handler.type)) {
|
|
164
|
+
throw new Error(`BlockHandler "${handler.type}" is already registered`);
|
|
165
|
+
}
|
|
166
|
+
this.handlers.set(handler.type, handler);
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get a handler by block type
|
|
171
|
+
*
|
|
172
|
+
* @param type - Block type (e.g. "table")
|
|
173
|
+
* @returns BlockHandler or undefined if not found
|
|
174
|
+
*/
|
|
175
|
+
get(type) {
|
|
176
|
+
return this.handlers.get(type);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if a handler is registered for a block type
|
|
180
|
+
*
|
|
181
|
+
* @param type - Block type
|
|
182
|
+
* @returns true if handler exists
|
|
183
|
+
*/
|
|
184
|
+
has(type) {
|
|
185
|
+
return this.handlers.has(type);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get the number of registered handlers
|
|
189
|
+
*/
|
|
190
|
+
get size() {
|
|
191
|
+
return this.handlers.size;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get all registered block type names
|
|
195
|
+
*/
|
|
196
|
+
getTypes() {
|
|
197
|
+
return Array.from(this.handlers.keys());
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/schema/blocks/table.ts
|
|
202
|
+
import { Delta as Delta2 } from "@scrider/delta";
|
|
203
|
+
|
|
204
|
+
// src/conversion/sanitize.ts
|
|
205
|
+
import { Delta, isInsert, isRetain, isEmbedInsert, deepClone } from "@scrider/delta";
|
|
206
|
+
var DEFAULT_OPTIONS = {
|
|
207
|
+
removeUnknown: true,
|
|
208
|
+
normalize: true,
|
|
209
|
+
removeInvalidEmbeds: false,
|
|
210
|
+
allowedEmbeds: []
|
|
211
|
+
};
|
|
212
|
+
function sanitizeDelta(delta, registry, options = {}) {
|
|
213
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
214
|
+
const result = new Delta();
|
|
215
|
+
for (const op of delta.ops) {
|
|
216
|
+
const sanitizedOp = sanitizeOp(op, registry, opts);
|
|
217
|
+
if (sanitizedOp !== null) {
|
|
218
|
+
result.push(sanitizedOp);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
function sanitizeOp(op, registry, options) {
|
|
224
|
+
if (isInsert(op)) {
|
|
225
|
+
if (isEmbedInsert(op)) {
|
|
226
|
+
const embedValue = op.insert;
|
|
227
|
+
const embedType = Object.keys(embedValue)[0];
|
|
228
|
+
if (!embedType) {
|
|
229
|
+
return options.removeInvalidEmbeds ? null : op;
|
|
230
|
+
}
|
|
231
|
+
if (options.allowedEmbeds.length > 0) {
|
|
232
|
+
if (!options.allowedEmbeds.includes(embedType)) {
|
|
233
|
+
return options.removeInvalidEmbeds ? null : op;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!registry.has(embedType)) {
|
|
237
|
+
return options.removeInvalidEmbeds ? null : op;
|
|
238
|
+
}
|
|
239
|
+
const format = registry.get(embedType);
|
|
240
|
+
if (format?.validate && !format.validate(embedValue[embedType])) {
|
|
241
|
+
return options.removeInvalidEmbeds ? null : op;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const sanitizedAttrs = sanitizeAttributes(op.attributes, registry, options);
|
|
245
|
+
if (sanitizedAttrs === op.attributes) {
|
|
246
|
+
return op;
|
|
247
|
+
}
|
|
248
|
+
const newOp = { insert: op.insert };
|
|
249
|
+
if (sanitizedAttrs !== void 0) {
|
|
250
|
+
newOp.attributes = sanitizedAttrs;
|
|
251
|
+
}
|
|
252
|
+
return newOp;
|
|
253
|
+
}
|
|
254
|
+
if (isRetain(op)) {
|
|
255
|
+
const sanitizedAttrs = sanitizeAttributes(op.attributes, registry, options);
|
|
256
|
+
if (sanitizedAttrs === op.attributes) {
|
|
257
|
+
return op;
|
|
258
|
+
}
|
|
259
|
+
const newOp = { retain: op.retain };
|
|
260
|
+
if (sanitizedAttrs !== void 0) {
|
|
261
|
+
newOp.attributes = sanitizedAttrs;
|
|
262
|
+
}
|
|
263
|
+
return newOp;
|
|
264
|
+
}
|
|
265
|
+
return op;
|
|
266
|
+
}
|
|
267
|
+
function sanitizeAttributes(attributes, registry, options) {
|
|
268
|
+
if (!attributes) {
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
let result;
|
|
272
|
+
let hasChanges = false;
|
|
273
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
274
|
+
const format = registry.get(key);
|
|
275
|
+
if (!format && options.removeUnknown) {
|
|
276
|
+
hasChanges = true;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (format?.validate && !format.validate(value)) {
|
|
280
|
+
hasChanges = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!result) {
|
|
284
|
+
result = {};
|
|
285
|
+
}
|
|
286
|
+
if (format?.normalize && options.normalize) {
|
|
287
|
+
const normalized = format.normalize(value);
|
|
288
|
+
result[key] = normalized;
|
|
289
|
+
if (normalized !== value) {
|
|
290
|
+
hasChanges = true;
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
result[key] = value;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!hasChanges) {
|
|
297
|
+
return attributes;
|
|
298
|
+
}
|
|
299
|
+
return result && Object.keys(result).length > 0 ? result : void 0;
|
|
300
|
+
}
|
|
301
|
+
function normalizeDelta(delta, registry) {
|
|
302
|
+
return sanitizeDelta(delta, registry, {
|
|
303
|
+
removeUnknown: false,
|
|
304
|
+
normalize: true,
|
|
305
|
+
removeInvalidEmbeds: false
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function validateDelta(delta, registry) {
|
|
309
|
+
for (const op of delta.ops) {
|
|
310
|
+
if (isInsert(op) && isEmbedInsert(op)) {
|
|
311
|
+
const embedValue = op.insert;
|
|
312
|
+
const embedType = Object.keys(embedValue)[0];
|
|
313
|
+
if (!embedType) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
if (!registry.has(embedType)) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
const format = registry.get(embedType);
|
|
320
|
+
if (format?.validate && !format.validate(embedValue[embedType])) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if ("attributes" in op && op.attributes) {
|
|
325
|
+
if (!registry.validate(op.attributes)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
function cloneDelta(delta) {
|
|
333
|
+
return new Delta(deepClone(delta.ops));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/conversion/adapters/types.ts
|
|
337
|
+
var NODE_TYPE = {
|
|
338
|
+
ELEMENT_NODE: 1,
|
|
339
|
+
TEXT_NODE: 3,
|
|
340
|
+
DOCUMENT_NODE: 9,
|
|
341
|
+
DOCUMENT_FRAGMENT_NODE: 11
|
|
342
|
+
};
|
|
343
|
+
function isElement(node) {
|
|
344
|
+
return node.nodeType === NODE_TYPE.ELEMENT_NODE;
|
|
345
|
+
}
|
|
346
|
+
function isTextNode(node) {
|
|
347
|
+
return node.nodeType === NODE_TYPE.TEXT_NODE;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/schema/blocks/table.ts
|
|
351
|
+
var CELL_KEY_RE = /^(\d+):(\d+)$/;
|
|
352
|
+
function parseCellKey(key) {
|
|
353
|
+
const match = CELL_KEY_RE.exec(key);
|
|
354
|
+
if (!match) return null;
|
|
355
|
+
return [parseInt(match[1], 10), parseInt(match[2], 10)];
|
|
356
|
+
}
|
|
357
|
+
function getGridDimensions(cells) {
|
|
358
|
+
let maxRow = -1;
|
|
359
|
+
let maxCol = -1;
|
|
360
|
+
for (const key of Object.keys(cells)) {
|
|
361
|
+
const parsed = parseCellKey(key);
|
|
362
|
+
if (!parsed) return null;
|
|
363
|
+
const [r, c] = parsed;
|
|
364
|
+
if (r > maxRow) maxRow = r;
|
|
365
|
+
if (c > maxCol) maxCol = c;
|
|
366
|
+
}
|
|
367
|
+
if (maxRow < 0 || maxCol < 0) return null;
|
|
368
|
+
return [maxRow + 1, maxCol + 1];
|
|
369
|
+
}
|
|
370
|
+
function isValidCellData(cell) {
|
|
371
|
+
return typeof cell === "object" && cell !== null && Array.isArray(cell.ops) && cell.ops.length > 0 && (cell.colspan === void 0 || Number.isInteger(cell.colspan) && cell.colspan >= 1) && (cell.rowspan === void 0 || Number.isInteger(cell.rowspan) && cell.rowspan >= 1);
|
|
372
|
+
}
|
|
373
|
+
function validateMergedCells(cells, rows, cols) {
|
|
374
|
+
const covered = /* @__PURE__ */ new Set();
|
|
375
|
+
for (const [key, cell] of Object.entries(cells)) {
|
|
376
|
+
if (cell === null) continue;
|
|
377
|
+
const parsed = parseCellKey(key);
|
|
378
|
+
if (!parsed) return false;
|
|
379
|
+
const [r, c] = parsed;
|
|
380
|
+
const cs = cell.colspan ?? 1;
|
|
381
|
+
const rs = cell.rowspan ?? 1;
|
|
382
|
+
if (r + rs > rows || c + cs > cols) return false;
|
|
383
|
+
for (let dr = 0; dr < rs; dr++) {
|
|
384
|
+
for (let dc = 0; dc < cs; dc++) {
|
|
385
|
+
if (dr === 0 && dc === 0) continue;
|
|
386
|
+
const coveredKey = `${r + dr}:${c + dc}`;
|
|
387
|
+
if (covered.has(coveredKey)) return false;
|
|
388
|
+
covered.add(coveredKey);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const [key, cell] of Object.entries(cells)) {
|
|
393
|
+
if (cell === null) {
|
|
394
|
+
if (!covered.has(key)) return false;
|
|
395
|
+
} else {
|
|
396
|
+
if (covered.has(key)) return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
function renderExtendedRow(data, row, cols, defaultCellTag, context, pretty) {
|
|
402
|
+
const nl = pretty ? "\n" : "";
|
|
403
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
404
|
+
let html = `${ind(2)}<tr>${nl}`;
|
|
405
|
+
for (let c = 0; c < cols; c++) {
|
|
406
|
+
const key = `${row}:${c}`;
|
|
407
|
+
const cell = data.cells[key];
|
|
408
|
+
if (cell === null) continue;
|
|
409
|
+
if (cell === void 0) continue;
|
|
410
|
+
const cellTag = row < (data.headerRows ?? 0) ? "th" : defaultCellTag;
|
|
411
|
+
const attrs = [];
|
|
412
|
+
if (cell.colspan && cell.colspan > 1) {
|
|
413
|
+
attrs.push(`colspan="${cell.colspan}"`);
|
|
414
|
+
}
|
|
415
|
+
if (cell.rowspan && cell.rowspan > 1) {
|
|
416
|
+
attrs.push(`rowspan="${cell.rowspan}"`);
|
|
417
|
+
}
|
|
418
|
+
if (data.colAligns) {
|
|
419
|
+
const align = data.colAligns[c];
|
|
420
|
+
if (align && align !== "left") {
|
|
421
|
+
attrs.push(`style="text-align: ${align}"`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
|
|
425
|
+
let content = "";
|
|
426
|
+
if (context.renderDelta) {
|
|
427
|
+
content = context.renderDelta(cell.ops);
|
|
428
|
+
}
|
|
429
|
+
html += `${ind(3)}<${cellTag}${attrStr}>${content}</${cellTag}>${nl}`;
|
|
430
|
+
}
|
|
431
|
+
html += `${ind(2)}</tr>${nl}`;
|
|
432
|
+
return html;
|
|
433
|
+
}
|
|
434
|
+
function isGfmCompatible(data) {
|
|
435
|
+
if (data.colWidths && data.colWidths.some((w) => w > 0)) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
for (const cell of Object.values(data.cells)) {
|
|
439
|
+
if (cell === null) return false;
|
|
440
|
+
if (cell.colspan && cell.colspan > 1) return false;
|
|
441
|
+
if (cell.rowspan && cell.rowspan > 1) return false;
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
function stripCellContent(rendered) {
|
|
446
|
+
return rendered.replace(/\n+$/, "").replace(/\n/g, " ");
|
|
447
|
+
}
|
|
448
|
+
function renderGfmTable(data, context) {
|
|
449
|
+
const dims = getGridDimensions(data.cells);
|
|
450
|
+
if (!dims) return "";
|
|
451
|
+
const [rows, cols] = dims;
|
|
452
|
+
const headerRows = data.headerRows ?? 0;
|
|
453
|
+
const lines = [];
|
|
454
|
+
if (headerRows === 0) {
|
|
455
|
+
const emptyParts = [];
|
|
456
|
+
for (let c = 0; c < cols; c++) {
|
|
457
|
+
emptyParts.push(" ");
|
|
458
|
+
}
|
|
459
|
+
lines.push("| " + emptyParts.join(" | ") + " |");
|
|
460
|
+
lines.push(renderGfmSeparator(cols, data.colAligns));
|
|
461
|
+
}
|
|
462
|
+
for (let r = 0; r < rows; r++) {
|
|
463
|
+
const parts = [];
|
|
464
|
+
for (let c = 0; c < cols; c++) {
|
|
465
|
+
const cell = data.cells[`${r}:${c}`];
|
|
466
|
+
let text = "";
|
|
467
|
+
if (cell && context.renderDelta) {
|
|
468
|
+
text = stripCellContent(context.renderDelta(cell.ops));
|
|
469
|
+
}
|
|
470
|
+
parts.push(text.replace(/\|/g, "\\|"));
|
|
471
|
+
}
|
|
472
|
+
lines.push("| " + parts.join(" | ") + " |");
|
|
473
|
+
if (headerRows > 0 && r === headerRows - 1) {
|
|
474
|
+
lines.push(renderGfmSeparator(cols, data.colAligns));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return lines.join("\n");
|
|
478
|
+
}
|
|
479
|
+
function renderGfmSeparator(cols, colAligns) {
|
|
480
|
+
const sepParts = [];
|
|
481
|
+
for (let c = 0; c < cols; c++) {
|
|
482
|
+
const align = colAligns?.[c];
|
|
483
|
+
if (align === "center") {
|
|
484
|
+
sepParts.push(":---:");
|
|
485
|
+
} else if (align === "right") {
|
|
486
|
+
sepParts.push("---:");
|
|
487
|
+
} else if (align === "left") {
|
|
488
|
+
sepParts.push(":---");
|
|
489
|
+
} else {
|
|
490
|
+
sepParts.push("---");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return "| " + sepParts.join(" | ") + " |";
|
|
494
|
+
}
|
|
495
|
+
function collectTableRows(table) {
|
|
496
|
+
const rows = [];
|
|
497
|
+
let headerRowCount = 0;
|
|
498
|
+
const children = table.childNodes;
|
|
499
|
+
for (let i = 0; i < children.length; i++) {
|
|
500
|
+
const section = children[i];
|
|
501
|
+
if (!section || !isElement(section)) continue;
|
|
502
|
+
const sectionTag = section.tagName.toLowerCase();
|
|
503
|
+
if (sectionTag === "thead" || sectionTag === "tbody" || sectionTag === "tfoot") {
|
|
504
|
+
const isHeader = sectionTag === "thead";
|
|
505
|
+
const sectionChildren = section.childNodes;
|
|
506
|
+
for (let j = 0; j < sectionChildren.length; j++) {
|
|
507
|
+
const row = sectionChildren[j];
|
|
508
|
+
if (!row || !isElement(row) || row.tagName.toLowerCase() !== "tr") continue;
|
|
509
|
+
rows.push(row);
|
|
510
|
+
if (isHeader) headerRowCount++;
|
|
511
|
+
}
|
|
512
|
+
} else if (sectionTag === "tr") {
|
|
513
|
+
rows.push(section);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return { rows, headerRowCount };
|
|
517
|
+
}
|
|
518
|
+
function extractColWidths(table) {
|
|
519
|
+
const children = table.childNodes;
|
|
520
|
+
for (let i = 0; i < children.length; i++) {
|
|
521
|
+
const child = children[i];
|
|
522
|
+
if (!child || !isElement(child) || child.tagName.toLowerCase() !== "colgroup") continue;
|
|
523
|
+
const widths = [];
|
|
524
|
+
const cols = child.childNodes;
|
|
525
|
+
for (let j = 0; j < cols.length; j++) {
|
|
526
|
+
const col = cols[j];
|
|
527
|
+
if (!col || !isElement(col) || col.tagName.toLowerCase() !== "col") continue;
|
|
528
|
+
let width = 0;
|
|
529
|
+
const style = col.getAttribute("style") || "";
|
|
530
|
+
const widthMatch = style.match(/width:\s*([\d.]+)(%|px)/);
|
|
531
|
+
if (widthMatch?.[1]) {
|
|
532
|
+
width = parseFloat(widthMatch[1]);
|
|
533
|
+
} else {
|
|
534
|
+
const widthAttr = col.getAttribute("width");
|
|
535
|
+
if (widthAttr) {
|
|
536
|
+
width = parseFloat(widthAttr) || 0;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
widths.push(width);
|
|
540
|
+
}
|
|
541
|
+
if (widths.length > 0) return widths;
|
|
542
|
+
}
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
function extractCellAlign(cell) {
|
|
546
|
+
const textAlign = cell.style?.textAlign || cell.style?.getPropertyValue?.("text-align");
|
|
547
|
+
if (textAlign === "left" || textAlign === "center" || textAlign === "right") {
|
|
548
|
+
return textAlign;
|
|
549
|
+
}
|
|
550
|
+
const style = cell.getAttribute("style") || "";
|
|
551
|
+
const match = style.match(/text-align:\s*(left|center|right)/);
|
|
552
|
+
if (match?.[1]) {
|
|
553
|
+
return match[1];
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
function parseTableElement(table, context) {
|
|
558
|
+
const { rows, headerRowCount } = collectTableRows(table);
|
|
559
|
+
if (rows.length === 0) return null;
|
|
560
|
+
const cells = {};
|
|
561
|
+
const colAligns = [];
|
|
562
|
+
let maxCol = 0;
|
|
563
|
+
let firstRowProcessed = false;
|
|
564
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
565
|
+
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
566
|
+
const row = rows[rowIdx];
|
|
567
|
+
let colIdx = 0;
|
|
568
|
+
const cellChildren = row.childNodes;
|
|
569
|
+
for (let ci = 0; ci < cellChildren.length; ci++) {
|
|
570
|
+
const cell = cellChildren[ci];
|
|
571
|
+
if (!cell || !isElement(cell)) continue;
|
|
572
|
+
const cellTag = cell.tagName.toLowerCase();
|
|
573
|
+
if (cellTag !== "td" && cellTag !== "th") continue;
|
|
574
|
+
while (occupied.has(`${rowIdx}:${colIdx}`)) {
|
|
575
|
+
colIdx++;
|
|
576
|
+
}
|
|
577
|
+
const colspanAttr = cell.getAttribute("colspan");
|
|
578
|
+
const rowspanAttr = cell.getAttribute("rowspan");
|
|
579
|
+
const colspan = colspanAttr ? parseInt(colspanAttr, 10) || 1 : 1;
|
|
580
|
+
const rowspan = rowspanAttr ? parseInt(rowspanAttr, 10) || 1 : 1;
|
|
581
|
+
let ops;
|
|
582
|
+
if (context.parseElement) {
|
|
583
|
+
ops = context.parseElement(cell);
|
|
584
|
+
if (ops.length === 0) {
|
|
585
|
+
ops = [{ insert: "\n" }];
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
ops = [{ insert: "\n" }];
|
|
589
|
+
}
|
|
590
|
+
const cellData = { ops };
|
|
591
|
+
if (colspan > 1) cellData.colspan = colspan;
|
|
592
|
+
if (rowspan > 1) cellData.rowspan = rowspan;
|
|
593
|
+
cells[`${rowIdx}:${colIdx}`] = cellData;
|
|
594
|
+
for (let dr = 0; dr < rowspan; dr++) {
|
|
595
|
+
for (let dc = 0; dc < colspan; dc++) {
|
|
596
|
+
if (dr === 0 && dc === 0) continue;
|
|
597
|
+
const coveredKey = `${rowIdx + dr}:${colIdx + dc}`;
|
|
598
|
+
occupied.add(coveredKey);
|
|
599
|
+
cells[coveredKey] = null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
for (let dc = 1; dc < colspan; dc++) {
|
|
603
|
+
occupied.add(`${rowIdx}:${colIdx + dc}`);
|
|
604
|
+
}
|
|
605
|
+
if (!firstRowProcessed) {
|
|
606
|
+
const align = extractCellAlign(cell);
|
|
607
|
+
for (let dc = 0; dc < colspan; dc++) {
|
|
608
|
+
colAligns.push(align);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const cellEndCol = colIdx + colspan - 1;
|
|
612
|
+
if (cellEndCol > maxCol) maxCol = cellEndCol;
|
|
613
|
+
colIdx += colspan;
|
|
614
|
+
}
|
|
615
|
+
while (occupied.has(`${rowIdx}:${colIdx}`)) {
|
|
616
|
+
colIdx++;
|
|
617
|
+
}
|
|
618
|
+
if (colIdx - 1 > maxCol) maxCol = colIdx - 1;
|
|
619
|
+
if (!firstRowProcessed) firstRowProcessed = true;
|
|
620
|
+
}
|
|
621
|
+
const totalCols = maxCol + 1;
|
|
622
|
+
const totalRows = rows.length;
|
|
623
|
+
for (let r = 0; r < totalRows; r++) {
|
|
624
|
+
for (let c = 0; c < totalCols; c++) {
|
|
625
|
+
const key = `${r}:${c}`;
|
|
626
|
+
if (!(key in cells)) {
|
|
627
|
+
cells[key] = { ops: [{ insert: "\n" }] };
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const result = {
|
|
632
|
+
type: "table",
|
|
633
|
+
cells
|
|
634
|
+
};
|
|
635
|
+
if (headerRowCount > 0) {
|
|
636
|
+
result.headerRows = headerRowCount;
|
|
637
|
+
}
|
|
638
|
+
const colWidths = extractColWidths(table);
|
|
639
|
+
if (colWidths) {
|
|
640
|
+
while (colWidths.length < totalCols) colWidths.push(0);
|
|
641
|
+
if (colWidths.length > totalCols) colWidths.length = totalCols;
|
|
642
|
+
result.colWidths = colWidths;
|
|
643
|
+
}
|
|
644
|
+
if (colAligns.length > 0 && colAligns.some((a) => a !== null)) {
|
|
645
|
+
while (colAligns.length < totalCols) colAligns.push(null);
|
|
646
|
+
if (colAligns.length > totalCols) colAligns.length = totalCols;
|
|
647
|
+
result.colAligns = colAligns;
|
|
648
|
+
}
|
|
649
|
+
return result;
|
|
650
|
+
}
|
|
651
|
+
var tableBlockHandler = {
|
|
652
|
+
type: "table",
|
|
653
|
+
validate(data) {
|
|
654
|
+
if (!data || typeof data !== "object" || data.type !== "table") {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (!data.cells || typeof data.cells !== "object" || Array.isArray(data.cells)) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
const cellKeys = Object.keys(data.cells);
|
|
661
|
+
if (cellKeys.length === 0) return false;
|
|
662
|
+
for (const key of cellKeys) {
|
|
663
|
+
if (!CELL_KEY_RE.test(key)) return false;
|
|
664
|
+
}
|
|
665
|
+
const dims = getGridDimensions(data.cells);
|
|
666
|
+
if (!dims) return false;
|
|
667
|
+
const [rows, cols] = dims;
|
|
668
|
+
if (cellKeys.length !== rows * cols) return false;
|
|
669
|
+
for (let r = 0; r < rows; r++) {
|
|
670
|
+
for (let c = 0; c < cols; c++) {
|
|
671
|
+
if (!(`${r}:${c}` in data.cells)) return false;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
for (const [, cell] of Object.entries(data.cells)) {
|
|
675
|
+
if (cell !== null && !isValidCellData(cell)) return false;
|
|
676
|
+
}
|
|
677
|
+
if (!validateMergedCells(data.cells, rows, cols)) return false;
|
|
678
|
+
if (data.headerRows !== void 0) {
|
|
679
|
+
if (!Number.isInteger(data.headerRows) || data.headerRows < 0 || data.headerRows > rows) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (data.colWidths !== void 0) {
|
|
684
|
+
if (!Array.isArray(data.colWidths) || data.colWidths.length !== cols) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
for (const w of data.colWidths) {
|
|
688
|
+
if (typeof w !== "number" || w < 0) return false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (data.colAligns !== void 0) {
|
|
692
|
+
if (!Array.isArray(data.colAligns) || data.colAligns.length !== cols) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
const validAligns = ["left", "center", "right", null];
|
|
696
|
+
for (const a of data.colAligns) {
|
|
697
|
+
if (!validAligns.includes(a)) return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return true;
|
|
701
|
+
},
|
|
702
|
+
// ── Conversion ──────────────────────────────────────────────
|
|
703
|
+
toHtml(data, context) {
|
|
704
|
+
const pretty = context.options?.pretty ?? false;
|
|
705
|
+
const nl = pretty ? "\n" : "";
|
|
706
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
707
|
+
const dims = getGridDimensions(data.cells);
|
|
708
|
+
if (!dims) return "";
|
|
709
|
+
const [rows, cols] = dims;
|
|
710
|
+
const headerRows = data.headerRows ?? 0;
|
|
711
|
+
let html = `<table>${nl}`;
|
|
712
|
+
if (data.colWidths && data.colWidths.length > 0) {
|
|
713
|
+
const widthMode = context.options?.tableWidthMode ?? "percent";
|
|
714
|
+
html += `${ind(1)}<colgroup>${nl}`;
|
|
715
|
+
for (let c = 0; c < cols; c++) {
|
|
716
|
+
const w = data.colWidths[c];
|
|
717
|
+
if (w !== void 0 && w > 0) {
|
|
718
|
+
const unit = widthMode === "pixel" ? "px" : "%";
|
|
719
|
+
html += `${ind(2)}<col style="width: ${w}${unit}">${nl}`;
|
|
720
|
+
} else {
|
|
721
|
+
html += `${ind(2)}<col>${nl}`;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
html += `${ind(1)}</colgroup>${nl}`;
|
|
725
|
+
}
|
|
726
|
+
if (headerRows > 0) {
|
|
727
|
+
html += `${ind(1)}<thead>${nl}`;
|
|
728
|
+
for (let r = 0; r < headerRows; r++) {
|
|
729
|
+
html += renderExtendedRow(data, r, cols, "th", context, pretty);
|
|
730
|
+
}
|
|
731
|
+
html += `${ind(1)}</thead>${nl}`;
|
|
732
|
+
}
|
|
733
|
+
const bodyStart = headerRows;
|
|
734
|
+
if (bodyStart < rows) {
|
|
735
|
+
html += `${ind(1)}<tbody>${nl}`;
|
|
736
|
+
for (let r = bodyStart; r < rows; r++) {
|
|
737
|
+
html += renderExtendedRow(data, r, cols, "td", context, pretty);
|
|
738
|
+
}
|
|
739
|
+
html += `${ind(1)}</tbody>${nl}`;
|
|
740
|
+
}
|
|
741
|
+
html += `</table>`;
|
|
742
|
+
return html;
|
|
743
|
+
},
|
|
744
|
+
fromHtml(element, context) {
|
|
745
|
+
return parseTableElement(element, context);
|
|
746
|
+
},
|
|
747
|
+
toMarkdown(data, context) {
|
|
748
|
+
if (isGfmCompatible(data)) {
|
|
749
|
+
return renderGfmTable(data, context);
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
},
|
|
753
|
+
// ── Normalize ─────────────────────────────────────────────
|
|
754
|
+
normalize(data, registry) {
|
|
755
|
+
const newCells = {};
|
|
756
|
+
let changed = false;
|
|
757
|
+
for (const [key, cell] of Object.entries(data.cells)) {
|
|
758
|
+
if (cell !== null) {
|
|
759
|
+
const normalized = normalizeDelta(new Delta2(cell.ops), registry);
|
|
760
|
+
if (normalized.ops !== cell.ops) {
|
|
761
|
+
newCells[key] = { ...cell, ops: normalized.ops };
|
|
762
|
+
changed = true;
|
|
763
|
+
} else {
|
|
764
|
+
newCells[key] = cell;
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
newCells[key] = null;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return changed ? { ...data, cells: newCells } : data;
|
|
771
|
+
},
|
|
772
|
+
// ── Nested Deltas ──────────────────────────────────────────
|
|
773
|
+
getNestedDeltas(data) {
|
|
774
|
+
const deltas = [];
|
|
775
|
+
for (const cell of Object.values(data.cells)) {
|
|
776
|
+
if (cell !== null) {
|
|
777
|
+
deltas.push(cell.ops);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return deltas;
|
|
781
|
+
},
|
|
782
|
+
setNestedDeltas(data, deltas) {
|
|
783
|
+
const newCells = {};
|
|
784
|
+
let idx = 0;
|
|
785
|
+
for (const [key, cell] of Object.entries(data.cells)) {
|
|
786
|
+
if (cell !== null) {
|
|
787
|
+
newCells[key] = { ...cell, ops: deltas[idx++] };
|
|
788
|
+
} else {
|
|
789
|
+
newCells[key] = null;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return { ...data, cells: newCells };
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// src/schema/blocks/footnotes.ts
|
|
797
|
+
import { isTextInsert, Delta as Delta3 } from "@scrider/delta";
|
|
798
|
+
function isValidNoteData(note) {
|
|
799
|
+
return typeof note === "object" && note !== null && Array.isArray(note.ops) && note.ops.length > 0;
|
|
800
|
+
}
|
|
801
|
+
function extractFootnoteId(element) {
|
|
802
|
+
const id = element.getAttribute("id") || "";
|
|
803
|
+
if (id.startsWith("fn-")) {
|
|
804
|
+
return id.slice(3);
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
var footnotesBlockHandler = {
|
|
809
|
+
type: "footnotes",
|
|
810
|
+
validate(data) {
|
|
811
|
+
if (!data || typeof data !== "object" || data.type !== "footnotes") {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
if (!data.notes || typeof data.notes !== "object" || Array.isArray(data.notes)) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
const noteIds = Object.keys(data.notes);
|
|
818
|
+
if (noteIds.length === 0) return false;
|
|
819
|
+
for (const id of noteIds) {
|
|
820
|
+
if (typeof id !== "string" || id.trim().length === 0) return false;
|
|
821
|
+
}
|
|
822
|
+
for (const note of Object.values(data.notes)) {
|
|
823
|
+
if (!isValidNoteData(note)) return false;
|
|
824
|
+
}
|
|
825
|
+
return true;
|
|
826
|
+
},
|
|
827
|
+
// ── Conversion ──────────────────────────────────────────────
|
|
828
|
+
toHtml(data, context) {
|
|
829
|
+
const pretty = context.options?.pretty ?? false;
|
|
830
|
+
const nl = pretty ? "\n" : "";
|
|
831
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
832
|
+
const noteEntries = Object.entries(data.notes);
|
|
833
|
+
if (noteEntries.length === 0) return "";
|
|
834
|
+
let html = `<section class="footnotes">${nl}`;
|
|
835
|
+
html += `${ind(1)}<ol>${nl}`;
|
|
836
|
+
for (const [id, note] of noteEntries) {
|
|
837
|
+
html += `${ind(2)}<li id="fn-${id}">${nl}`;
|
|
838
|
+
let content = "";
|
|
839
|
+
if (context.renderDelta) {
|
|
840
|
+
content = context.renderDelta(note.ops);
|
|
841
|
+
}
|
|
842
|
+
const backref = ` <a href="#fnref-${id}" class="footnote-backref">\u21A9</a>`;
|
|
843
|
+
const lastPClose = content.lastIndexOf("</p>");
|
|
844
|
+
if (lastPClose !== -1) {
|
|
845
|
+
content = content.slice(0, lastPClose) + backref + content.slice(lastPClose);
|
|
846
|
+
} else {
|
|
847
|
+
content += backref;
|
|
848
|
+
}
|
|
849
|
+
html += `${ind(3)}${content}${nl}`;
|
|
850
|
+
html += `${ind(2)}</li>${nl}`;
|
|
851
|
+
}
|
|
852
|
+
html += `${ind(1)}</ol>${nl}`;
|
|
853
|
+
html += `</section>`;
|
|
854
|
+
return html;
|
|
855
|
+
},
|
|
856
|
+
fromHtml(element, context) {
|
|
857
|
+
const tag = element.tagName.toLowerCase();
|
|
858
|
+
if (tag !== "section" && tag !== "div") return null;
|
|
859
|
+
const className = element.getAttribute("class") || "";
|
|
860
|
+
if (!className.includes("footnotes")) return null;
|
|
861
|
+
let ol = null;
|
|
862
|
+
const children = element.childNodes;
|
|
863
|
+
for (let i = 0; i < children.length; i++) {
|
|
864
|
+
const child = children[i];
|
|
865
|
+
if (child && isElement(child) && child.tagName.toLowerCase() === "ol") {
|
|
866
|
+
ol = child;
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (!ol) return null;
|
|
871
|
+
const notes = {};
|
|
872
|
+
const liChildren = ol.childNodes;
|
|
873
|
+
for (let i = 0; i < liChildren.length; i++) {
|
|
874
|
+
const li = liChildren[i];
|
|
875
|
+
if (!li || !isElement(li) || li.tagName.toLowerCase() !== "li") continue;
|
|
876
|
+
const id = extractFootnoteId(li);
|
|
877
|
+
if (!id) continue;
|
|
878
|
+
let ops;
|
|
879
|
+
if (context.parseElement) {
|
|
880
|
+
ops = context.parseElement(li);
|
|
881
|
+
ops = ops.map((op) => {
|
|
882
|
+
if (isTextInsert(op) && op.insert.includes("\u21A9")) {
|
|
883
|
+
return { ...op, insert: op.insert.replace(/\u21a9/g, "") };
|
|
884
|
+
}
|
|
885
|
+
return op;
|
|
886
|
+
}).filter((op) => !isTextInsert(op) || op.insert !== "");
|
|
887
|
+
if (ops.length === 0) {
|
|
888
|
+
ops = [{ insert: "\n" }];
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
ops = [{ insert: "\n" }];
|
|
892
|
+
}
|
|
893
|
+
notes[id] = { ops };
|
|
894
|
+
}
|
|
895
|
+
if (Object.keys(notes).length === 0) return null;
|
|
896
|
+
return { type: "footnotes", notes };
|
|
897
|
+
},
|
|
898
|
+
toMarkdown(data, context) {
|
|
899
|
+
const noteEntries = Object.entries(data.notes);
|
|
900
|
+
if (noteEntries.length === 0) return null;
|
|
901
|
+
const lines = [];
|
|
902
|
+
for (const [id, note] of noteEntries) {
|
|
903
|
+
let content;
|
|
904
|
+
if (context.renderDelta) {
|
|
905
|
+
content = context.renderDelta(note.ops).trim();
|
|
906
|
+
} else {
|
|
907
|
+
content = "";
|
|
908
|
+
}
|
|
909
|
+
const contentLines = content.split("\n");
|
|
910
|
+
const indented = contentLines.map((line, idx) => idx === 0 ? `[^${id}]: ${line}` : ` ${line}`).join("\n");
|
|
911
|
+
lines.push(indented);
|
|
912
|
+
}
|
|
913
|
+
return "\n" + lines.join("\n\n");
|
|
914
|
+
},
|
|
915
|
+
// ── Normalize ─────────────────────────────────────────────
|
|
916
|
+
normalize(data, registry) {
|
|
917
|
+
const newNotes = {};
|
|
918
|
+
let changed = false;
|
|
919
|
+
for (const [id, note] of Object.entries(data.notes)) {
|
|
920
|
+
const normalized = normalizeDelta(new Delta3(note.ops), registry);
|
|
921
|
+
if (normalized.ops !== note.ops) {
|
|
922
|
+
newNotes[id] = { ops: normalized.ops };
|
|
923
|
+
changed = true;
|
|
924
|
+
} else {
|
|
925
|
+
newNotes[id] = note;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return changed ? { ...data, notes: newNotes } : data;
|
|
929
|
+
},
|
|
930
|
+
// ── Nested Deltas ──────────────────────────────────────────
|
|
931
|
+
getNestedDeltas(data) {
|
|
932
|
+
return Object.values(data.notes).map((note) => note.ops);
|
|
933
|
+
},
|
|
934
|
+
setNestedDeltas(data, deltas) {
|
|
935
|
+
const ids = Object.keys(data.notes);
|
|
936
|
+
const newNotes = {};
|
|
937
|
+
ids.forEach((id, i) => {
|
|
938
|
+
newNotes[id] = { ops: deltas[i] };
|
|
939
|
+
});
|
|
940
|
+
return { ...data, notes: newNotes };
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// src/schema/blocks/alert.ts
|
|
945
|
+
import { Delta as Delta4 } from "@scrider/delta";
|
|
946
|
+
var ALERT_TYPES = ["note", "tip", "important", "warning", "caution"];
|
|
947
|
+
function isAlertType(value) {
|
|
948
|
+
return ALERT_TYPES.includes(value);
|
|
949
|
+
}
|
|
950
|
+
function capitalize(s) {
|
|
951
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
952
|
+
}
|
|
953
|
+
var alertBlockHandler = {
|
|
954
|
+
type: "alert",
|
|
955
|
+
validate(data) {
|
|
956
|
+
if (!data || typeof data !== "object" || data.type !== "alert") {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
if (typeof data.alertType !== "string" || !isAlertType(data.alertType)) {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
if (!data.content || typeof data.content !== "object" || !Array.isArray(data.content.ops) || data.content.ops.length === 0) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
return true;
|
|
966
|
+
},
|
|
967
|
+
// ── Conversion ──────────────────────────────────────────────
|
|
968
|
+
toHtml(data, context) {
|
|
969
|
+
const pretty = context.options?.pretty ?? false;
|
|
970
|
+
const nl = pretty ? "\n" : "";
|
|
971
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
972
|
+
const alertClass = `markdown-alert markdown-alert-${data.alertType}`;
|
|
973
|
+
const title = capitalize(data.alertType);
|
|
974
|
+
let html = `<div class="${alertClass}">${nl}`;
|
|
975
|
+
html += `${ind(1)}<p class="markdown-alert-title">${title}</p>${nl}`;
|
|
976
|
+
if (context.renderDelta) {
|
|
977
|
+
html += `${ind(1)}${context.renderDelta(data.content.ops)}${nl}`;
|
|
978
|
+
}
|
|
979
|
+
html += `</div>`;
|
|
980
|
+
return html;
|
|
981
|
+
},
|
|
982
|
+
fromHtml(element, context) {
|
|
983
|
+
const tag = element.tagName.toLowerCase();
|
|
984
|
+
if (tag !== "div" && tag !== "section") return null;
|
|
985
|
+
const className = element.getAttribute("class") || "";
|
|
986
|
+
if (!className.includes("markdown-alert")) return null;
|
|
987
|
+
let alertType = "note";
|
|
988
|
+
for (const t of ALERT_TYPES) {
|
|
989
|
+
if (className.includes(`markdown-alert-${t}`)) {
|
|
990
|
+
alertType = t;
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
let ops = [];
|
|
995
|
+
const children = element.childNodes;
|
|
996
|
+
for (let i = 0; i < children.length; i++) {
|
|
997
|
+
const child = children[i];
|
|
998
|
+
if (!child || !isElement(child)) continue;
|
|
999
|
+
const childClass = child.getAttribute("class") || "";
|
|
1000
|
+
if (childClass.includes("markdown-alert-title")) continue;
|
|
1001
|
+
if (context.parseElement) {
|
|
1002
|
+
const parsed = context.parseElement(child);
|
|
1003
|
+
ops = ops.concat(parsed);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (ops.length === 0) {
|
|
1007
|
+
ops = [{ insert: "\n" }];
|
|
1008
|
+
}
|
|
1009
|
+
return {
|
|
1010
|
+
type: "alert",
|
|
1011
|
+
alertType,
|
|
1012
|
+
content: { ops }
|
|
1013
|
+
};
|
|
1014
|
+
},
|
|
1015
|
+
toMarkdown(data, context) {
|
|
1016
|
+
const tag = `[!${data.alertType.toUpperCase()}]`;
|
|
1017
|
+
let contentMd = "";
|
|
1018
|
+
if (context.renderDelta) {
|
|
1019
|
+
contentMd = context.renderDelta(data.content.ops);
|
|
1020
|
+
}
|
|
1021
|
+
const lines = contentMd.split("\n");
|
|
1022
|
+
while (lines.length > 0 && (lines[lines.length - 1] ?? "").trim() === "") {
|
|
1023
|
+
lines.pop();
|
|
1024
|
+
}
|
|
1025
|
+
const prefixed = lines.map((line) => `> ${line}`).join("\n");
|
|
1026
|
+
return `> ${tag}
|
|
1027
|
+
${prefixed}`;
|
|
1028
|
+
},
|
|
1029
|
+
// ── Normalization ──────────────────────────────────────────
|
|
1030
|
+
normalize(data, registry) {
|
|
1031
|
+
const normalized = normalizeDelta(new Delta4(data.content.ops), registry);
|
|
1032
|
+
return {
|
|
1033
|
+
...data,
|
|
1034
|
+
content: { ops: normalized.ops }
|
|
1035
|
+
};
|
|
1036
|
+
},
|
|
1037
|
+
// ── Nested Deltas ──────────────────────────────────────────
|
|
1038
|
+
getNestedDeltas(data) {
|
|
1039
|
+
return [data.content.ops];
|
|
1040
|
+
},
|
|
1041
|
+
setNestedDeltas(data, deltas) {
|
|
1042
|
+
const first = deltas[0];
|
|
1043
|
+
if (!first) return data;
|
|
1044
|
+
return {
|
|
1045
|
+
...data,
|
|
1046
|
+
content: { ops: first }
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// src/schema/blocks/columns.ts
|
|
1052
|
+
import { Delta as Delta5 } from "@scrider/delta";
|
|
1053
|
+
var WIDTHS_SUM_TOLERANCE = 1;
|
|
1054
|
+
var columnsBlockHandler = {
|
|
1055
|
+
type: "columns",
|
|
1056
|
+
validate(data) {
|
|
1057
|
+
if (!data || typeof data !== "object" || data.type !== "columns") {
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
if (!Array.isArray(data.columns) || data.columns.length < 2) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
for (const col of data.columns) {
|
|
1064
|
+
if (!col || typeof col !== "object" || !Array.isArray(col.ops) || col.ops.length === 0) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (data.widths !== void 0) {
|
|
1069
|
+
if (!Array.isArray(data.widths)) {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
if (data.widths.length !== data.columns.length) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
for (const w of data.widths) {
|
|
1076
|
+
if (typeof w !== "number" || w <= 0 || !isFinite(w)) {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const sum = data.widths.reduce((a, b) => a + b, 0);
|
|
1081
|
+
if (Math.abs(sum - 100) > WIDTHS_SUM_TOLERANCE) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return true;
|
|
1086
|
+
},
|
|
1087
|
+
// ── Conversion ──────────────────────────────────────────────
|
|
1088
|
+
toHtml(data, context) {
|
|
1089
|
+
const n = data.columns.length;
|
|
1090
|
+
const pretty = context.options?.pretty ?? false;
|
|
1091
|
+
const nl = pretty ? "\n" : "";
|
|
1092
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
1093
|
+
const classAttr = `columns columns-${n}`;
|
|
1094
|
+
let styleAttr = "";
|
|
1095
|
+
if (data.widths && data.widths.length === n) {
|
|
1096
|
+
const cols = data.widths.map((w) => `${w}%`).join(" ");
|
|
1097
|
+
styleAttr = ` style="grid-template-columns: ${cols}"`;
|
|
1098
|
+
}
|
|
1099
|
+
let html = `<div class="${classAttr}"${styleAttr}>${nl}`;
|
|
1100
|
+
for (const col of data.columns) {
|
|
1101
|
+
html += `${ind(1)}<div class="column">${nl}`;
|
|
1102
|
+
if (context.renderDelta) {
|
|
1103
|
+
html += `${ind(2)}${context.renderDelta(col.ops)}${nl}`;
|
|
1104
|
+
}
|
|
1105
|
+
html += `${ind(1)}</div>${nl}`;
|
|
1106
|
+
}
|
|
1107
|
+
html += `</div>`;
|
|
1108
|
+
return html;
|
|
1109
|
+
},
|
|
1110
|
+
fromHtml(element, context) {
|
|
1111
|
+
const tag = element.tagName.toLowerCase();
|
|
1112
|
+
if (tag !== "div") return null;
|
|
1113
|
+
const className = element.getAttribute("class") || "";
|
|
1114
|
+
if (!className.includes("columns")) return null;
|
|
1115
|
+
if (!/\bcolumns\b/.test(className)) return null;
|
|
1116
|
+
const columns = [];
|
|
1117
|
+
const children = element.childNodes;
|
|
1118
|
+
for (let i = 0; i < children.length; i++) {
|
|
1119
|
+
const child = children[i];
|
|
1120
|
+
if (!child || !isElement(child)) continue;
|
|
1121
|
+
const childClass = child.getAttribute("class") || "";
|
|
1122
|
+
if (!/\bcolumn\b/.test(childClass)) continue;
|
|
1123
|
+
let ops = [];
|
|
1124
|
+
if (context.parseElement) {
|
|
1125
|
+
const childChildren = child.childNodes;
|
|
1126
|
+
for (let j = 0; j < childChildren.length; j++) {
|
|
1127
|
+
const el = childChildren[j];
|
|
1128
|
+
if (el && isElement(el)) {
|
|
1129
|
+
const parsed = context.parseElement(el);
|
|
1130
|
+
ops = ops.concat(parsed);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (ops.length === 0) {
|
|
1135
|
+
ops = [{ insert: "\n" }];
|
|
1136
|
+
}
|
|
1137
|
+
columns.push({ ops });
|
|
1138
|
+
}
|
|
1139
|
+
if (columns.length === 0) return null;
|
|
1140
|
+
let widths;
|
|
1141
|
+
const style = element.getAttribute("style") || "";
|
|
1142
|
+
const match = style.match(/grid-template-columns:\s*(.+?)(?:;|$)/);
|
|
1143
|
+
if (match && match[1]) {
|
|
1144
|
+
const parts = match[1].trim().split(/\s+/);
|
|
1145
|
+
const parsed = parts.map((p) => parseFloat(p)).filter((n) => !isNaN(n) && n > 0);
|
|
1146
|
+
if (parsed.length === columns.length) {
|
|
1147
|
+
widths = parsed;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const result = {
|
|
1151
|
+
type: "columns",
|
|
1152
|
+
columns
|
|
1153
|
+
};
|
|
1154
|
+
if (widths) {
|
|
1155
|
+
result.widths = widths;
|
|
1156
|
+
}
|
|
1157
|
+
return result;
|
|
1158
|
+
},
|
|
1159
|
+
toMarkdown(_data, _context) {
|
|
1160
|
+
return null;
|
|
1161
|
+
},
|
|
1162
|
+
// ── Normalization ──────────────────────────────────────────
|
|
1163
|
+
normalize(data, registry) {
|
|
1164
|
+
return {
|
|
1165
|
+
...data,
|
|
1166
|
+
columns: data.columns.map((col) => ({
|
|
1167
|
+
ops: normalizeDelta(new Delta5(col.ops), registry).ops
|
|
1168
|
+
}))
|
|
1169
|
+
};
|
|
1170
|
+
},
|
|
1171
|
+
// ── Nested Deltas ──────────────────────────────────────────
|
|
1172
|
+
getNestedDeltas(data) {
|
|
1173
|
+
return data.columns.map((col) => col.ops);
|
|
1174
|
+
},
|
|
1175
|
+
setNestedDeltas(data, deltas) {
|
|
1176
|
+
return {
|
|
1177
|
+
...data,
|
|
1178
|
+
columns: data.columns.map((col, i) => ({
|
|
1179
|
+
...col,
|
|
1180
|
+
ops: deltas[i] ?? col.ops
|
|
1181
|
+
}))
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/schema/blocks/box.ts
|
|
1187
|
+
import { Delta as Delta6 } from "@scrider/delta";
|
|
1188
|
+
var BOX_FLOAT_VALUES = ["left", "right", "center"];
|
|
1189
|
+
var BOX_OVERFLOW_VALUES = ["auto", "hidden", "visible"];
|
|
1190
|
+
function isBoxFloat(value) {
|
|
1191
|
+
return BOX_FLOAT_VALUES.includes(value);
|
|
1192
|
+
}
|
|
1193
|
+
function isBoxOverflow(value) {
|
|
1194
|
+
return BOX_OVERFLOW_VALUES.includes(value);
|
|
1195
|
+
}
|
|
1196
|
+
var boxBlockHandler = {
|
|
1197
|
+
type: "box",
|
|
1198
|
+
validate(data) {
|
|
1199
|
+
if (!data || typeof data !== "object" || data.type !== "box") {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
if (!data.content || typeof data.content !== "object" || !Array.isArray(data.content.ops) || data.content.ops.length === 0) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
},
|
|
1207
|
+
// ── Conversion ──────────────────────────────────────────────
|
|
1208
|
+
toHtml(data, context) {
|
|
1209
|
+
const pretty = context.options?.pretty ?? false;
|
|
1210
|
+
const nl = pretty ? "\n" : "";
|
|
1211
|
+
const ind = (level) => pretty ? " ".repeat(level) : "";
|
|
1212
|
+
const opAttrs = context.opAttributes;
|
|
1213
|
+
const float = opAttrs?.float || "left";
|
|
1214
|
+
const width = opAttrs?.width;
|
|
1215
|
+
const height = opAttrs?.height;
|
|
1216
|
+
const overflow = opAttrs?.overflow || "auto";
|
|
1217
|
+
let attrs = `class="inline-box" data-float="${float}"`;
|
|
1218
|
+
if (overflow !== "auto") {
|
|
1219
|
+
attrs += ` data-overflow="${overflow}"`;
|
|
1220
|
+
}
|
|
1221
|
+
const styles = [];
|
|
1222
|
+
if (width && width !== "auto") styles.push(`width: ${width}`);
|
|
1223
|
+
if (height && height !== "auto") styles.push(`height: ${height}`);
|
|
1224
|
+
if (styles.length > 0) {
|
|
1225
|
+
attrs += ` style="${styles.join("; ")}"`;
|
|
1226
|
+
}
|
|
1227
|
+
let html = `<div ${attrs}>${nl}`;
|
|
1228
|
+
if (context.renderDelta) {
|
|
1229
|
+
html += `${ind(1)}${context.renderDelta(data.content.ops)}${nl}`;
|
|
1230
|
+
}
|
|
1231
|
+
html += `</div>`;
|
|
1232
|
+
return html;
|
|
1233
|
+
},
|
|
1234
|
+
fromHtml(element, context) {
|
|
1235
|
+
const tag = element.tagName.toLowerCase();
|
|
1236
|
+
if (tag !== "div") return null;
|
|
1237
|
+
const className = element.getAttribute("class") || "";
|
|
1238
|
+
if (!className.includes("inline-box")) return null;
|
|
1239
|
+
let ops = [];
|
|
1240
|
+
const children = element.childNodes;
|
|
1241
|
+
for (let i = 0; i < children.length; i++) {
|
|
1242
|
+
const child = children[i];
|
|
1243
|
+
if (!child || !isElement(child)) continue;
|
|
1244
|
+
if (context.parseElement) {
|
|
1245
|
+
const parsed = context.parseElement(child);
|
|
1246
|
+
ops = ops.concat(parsed);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (ops.length === 0) {
|
|
1250
|
+
ops = [{ insert: "\n" }];
|
|
1251
|
+
}
|
|
1252
|
+
return {
|
|
1253
|
+
type: "box",
|
|
1254
|
+
content: { ops }
|
|
1255
|
+
};
|
|
1256
|
+
},
|
|
1257
|
+
toMarkdown(_data, _context) {
|
|
1258
|
+
return null;
|
|
1259
|
+
},
|
|
1260
|
+
// ── Normalization ──────────────────────────────────────────
|
|
1261
|
+
normalize(data, registry) {
|
|
1262
|
+
const normalized = normalizeDelta(new Delta6(data.content.ops), registry);
|
|
1263
|
+
return {
|
|
1264
|
+
...data,
|
|
1265
|
+
content: { ops: normalized.ops }
|
|
1266
|
+
};
|
|
1267
|
+
},
|
|
1268
|
+
// ── Nested Deltas ──────────────────────────────────────────
|
|
1269
|
+
getNestedDeltas(data) {
|
|
1270
|
+
return [data.content.ops];
|
|
1271
|
+
},
|
|
1272
|
+
setNestedDeltas(data, deltas) {
|
|
1273
|
+
const first = deltas[0];
|
|
1274
|
+
if (!first) return data;
|
|
1275
|
+
return {
|
|
1276
|
+
...data,
|
|
1277
|
+
content: { ops: first }
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
function extractBoxOpAttributes(element) {
|
|
1282
|
+
const attrs = {};
|
|
1283
|
+
const dataFloat = element.getAttribute("data-float");
|
|
1284
|
+
if (dataFloat && isBoxFloat(dataFloat)) {
|
|
1285
|
+
attrs.float = dataFloat;
|
|
1286
|
+
}
|
|
1287
|
+
const dataOverflow = element.getAttribute("data-overflow");
|
|
1288
|
+
if (dataOverflow && isBoxOverflow(dataOverflow)) {
|
|
1289
|
+
attrs.overflow = dataOverflow;
|
|
1290
|
+
}
|
|
1291
|
+
const style = element.getAttribute("style") || "";
|
|
1292
|
+
const widthMatch = style.match(/(?:^|;\s*)width:\s*([^;]+)/);
|
|
1293
|
+
if (widthMatch?.[1]) {
|
|
1294
|
+
attrs.width = widthMatch[1].trim();
|
|
1295
|
+
}
|
|
1296
|
+
const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
|
|
1297
|
+
if (heightMatch?.[1]) {
|
|
1298
|
+
attrs.height = heightMatch[1].trim();
|
|
1299
|
+
}
|
|
1300
|
+
return attrs;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/schema/utils/color.ts
|
|
1304
|
+
var NAMED_COLORS = {
|
|
1305
|
+
// Basic colors
|
|
1306
|
+
black: "#000000",
|
|
1307
|
+
silver: "#c0c0c0",
|
|
1308
|
+
gray: "#808080",
|
|
1309
|
+
white: "#ffffff",
|
|
1310
|
+
maroon: "#800000",
|
|
1311
|
+
red: "#ff0000",
|
|
1312
|
+
purple: "#800080",
|
|
1313
|
+
fuchsia: "#ff00ff",
|
|
1314
|
+
green: "#008000",
|
|
1315
|
+
lime: "#00ff00",
|
|
1316
|
+
olive: "#808000",
|
|
1317
|
+
yellow: "#ffff00",
|
|
1318
|
+
navy: "#000080",
|
|
1319
|
+
blue: "#0000ff",
|
|
1320
|
+
teal: "#008080",
|
|
1321
|
+
aqua: "#00ffff",
|
|
1322
|
+
// Extended colors
|
|
1323
|
+
aliceblue: "#f0f8ff",
|
|
1324
|
+
antiquewhite: "#faebd7",
|
|
1325
|
+
aquamarine: "#7fffd4",
|
|
1326
|
+
azure: "#f0ffff",
|
|
1327
|
+
beige: "#f5f5dc",
|
|
1328
|
+
bisque: "#ffe4c4",
|
|
1329
|
+
blanchedalmond: "#ffebcd",
|
|
1330
|
+
blueviolet: "#8a2be2",
|
|
1331
|
+
brown: "#a52a2a",
|
|
1332
|
+
burlywood: "#deb887",
|
|
1333
|
+
cadetblue: "#5f9ea0",
|
|
1334
|
+
chartreuse: "#7fff00",
|
|
1335
|
+
chocolate: "#d2691e",
|
|
1336
|
+
coral: "#ff7f50",
|
|
1337
|
+
cornflowerblue: "#6495ed",
|
|
1338
|
+
cornsilk: "#fff8dc",
|
|
1339
|
+
crimson: "#dc143c",
|
|
1340
|
+
cyan: "#00ffff",
|
|
1341
|
+
darkblue: "#00008b",
|
|
1342
|
+
darkcyan: "#008b8b",
|
|
1343
|
+
darkgoldenrod: "#b8860b",
|
|
1344
|
+
darkgray: "#a9a9a9",
|
|
1345
|
+
darkgreen: "#006400",
|
|
1346
|
+
darkgrey: "#a9a9a9",
|
|
1347
|
+
darkkhaki: "#bdb76b",
|
|
1348
|
+
darkmagenta: "#8b008b",
|
|
1349
|
+
darkolivegreen: "#556b2f",
|
|
1350
|
+
darkorange: "#ff8c00",
|
|
1351
|
+
darkorchid: "#9932cc",
|
|
1352
|
+
darkred: "#8b0000",
|
|
1353
|
+
darksalmon: "#e9967a",
|
|
1354
|
+
darkseagreen: "#8fbc8f",
|
|
1355
|
+
darkslateblue: "#483d8b",
|
|
1356
|
+
darkslategray: "#2f4f4f",
|
|
1357
|
+
darkslategrey: "#2f4f4f",
|
|
1358
|
+
darkturquoise: "#00ced1",
|
|
1359
|
+
darkviolet: "#9400d3",
|
|
1360
|
+
deeppink: "#ff1493",
|
|
1361
|
+
deepskyblue: "#00bfff",
|
|
1362
|
+
dimgray: "#696969",
|
|
1363
|
+
dimgrey: "#696969",
|
|
1364
|
+
dodgerblue: "#1e90ff",
|
|
1365
|
+
firebrick: "#b22222",
|
|
1366
|
+
floralwhite: "#fffaf0",
|
|
1367
|
+
forestgreen: "#228b22",
|
|
1368
|
+
gainsboro: "#dcdcdc",
|
|
1369
|
+
ghostwhite: "#f8f8ff",
|
|
1370
|
+
gold: "#ffd700",
|
|
1371
|
+
goldenrod: "#daa520",
|
|
1372
|
+
greenyellow: "#adff2f",
|
|
1373
|
+
grey: "#808080",
|
|
1374
|
+
honeydew: "#f0fff0",
|
|
1375
|
+
hotpink: "#ff69b4",
|
|
1376
|
+
indianred: "#cd5c5c",
|
|
1377
|
+
indigo: "#4b0082",
|
|
1378
|
+
ivory: "#fffff0",
|
|
1379
|
+
khaki: "#f0e68c",
|
|
1380
|
+
lavender: "#e6e6fa",
|
|
1381
|
+
lavenderblush: "#fff0f5",
|
|
1382
|
+
lawngreen: "#7cfc00",
|
|
1383
|
+
lemonchiffon: "#fffacd",
|
|
1384
|
+
lightblue: "#add8e6",
|
|
1385
|
+
lightcoral: "#f08080",
|
|
1386
|
+
lightcyan: "#e0ffff",
|
|
1387
|
+
lightgoldenrodyellow: "#fafad2",
|
|
1388
|
+
lightgray: "#d3d3d3",
|
|
1389
|
+
lightgreen: "#90ee90",
|
|
1390
|
+
lightgrey: "#d3d3d3",
|
|
1391
|
+
lightpink: "#ffb6c1",
|
|
1392
|
+
lightsalmon: "#ffa07a",
|
|
1393
|
+
lightseagreen: "#20b2aa",
|
|
1394
|
+
lightskyblue: "#87cefa",
|
|
1395
|
+
lightslategray: "#778899",
|
|
1396
|
+
lightslategrey: "#778899",
|
|
1397
|
+
lightsteelblue: "#b0c4de",
|
|
1398
|
+
lightyellow: "#ffffe0",
|
|
1399
|
+
limegreen: "#32cd32",
|
|
1400
|
+
linen: "#faf0e6",
|
|
1401
|
+
magenta: "#ff00ff",
|
|
1402
|
+
mediumaquamarine: "#66cdaa",
|
|
1403
|
+
mediumblue: "#0000cd",
|
|
1404
|
+
mediumorchid: "#ba55d3",
|
|
1405
|
+
mediumpurple: "#9370db",
|
|
1406
|
+
mediumseagreen: "#3cb371",
|
|
1407
|
+
mediumslateblue: "#7b68ee",
|
|
1408
|
+
mediumspringgreen: "#00fa9a",
|
|
1409
|
+
mediumturquoise: "#48d1cc",
|
|
1410
|
+
mediumvioletred: "#c71585",
|
|
1411
|
+
midnightblue: "#191970",
|
|
1412
|
+
mintcream: "#f5fffa",
|
|
1413
|
+
mistyrose: "#ffe4e1",
|
|
1414
|
+
moccasin: "#ffe4b5",
|
|
1415
|
+
navajowhite: "#ffdead",
|
|
1416
|
+
oldlace: "#fdf5e6",
|
|
1417
|
+
olivedrab: "#6b8e23",
|
|
1418
|
+
orange: "#ffa500",
|
|
1419
|
+
orangered: "#ff4500",
|
|
1420
|
+
orchid: "#da70d6",
|
|
1421
|
+
palegoldenrod: "#eee8aa",
|
|
1422
|
+
palegreen: "#98fb98",
|
|
1423
|
+
paleturquoise: "#afeeee",
|
|
1424
|
+
palevioletred: "#db7093",
|
|
1425
|
+
papayawhip: "#ffefd5",
|
|
1426
|
+
peachpuff: "#ffdab9",
|
|
1427
|
+
peru: "#cd853f",
|
|
1428
|
+
pink: "#ffc0cb",
|
|
1429
|
+
plum: "#dda0dd",
|
|
1430
|
+
powderblue: "#b0e0e6",
|
|
1431
|
+
rosybrown: "#bc8f8f",
|
|
1432
|
+
royalblue: "#4169e1",
|
|
1433
|
+
saddlebrown: "#8b4513",
|
|
1434
|
+
salmon: "#fa8072",
|
|
1435
|
+
sandybrown: "#f4a460",
|
|
1436
|
+
seagreen: "#2e8b57",
|
|
1437
|
+
seashell: "#fff5ee",
|
|
1438
|
+
sienna: "#a0522d",
|
|
1439
|
+
skyblue: "#87ceeb",
|
|
1440
|
+
slateblue: "#6a5acd",
|
|
1441
|
+
slategray: "#708090",
|
|
1442
|
+
slategrey: "#708090",
|
|
1443
|
+
snow: "#fffafa",
|
|
1444
|
+
springgreen: "#00ff7f",
|
|
1445
|
+
steelblue: "#4682b4",
|
|
1446
|
+
tan: "#d2b48c",
|
|
1447
|
+
thistle: "#d8bfd8",
|
|
1448
|
+
tomato: "#ff6347",
|
|
1449
|
+
turquoise: "#40e0d0",
|
|
1450
|
+
violet: "#ee82ee",
|
|
1451
|
+
wheat: "#f5deb3",
|
|
1452
|
+
whitesmoke: "#f5f5f5",
|
|
1453
|
+
yellowgreen: "#9acd32",
|
|
1454
|
+
// CSS4 colors
|
|
1455
|
+
rebeccapurple: "#663399"
|
|
1456
|
+
};
|
|
1457
|
+
function toHexColor(value) {
|
|
1458
|
+
const trimmed = value.trim().toLowerCase();
|
|
1459
|
+
if (trimmed.startsWith("#")) {
|
|
1460
|
+
return normalizeHex(trimmed);
|
|
1461
|
+
}
|
|
1462
|
+
if (NAMED_COLORS[trimmed]) {
|
|
1463
|
+
return NAMED_COLORS[trimmed];
|
|
1464
|
+
}
|
|
1465
|
+
if (trimmed.startsWith("rgb")) {
|
|
1466
|
+
return parseRgb(trimmed);
|
|
1467
|
+
}
|
|
1468
|
+
return value;
|
|
1469
|
+
}
|
|
1470
|
+
function normalizeHex(hex) {
|
|
1471
|
+
let color = hex.slice(1);
|
|
1472
|
+
if (color.length === 3) {
|
|
1473
|
+
color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
|
|
1474
|
+
}
|
|
1475
|
+
if (color.length === 4) {
|
|
1476
|
+
color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
|
|
1477
|
+
}
|
|
1478
|
+
if (color.length === 8) {
|
|
1479
|
+
color = color.slice(0, 6);
|
|
1480
|
+
}
|
|
1481
|
+
if (!/^[0-9a-f]{6}$/.test(color)) {
|
|
1482
|
+
return "#" + color;
|
|
1483
|
+
}
|
|
1484
|
+
return "#" + color;
|
|
1485
|
+
}
|
|
1486
|
+
function parseRgb(value) {
|
|
1487
|
+
const match = value.match(
|
|
1488
|
+
/rgba?\s*\(\s*(-?\d+)\s*[,\s]\s*(-?\d+)\s*[,\s]\s*(-?\d+)(?:\s*[,/]\s*[\d.]+)?\s*\)/
|
|
1489
|
+
);
|
|
1490
|
+
if (!match) {
|
|
1491
|
+
return value;
|
|
1492
|
+
}
|
|
1493
|
+
const [, rStr, gStr, bStr] = match;
|
|
1494
|
+
if (!rStr || !gStr || !bStr) return value;
|
|
1495
|
+
const r = Math.max(0, Math.min(255, parseInt(rStr, 10)));
|
|
1496
|
+
const g = Math.max(0, Math.min(255, parseInt(gStr, 10)));
|
|
1497
|
+
const b = Math.max(0, Math.min(255, parseInt(bStr, 10)));
|
|
1498
|
+
return "#" + toHex(r) + toHex(g) + toHex(b);
|
|
1499
|
+
}
|
|
1500
|
+
function toHex(n) {
|
|
1501
|
+
const hex = n.toString(16);
|
|
1502
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
1503
|
+
}
|
|
1504
|
+
function isValidHexColor(value) {
|
|
1505
|
+
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value);
|
|
1506
|
+
}
|
|
1507
|
+
function isValidColor(value) {
|
|
1508
|
+
const trimmed = value.trim().toLowerCase();
|
|
1509
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(trimmed)) {
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
if (NAMED_COLORS[trimmed]) {
|
|
1513
|
+
return true;
|
|
1514
|
+
}
|
|
1515
|
+
if (/^rgba?\s*\(\s*\d+\s*[,\s]\s*\d+\s*[,\s]\s*\d+(?:\s*[,/]\s*[\d.]+)?\s*\)$/.test(trimmed)) {
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
return false;
|
|
1519
|
+
}
|
|
1520
|
+
function getNamedColors() {
|
|
1521
|
+
return Object.keys(NAMED_COLORS);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/schema/formats/inline/background.ts
|
|
1525
|
+
var backgroundFormat = {
|
|
1526
|
+
name: "background",
|
|
1527
|
+
scope: "inline",
|
|
1528
|
+
normalize(value) {
|
|
1529
|
+
return toHexColor(value);
|
|
1530
|
+
},
|
|
1531
|
+
validate(value) {
|
|
1532
|
+
return typeof value === "string" && isValidColor(value);
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
// src/schema/formats/inline/bold.ts
|
|
1537
|
+
var boldFormat = {
|
|
1538
|
+
name: "bold",
|
|
1539
|
+
scope: "inline",
|
|
1540
|
+
validate(value) {
|
|
1541
|
+
return value === true;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
// src/schema/formats/inline/code.ts
|
|
1546
|
+
var codeFormat = {
|
|
1547
|
+
name: "code",
|
|
1548
|
+
scope: "inline",
|
|
1549
|
+
validate(value) {
|
|
1550
|
+
return value === true;
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
// src/schema/formats/inline/color.ts
|
|
1555
|
+
var colorFormat = {
|
|
1556
|
+
name: "color",
|
|
1557
|
+
scope: "inline",
|
|
1558
|
+
normalize(value) {
|
|
1559
|
+
return toHexColor(value);
|
|
1560
|
+
},
|
|
1561
|
+
validate(value) {
|
|
1562
|
+
return typeof value === "string" && isValidColor(value);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
// src/schema/formats/inline/italic.ts
|
|
1567
|
+
var italicFormat = {
|
|
1568
|
+
name: "italic",
|
|
1569
|
+
scope: "inline",
|
|
1570
|
+
validate(value) {
|
|
1571
|
+
return value === true;
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// src/schema/formats/inline/kbd.ts
|
|
1576
|
+
var kbdFormat = {
|
|
1577
|
+
name: "kbd",
|
|
1578
|
+
scope: "inline",
|
|
1579
|
+
validate(value) {
|
|
1580
|
+
return value === true;
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
// src/schema/formats/inline/link.ts
|
|
1585
|
+
var linkFormat = {
|
|
1586
|
+
name: "link",
|
|
1587
|
+
scope: "inline",
|
|
1588
|
+
normalize(value) {
|
|
1589
|
+
return value.trim();
|
|
1590
|
+
},
|
|
1591
|
+
validate(value) {
|
|
1592
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
const trimmed = value.trim();
|
|
1596
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1599
|
+
if (trimmed.startsWith("//")) {
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
if (trimmed.startsWith("mailto:") || trimmed.startsWith("tel:")) {
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
try {
|
|
1606
|
+
const url = new URL(trimmed);
|
|
1607
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1608
|
+
} catch {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
// src/schema/formats/inline/mark.ts
|
|
1615
|
+
var markFormat = {
|
|
1616
|
+
name: "mark",
|
|
1617
|
+
scope: "inline",
|
|
1618
|
+
validate(value) {
|
|
1619
|
+
return value === true;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
// src/schema/formats/inline/strike.ts
|
|
1624
|
+
var strikeFormat = {
|
|
1625
|
+
name: "strike",
|
|
1626
|
+
scope: "inline",
|
|
1627
|
+
validate(value) {
|
|
1628
|
+
return value === true;
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
// src/schema/formats/inline/subscript.ts
|
|
1633
|
+
var subscriptFormat = {
|
|
1634
|
+
name: "subscript",
|
|
1635
|
+
scope: "inline",
|
|
1636
|
+
validate(value) {
|
|
1637
|
+
return value === true;
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
// src/schema/formats/inline/superscript.ts
|
|
1642
|
+
var superscriptFormat = {
|
|
1643
|
+
name: "superscript",
|
|
1644
|
+
scope: "inline",
|
|
1645
|
+
validate(value) {
|
|
1646
|
+
return value === true;
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
// src/schema/formats/inline/underline.ts
|
|
1651
|
+
var underlineFormat = {
|
|
1652
|
+
name: "underline",
|
|
1653
|
+
scope: "inline",
|
|
1654
|
+
validate(value) {
|
|
1655
|
+
return value === true;
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// src/schema/formats/block/align.ts
|
|
1660
|
+
var VALID_ALIGN_TYPES = ["left", "center", "right", "justify"];
|
|
1661
|
+
var alignFormat = {
|
|
1662
|
+
name: "align",
|
|
1663
|
+
scope: "block",
|
|
1664
|
+
normalize(value) {
|
|
1665
|
+
return value.toLowerCase();
|
|
1666
|
+
},
|
|
1667
|
+
validate(value) {
|
|
1668
|
+
return VALID_ALIGN_TYPES.includes(value);
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
// src/schema/formats/block/blockquote.ts
|
|
1673
|
+
var blockquoteFormat = {
|
|
1674
|
+
name: "blockquote",
|
|
1675
|
+
scope: "block",
|
|
1676
|
+
validate(value) {
|
|
1677
|
+
return value === true;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// src/schema/formats/block/code-block.ts
|
|
1682
|
+
var codeBlockFormat = {
|
|
1683
|
+
name: "code-block",
|
|
1684
|
+
scope: "block",
|
|
1685
|
+
normalize(value) {
|
|
1686
|
+
if (typeof value === "string") {
|
|
1687
|
+
return value.toLowerCase().trim();
|
|
1688
|
+
}
|
|
1689
|
+
return value;
|
|
1690
|
+
},
|
|
1691
|
+
validate(value) {
|
|
1692
|
+
if (value === true) {
|
|
1693
|
+
return true;
|
|
1694
|
+
}
|
|
1695
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1696
|
+
return true;
|
|
1697
|
+
}
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
// src/schema/formats/block/header.ts
|
|
1703
|
+
var headerFormat = {
|
|
1704
|
+
name: "header",
|
|
1705
|
+
scope: "block",
|
|
1706
|
+
normalize(value) {
|
|
1707
|
+
return Math.max(1, Math.min(6, Math.floor(value)));
|
|
1708
|
+
},
|
|
1709
|
+
validate(value) {
|
|
1710
|
+
return Number.isInteger(value) && value >= 1 && value <= 6;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
|
|
1714
|
+
// src/schema/formats/block/header-id.ts
|
|
1715
|
+
var headerIdFormat = {
|
|
1716
|
+
name: "header-id",
|
|
1717
|
+
scope: "block",
|
|
1718
|
+
normalize(value) {
|
|
1719
|
+
return String(value).trim().toLowerCase();
|
|
1720
|
+
},
|
|
1721
|
+
validate(value) {
|
|
1722
|
+
if (typeof value !== "string") return false;
|
|
1723
|
+
const trimmed = value.trim();
|
|
1724
|
+
return trimmed.length > 0 && !/\s/.test(trimmed);
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/schema/formats/block/indent.ts
|
|
1729
|
+
var MAX_INDENT = 8;
|
|
1730
|
+
var indentFormat = {
|
|
1731
|
+
name: "indent",
|
|
1732
|
+
scope: "block",
|
|
1733
|
+
normalize(value) {
|
|
1734
|
+
return Math.max(0, Math.min(MAX_INDENT, Math.floor(value)));
|
|
1735
|
+
},
|
|
1736
|
+
validate(value) {
|
|
1737
|
+
return Number.isInteger(value) && value >= 0 && value <= MAX_INDENT;
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
// src/schema/formats/block/list.ts
|
|
1742
|
+
var VALID_LIST_TYPES = ["ordered", "bullet", "checked", "unchecked"];
|
|
1743
|
+
var listFormat = {
|
|
1744
|
+
name: "list",
|
|
1745
|
+
scope: "block",
|
|
1746
|
+
normalize(value) {
|
|
1747
|
+
return value.toLowerCase();
|
|
1748
|
+
},
|
|
1749
|
+
validate(value) {
|
|
1750
|
+
return VALID_LIST_TYPES.includes(value);
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
// src/schema/formats/block/table-row.ts
|
|
1755
|
+
var tableRowFormat = {
|
|
1756
|
+
name: "table-row",
|
|
1757
|
+
scope: "block",
|
|
1758
|
+
validate(value) {
|
|
1759
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
// src/schema/formats/block/table-col.ts
|
|
1764
|
+
var tableColFormat = {
|
|
1765
|
+
name: "table-col",
|
|
1766
|
+
scope: "block",
|
|
1767
|
+
validate(value) {
|
|
1768
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
// src/schema/formats/block/table-header.ts
|
|
1773
|
+
var tableHeaderFormat = {
|
|
1774
|
+
name: "table-header",
|
|
1775
|
+
scope: "block",
|
|
1776
|
+
validate(value) {
|
|
1777
|
+
return value === true;
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
// src/schema/formats/block/table-col-align.ts
|
|
1782
|
+
var VALID_ALIGNS = ["left", "center", "right"];
|
|
1783
|
+
var tableColAlignFormat = {
|
|
1784
|
+
name: "table-col-align",
|
|
1785
|
+
scope: "block",
|
|
1786
|
+
normalize(value) {
|
|
1787
|
+
return value.toLowerCase();
|
|
1788
|
+
},
|
|
1789
|
+
validate(value) {
|
|
1790
|
+
return VALID_ALIGNS.includes(value);
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
// src/schema/formats/embed/block.ts
|
|
1795
|
+
var blockFormat = {
|
|
1796
|
+
name: "block",
|
|
1797
|
+
scope: "embed",
|
|
1798
|
+
validate(value) {
|
|
1799
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string" && value.type.length > 0;
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// src/conversion/html/config.ts
|
|
1804
|
+
var INLINE_FORMAT_TAGS = {
|
|
1805
|
+
link: "a",
|
|
1806
|
+
bold: "strong",
|
|
1807
|
+
italic: "em",
|
|
1808
|
+
underline: "u",
|
|
1809
|
+
strike: "s",
|
|
1810
|
+
subscript: "sub",
|
|
1811
|
+
superscript: "sup",
|
|
1812
|
+
code: "code",
|
|
1813
|
+
mark: "mark",
|
|
1814
|
+
kbd: "kbd"
|
|
1815
|
+
};
|
|
1816
|
+
var INLINE_FORMAT_ORDER = [
|
|
1817
|
+
"link",
|
|
1818
|
+
"bold",
|
|
1819
|
+
"italic",
|
|
1820
|
+
"underline",
|
|
1821
|
+
"strike",
|
|
1822
|
+
"subscript",
|
|
1823
|
+
"superscript",
|
|
1824
|
+
"code",
|
|
1825
|
+
"mark",
|
|
1826
|
+
"kbd"
|
|
1827
|
+
];
|
|
1828
|
+
var INLINE_STYLE_FORMATS = {
|
|
1829
|
+
color: "color",
|
|
1830
|
+
background: "background-color"
|
|
1831
|
+
};
|
|
1832
|
+
var BLOCK_FORMAT_TAGS = {
|
|
1833
|
+
header: (value) => `h${String(value)}`,
|
|
1834
|
+
blockquote: "blockquote",
|
|
1835
|
+
"code-block": "pre",
|
|
1836
|
+
list: "li"
|
|
1837
|
+
// Wrapped in ul/ol based on list type
|
|
1838
|
+
};
|
|
1839
|
+
var LIST_WRAPPER_TAGS = {
|
|
1840
|
+
ordered: "ol",
|
|
1841
|
+
bullet: "ul",
|
|
1842
|
+
checked: "ul",
|
|
1843
|
+
unchecked: "ul"
|
|
1844
|
+
};
|
|
1845
|
+
var EMBED_RENDERERS = {
|
|
1846
|
+
image: (value, attrs) => {
|
|
1847
|
+
const src = typeof value === "string" ? value : "";
|
|
1848
|
+
const altVal = attrs?.alt;
|
|
1849
|
+
const widthVal = attrs?.width;
|
|
1850
|
+
const heightVal = attrs?.height;
|
|
1851
|
+
const floatVal = attrs?.float;
|
|
1852
|
+
const alt = altVal != null && (typeof altVal === "string" || typeof altVal === "number") ? ` alt="${escapeHtml(String(altVal))}"` : "";
|
|
1853
|
+
const width = widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number") ? ` width="${String(widthVal)}"` : "";
|
|
1854
|
+
const height = heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number") ? ` height="${String(heightVal)}"` : "";
|
|
1855
|
+
const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
|
|
1856
|
+
return `<img src="${escapeHtml(src)}"${alt}${width}${height}${float}>`;
|
|
1857
|
+
},
|
|
1858
|
+
video: (value, attrs) => {
|
|
1859
|
+
const src = typeof value === "string" ? value : "";
|
|
1860
|
+
const floatVal = attrs?.float;
|
|
1861
|
+
const widthVal = attrs?.width;
|
|
1862
|
+
const heightVal = attrs?.height;
|
|
1863
|
+
const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
|
|
1864
|
+
const styles = [];
|
|
1865
|
+
if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
|
|
1866
|
+
const w = String(widthVal);
|
|
1867
|
+
if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
|
|
1868
|
+
}
|
|
1869
|
+
if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
|
|
1870
|
+
const h = String(heightVal);
|
|
1871
|
+
if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
|
|
1872
|
+
}
|
|
1873
|
+
const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
|
|
1874
|
+
const embedSrc = toVideoEmbedUrl(src);
|
|
1875
|
+
if (embedSrc) {
|
|
1876
|
+
return `<iframe src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${float}${style}></iframe>`;
|
|
1877
|
+
}
|
|
1878
|
+
return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
|
|
1879
|
+
},
|
|
1880
|
+
formula: (value) => {
|
|
1881
|
+
const latex = typeof value === "string" ? value : "";
|
|
1882
|
+
return `<span class="formula" data-formula="${escapeHtml(latex)}">${escapeHtml(latex)}</span>`;
|
|
1883
|
+
},
|
|
1884
|
+
diagram: (value) => {
|
|
1885
|
+
const source = typeof value === "string" ? value : "";
|
|
1886
|
+
return `<span class="diagram" data-diagram="${escapeHtml(source)}">${escapeHtml(source)}</span>`;
|
|
1887
|
+
},
|
|
1888
|
+
drawio: (value, attrs) => {
|
|
1889
|
+
const src = typeof value === "string" ? value : "";
|
|
1890
|
+
const altVal = attrs?.alt;
|
|
1891
|
+
const alt = altVal != null && (typeof altVal === "string" || typeof altVal === "number") ? ` data-alt="${escapeHtml(String(altVal))}"` : "";
|
|
1892
|
+
return `<span class="drawio" data-drawio-src="${escapeHtml(src)}"${alt}></span>`;
|
|
1893
|
+
},
|
|
1894
|
+
"footnote-ref": (value) => {
|
|
1895
|
+
const id = typeof value === "string" ? value : String(value);
|
|
1896
|
+
return `<sup class="footnote-ref"><a href="#fn-${escapeHtml(id)}" id="fnref-${escapeHtml(id)}">[${escapeHtml(id)}]</a></sup>`;
|
|
1897
|
+
},
|
|
1898
|
+
divider: () => "<hr>"
|
|
1899
|
+
};
|
|
1900
|
+
var TAG_TO_INLINE_FORMAT = {
|
|
1901
|
+
strong: { format: "bold", value: true },
|
|
1902
|
+
b: { format: "bold", value: true },
|
|
1903
|
+
em: { format: "italic", value: true },
|
|
1904
|
+
i: { format: "italic", value: true },
|
|
1905
|
+
u: { format: "underline", value: true },
|
|
1906
|
+
ins: { format: "underline", value: true },
|
|
1907
|
+
s: { format: "strike", value: true },
|
|
1908
|
+
strike: { format: "strike", value: true },
|
|
1909
|
+
del: { format: "strike", value: true },
|
|
1910
|
+
sub: { format: "subscript", value: true },
|
|
1911
|
+
sup: { format: "superscript", value: true },
|
|
1912
|
+
code: { format: "code", value: true },
|
|
1913
|
+
mark: { format: "mark", value: true },
|
|
1914
|
+
kbd: { format: "kbd", value: true }
|
|
1915
|
+
};
|
|
1916
|
+
var TAG_TO_BLOCK_FORMAT = {
|
|
1917
|
+
h1: { format: "header", value: 1 },
|
|
1918
|
+
h2: { format: "header", value: 2 },
|
|
1919
|
+
h3: { format: "header", value: 3 },
|
|
1920
|
+
h4: { format: "header", value: 4 },
|
|
1921
|
+
h5: { format: "header", value: 5 },
|
|
1922
|
+
h6: { format: "header", value: 6 },
|
|
1923
|
+
blockquote: { format: "blockquote", value: true },
|
|
1924
|
+
pre: { format: "code-block", value: true }
|
|
1925
|
+
};
|
|
1926
|
+
var CSS_ALIGN_TO_FORMAT = {
|
|
1927
|
+
left: "left",
|
|
1928
|
+
center: "center",
|
|
1929
|
+
right: "right",
|
|
1930
|
+
justify: "justify"
|
|
1931
|
+
};
|
|
1932
|
+
function escapeHtml(text) {
|
|
1933
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1934
|
+
}
|
|
1935
|
+
function unescapeHtml(text) {
|
|
1936
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
1937
|
+
}
|
|
1938
|
+
function toVideoEmbedUrl(url) {
|
|
1939
|
+
if (url.includes("youtube.com/embed") || url.includes("player.vimeo.com") || url.includes("dailymotion.com/embed") || url.includes("video_ext.php") || url.includes("rutube.ru/play/embed")) {
|
|
1940
|
+
return url;
|
|
1941
|
+
}
|
|
1942
|
+
const ytMatch = url.match(/youtube\.com\/watch\?v=([\w-]+)/);
|
|
1943
|
+
if (ytMatch) {
|
|
1944
|
+
return `https://www.youtube.com/embed/${ytMatch[1]}`;
|
|
1945
|
+
}
|
|
1946
|
+
const ytShortMatch = url.match(/youtu\.be\/([\w-]+)/);
|
|
1947
|
+
if (ytShortMatch) {
|
|
1948
|
+
return `https://www.youtube.com/embed/${ytShortMatch[1]}`;
|
|
1949
|
+
}
|
|
1950
|
+
const rtMatch = url.match(/rutube\.ru\/video\/([\w]+)/);
|
|
1951
|
+
if (rtMatch) {
|
|
1952
|
+
return `https://rutube.ru/play/embed/${rtMatch[1]}`;
|
|
1953
|
+
}
|
|
1954
|
+
return null;
|
|
1955
|
+
}
|
|
1956
|
+
function fromVideoEmbedUrl(embedUrl) {
|
|
1957
|
+
const ytMatch = embedUrl.match(/youtube\.com\/embed\/([\w-]+)/);
|
|
1958
|
+
if (ytMatch) {
|
|
1959
|
+
return `https://www.youtube.com/watch?v=${ytMatch[1]}`;
|
|
1960
|
+
}
|
|
1961
|
+
const rtMatch = embedUrl.match(/rutube\.ru\/play\/embed\/([\w]+)/);
|
|
1962
|
+
if (rtMatch) {
|
|
1963
|
+
return `https://rutube.ru/video/${rtMatch[1]}/`;
|
|
1964
|
+
}
|
|
1965
|
+
return embedUrl;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// src/schema/formats/embed/divider.ts
|
|
1969
|
+
var dividerFormat = {
|
|
1970
|
+
name: "divider",
|
|
1971
|
+
scope: "embed",
|
|
1972
|
+
normalize(value) {
|
|
1973
|
+
return !!value;
|
|
1974
|
+
},
|
|
1975
|
+
validate(value) {
|
|
1976
|
+
return value === true;
|
|
1977
|
+
},
|
|
1978
|
+
render() {
|
|
1979
|
+
return "<hr>";
|
|
1980
|
+
},
|
|
1981
|
+
match(element) {
|
|
1982
|
+
if (element.tagName.toLowerCase() !== "hr") return null;
|
|
1983
|
+
return { value: true };
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
|
|
1987
|
+
// src/schema/formats/embed/footnote-ref.ts
|
|
1988
|
+
var footnoteRefFormat = {
|
|
1989
|
+
name: "footnote-ref",
|
|
1990
|
+
scope: "embed",
|
|
1991
|
+
validate(value) {
|
|
1992
|
+
if (typeof value !== "string") {
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
return value.trim().length > 0;
|
|
1996
|
+
},
|
|
1997
|
+
render(value) {
|
|
1998
|
+
const id = typeof value === "string" ? value : String(value);
|
|
1999
|
+
return `<sup class="footnote-ref"><a href="#fn-${escapeHtml(id)}" id="fnref-${escapeHtml(id)}">[${escapeHtml(id)}]</a></sup>`;
|
|
2000
|
+
},
|
|
2001
|
+
match(element) {
|
|
2002
|
+
if (element.tagName.toLowerCase() !== "sup") return null;
|
|
2003
|
+
const className = element.getAttribute("class") || "";
|
|
2004
|
+
if (!className.includes("footnote-ref")) return null;
|
|
2005
|
+
const children = element.childNodes;
|
|
2006
|
+
for (let i = 0; i < children.length; i++) {
|
|
2007
|
+
const child = children[i];
|
|
2008
|
+
if (child && isElement(child)) {
|
|
2009
|
+
const href = child.getAttribute("href") || "";
|
|
2010
|
+
const hrefMatch = href.match(/#fn-(.+)/);
|
|
2011
|
+
if (hrefMatch?.[1]) {
|
|
2012
|
+
return { value: hrefMatch[1] };
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
const id = element.getAttribute("id") || "";
|
|
2017
|
+
const refMatch = id.match(/^fnref-(.+)/);
|
|
2018
|
+
if (refMatch?.[1]) {
|
|
2019
|
+
return { value: refMatch[1] };
|
|
2020
|
+
}
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
|
|
2025
|
+
// src/schema/formats/embed/formula.ts
|
|
2026
|
+
function isValidLatex(value) {
|
|
2027
|
+
if (!value || value.trim().length === 0) {
|
|
2028
|
+
return false;
|
|
2029
|
+
}
|
|
2030
|
+
let braceCount = 0;
|
|
2031
|
+
for (const char of value) {
|
|
2032
|
+
if (char === "{") braceCount++;
|
|
2033
|
+
if (char === "}") braceCount--;
|
|
2034
|
+
if (braceCount < 0) return false;
|
|
2035
|
+
}
|
|
2036
|
+
if (braceCount !== 0) return false;
|
|
2037
|
+
let bracketCount = 0;
|
|
2038
|
+
for (const char of value) {
|
|
2039
|
+
if (char === "[") bracketCount++;
|
|
2040
|
+
if (char === "]") bracketCount--;
|
|
2041
|
+
if (bracketCount < 0) return false;
|
|
2042
|
+
}
|
|
2043
|
+
if (bracketCount !== 0) return false;
|
|
2044
|
+
const invalidCommand = /\\(?![a-zA-Z]|\\|{|}|\[|\]|\s|,|;|!|\^|_)/;
|
|
2045
|
+
if (invalidCommand.test(value)) {
|
|
2046
|
+
return false;
|
|
2047
|
+
}
|
|
2048
|
+
return true;
|
|
2049
|
+
}
|
|
2050
|
+
var formulaFormat = {
|
|
2051
|
+
name: "formula",
|
|
2052
|
+
scope: "embed",
|
|
2053
|
+
normalize(value) {
|
|
2054
|
+
return value.trim();
|
|
2055
|
+
},
|
|
2056
|
+
validate(value) {
|
|
2057
|
+
if (typeof value !== "string") {
|
|
2058
|
+
return false;
|
|
2059
|
+
}
|
|
2060
|
+
return isValidLatex(value);
|
|
2061
|
+
},
|
|
2062
|
+
render(value) {
|
|
2063
|
+
const latex = typeof value === "string" ? value : "";
|
|
2064
|
+
return `<span class="formula" data-formula="${escapeHtml(latex)}">${escapeHtml(latex)}</span>`;
|
|
2065
|
+
},
|
|
2066
|
+
match(element) {
|
|
2067
|
+
if (element.tagName.toLowerCase() !== "span") return null;
|
|
2068
|
+
const className = element.getAttribute("class") || "";
|
|
2069
|
+
if (!className.includes("formula")) return null;
|
|
2070
|
+
const formula = element.getAttribute("data-formula");
|
|
2071
|
+
if (!formula) return null;
|
|
2072
|
+
return { value: formula };
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
// src/schema/formats/embed/image.ts
|
|
2077
|
+
var imageFormat = {
|
|
2078
|
+
name: "image",
|
|
2079
|
+
scope: "embed",
|
|
2080
|
+
normalize(value) {
|
|
2081
|
+
return value.trim();
|
|
2082
|
+
},
|
|
2083
|
+
validate(value) {
|
|
2084
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2085
|
+
return false;
|
|
2086
|
+
}
|
|
2087
|
+
const trimmed = value.trim();
|
|
2088
|
+
if (trimmed.startsWith("data:image/")) {
|
|
2089
|
+
return true;
|
|
2090
|
+
}
|
|
2091
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
2092
|
+
return true;
|
|
2093
|
+
}
|
|
2094
|
+
if (trimmed.startsWith("//")) {
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
try {
|
|
2098
|
+
const url = new URL(trimmed);
|
|
2099
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2100
|
+
} catch {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
render(value, attributes) {
|
|
2105
|
+
const src = typeof value === "string" ? value : "";
|
|
2106
|
+
const altVal = attributes?.alt;
|
|
2107
|
+
const widthVal = attributes?.width;
|
|
2108
|
+
const heightVal = attributes?.height;
|
|
2109
|
+
const floatVal = attributes?.float;
|
|
2110
|
+
const alt = altVal != null && (typeof altVal === "string" || typeof altVal === "number") ? ` alt="${escapeHtml(String(altVal))}"` : "";
|
|
2111
|
+
const width = widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number") ? ` width="${String(widthVal)}"` : "";
|
|
2112
|
+
const height = heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number") ? ` height="${String(heightVal)}"` : "";
|
|
2113
|
+
const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
|
|
2114
|
+
return `<img src="${escapeHtml(src)}"${alt}${width}${height}${float}>`;
|
|
2115
|
+
},
|
|
2116
|
+
match(element) {
|
|
2117
|
+
if (element.tagName.toLowerCase() !== "img") return null;
|
|
2118
|
+
const src = element.getAttribute("src");
|
|
2119
|
+
if (!src) return null;
|
|
2120
|
+
const attrs = {};
|
|
2121
|
+
const alt = element.getAttribute("alt");
|
|
2122
|
+
const width = element.getAttribute("width");
|
|
2123
|
+
const height = element.getAttribute("height");
|
|
2124
|
+
const float = element.getAttribute("data-float");
|
|
2125
|
+
if (alt) attrs.alt = alt;
|
|
2126
|
+
if (width) attrs.width = parseInt(width, 10);
|
|
2127
|
+
if (height) attrs.height = parseInt(height, 10);
|
|
2128
|
+
if (float) attrs.float = float;
|
|
2129
|
+
if (Object.keys(attrs).length > 0) {
|
|
2130
|
+
return { value: src, attributes: attrs };
|
|
2131
|
+
}
|
|
2132
|
+
return { value: src };
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
|
|
2136
|
+
// src/schema/formats/embed/video.ts
|
|
2137
|
+
var videoFormat = {
|
|
2138
|
+
name: "video",
|
|
2139
|
+
scope: "embed",
|
|
2140
|
+
normalize(value) {
|
|
2141
|
+
return value.trim();
|
|
2142
|
+
},
|
|
2143
|
+
validate(value) {
|
|
2144
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
const trimmed = value.trim();
|
|
2148
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
2149
|
+
return true;
|
|
2150
|
+
}
|
|
2151
|
+
if (trimmed.startsWith("//")) {
|
|
2152
|
+
return true;
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
const url = new URL(trimmed);
|
|
2156
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2157
|
+
} catch {
|
|
2158
|
+
return false;
|
|
2159
|
+
}
|
|
2160
|
+
},
|
|
2161
|
+
render(value, attributes) {
|
|
2162
|
+
const src = typeof value === "string" ? value : "";
|
|
2163
|
+
const floatVal = attributes?.float;
|
|
2164
|
+
const widthVal = attributes?.width;
|
|
2165
|
+
const heightVal = attributes?.height;
|
|
2166
|
+
const float = floatVal != null && typeof floatVal === "string" && floatVal !== "none" ? ` data-float="${escapeHtml(floatVal)}"` : "";
|
|
2167
|
+
const styles = [];
|
|
2168
|
+
if (widthVal != null && (typeof widthVal === "string" || typeof widthVal === "number")) {
|
|
2169
|
+
const w = String(widthVal);
|
|
2170
|
+
if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
|
|
2171
|
+
}
|
|
2172
|
+
if (heightVal != null && (typeof heightVal === "string" || typeof heightVal === "number")) {
|
|
2173
|
+
const h = String(heightVal);
|
|
2174
|
+
if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
|
|
2175
|
+
}
|
|
2176
|
+
const style = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
|
|
2177
|
+
const embedSrc = toVideoEmbedUrl(src);
|
|
2178
|
+
if (embedSrc) {
|
|
2179
|
+
return `<iframe src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${float}${style}></iframe>`;
|
|
2180
|
+
}
|
|
2181
|
+
return `<video src="${escapeHtml(src)}" controls${float}${style}></video>`;
|
|
2182
|
+
},
|
|
2183
|
+
match(element) {
|
|
2184
|
+
const tagName = element.tagName.toLowerCase();
|
|
2185
|
+
if (tagName !== "video" && tagName !== "iframe") return null;
|
|
2186
|
+
const src = element.getAttribute("src");
|
|
2187
|
+
if (!src) return null;
|
|
2188
|
+
const attrs = {};
|
|
2189
|
+
const float = element.getAttribute("data-float");
|
|
2190
|
+
const styleAttr = element.getAttribute("style") || "";
|
|
2191
|
+
if (float) attrs.float = float;
|
|
2192
|
+
const widthMatch = styleAttr.match(/(?:^|;\s*)width:\s*([^;]+)/);
|
|
2193
|
+
if (widthMatch?.[1]) attrs.width = widthMatch[1].trim().replace(/px$/, "");
|
|
2194
|
+
const heightMatch = styleAttr.match(/(?:^|;\s*)height:\s*([^;]+)/);
|
|
2195
|
+
if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
|
|
2196
|
+
if (Object.keys(attrs).length > 0) {
|
|
2197
|
+
return { value: fromVideoEmbedUrl(src), attributes: attrs };
|
|
2198
|
+
}
|
|
2199
|
+
return { value: fromVideoEmbedUrl(src) };
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
// src/schema/defaults.ts
|
|
2204
|
+
var defaultInlineFormats = [
|
|
2205
|
+
boldFormat,
|
|
2206
|
+
italicFormat,
|
|
2207
|
+
underlineFormat,
|
|
2208
|
+
strikeFormat,
|
|
2209
|
+
subscriptFormat,
|
|
2210
|
+
superscriptFormat,
|
|
2211
|
+
codeFormat,
|
|
2212
|
+
linkFormat,
|
|
2213
|
+
colorFormat,
|
|
2214
|
+
backgroundFormat,
|
|
2215
|
+
markFormat,
|
|
2216
|
+
kbdFormat
|
|
2217
|
+
];
|
|
2218
|
+
var defaultBlockFormats = [
|
|
2219
|
+
headerFormat,
|
|
2220
|
+
headerIdFormat,
|
|
2221
|
+
blockquoteFormat,
|
|
2222
|
+
codeBlockFormat,
|
|
2223
|
+
listFormat,
|
|
2224
|
+
alignFormat,
|
|
2225
|
+
indentFormat,
|
|
2226
|
+
tableRowFormat,
|
|
2227
|
+
tableColFormat,
|
|
2228
|
+
tableHeaderFormat,
|
|
2229
|
+
tableColAlignFormat
|
|
2230
|
+
];
|
|
2231
|
+
var defaultEmbedFormats = [
|
|
2232
|
+
imageFormat,
|
|
2233
|
+
videoFormat,
|
|
2234
|
+
formulaFormat,
|
|
2235
|
+
dividerFormat,
|
|
2236
|
+
blockFormat,
|
|
2237
|
+
footnoteRefFormat
|
|
2238
|
+
];
|
|
2239
|
+
var defaultFormats = [
|
|
2240
|
+
...defaultInlineFormats,
|
|
2241
|
+
...defaultBlockFormats,
|
|
2242
|
+
...defaultEmbedFormats
|
|
2243
|
+
];
|
|
2244
|
+
function createDefaultRegistry() {
|
|
2245
|
+
return new Registry().register(defaultFormats);
|
|
2246
|
+
}
|
|
2247
|
+
function createDefaultBlockHandlers() {
|
|
2248
|
+
return new BlockHandlerRegistry().register(tableBlockHandler).register(footnotesBlockHandler).register(alertBlockHandler).register(columnsBlockHandler).register(boxBlockHandler);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// src/conversion/adapters/browser.ts
|
|
2252
|
+
var BrowserDOMAdapter = class {
|
|
2253
|
+
/**
|
|
2254
|
+
* Parse HTML string into a document fragment
|
|
2255
|
+
*
|
|
2256
|
+
* Uses a template element to parse arbitrary HTML safely.
|
|
2257
|
+
*/
|
|
2258
|
+
parseHTML(html) {
|
|
2259
|
+
if (!this.isAvailable()) {
|
|
2260
|
+
throw new Error("BrowserDOMAdapter is not available in this environment");
|
|
2261
|
+
}
|
|
2262
|
+
const template = document.createElement("template");
|
|
2263
|
+
template.innerHTML = html;
|
|
2264
|
+
return template.content;
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Serialize a node to HTML string
|
|
2268
|
+
*/
|
|
2269
|
+
serializeHTML(node) {
|
|
2270
|
+
if (!this.isAvailable()) {
|
|
2271
|
+
throw new Error("BrowserDOMAdapter is not available in this environment");
|
|
2272
|
+
}
|
|
2273
|
+
if (node.nodeType === 11) {
|
|
2274
|
+
const container = document.createElement("div");
|
|
2275
|
+
const clone = node.cloneNode(true);
|
|
2276
|
+
container.appendChild(clone);
|
|
2277
|
+
return container.innerHTML;
|
|
2278
|
+
}
|
|
2279
|
+
if (node.nodeType === 1) {
|
|
2280
|
+
return node.outerHTML;
|
|
2281
|
+
}
|
|
2282
|
+
if (node.nodeType === 3) {
|
|
2283
|
+
return String(node.textContent ?? "");
|
|
2284
|
+
}
|
|
2285
|
+
return "";
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Create a new document for building DOM structures
|
|
2289
|
+
*/
|
|
2290
|
+
createDocument() {
|
|
2291
|
+
if (!this.isAvailable()) {
|
|
2292
|
+
throw new Error("BrowserDOMAdapter is not available in this environment");
|
|
2293
|
+
}
|
|
2294
|
+
return document;
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Check if browser DOM APIs are available
|
|
2298
|
+
*/
|
|
2299
|
+
isAvailable() {
|
|
2300
|
+
return typeof document !== "undefined" && typeof document.createElement === "function";
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
var browserAdapter = new BrowserDOMAdapter();
|
|
2304
|
+
|
|
2305
|
+
// src/conversion/adapters/node.ts
|
|
2306
|
+
var jsdomModule = null;
|
|
2307
|
+
var jsdomLoadError = null;
|
|
2308
|
+
async function loadJsdom() {
|
|
2309
|
+
if (jsdomLoadError) {
|
|
2310
|
+
throw jsdomLoadError;
|
|
2311
|
+
}
|
|
2312
|
+
if (jsdomModule) {
|
|
2313
|
+
return jsdomModule;
|
|
2314
|
+
}
|
|
2315
|
+
try {
|
|
2316
|
+
const mod = await import("jsdom");
|
|
2317
|
+
jsdomModule = mod;
|
|
2318
|
+
return jsdomModule;
|
|
2319
|
+
} catch {
|
|
2320
|
+
jsdomLoadError = new Error("jsdom is not installed. Install it with: pnpm add jsdom");
|
|
2321
|
+
throw jsdomLoadError;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
function isJsdomAvailable() {
|
|
2325
|
+
if (typeof window !== "undefined") {
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
if (jsdomModule) return true;
|
|
2330
|
+
if (jsdomLoadError) return false;
|
|
2331
|
+
__require.resolve("jsdom");
|
|
2332
|
+
return true;
|
|
2333
|
+
} catch {
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
var NodeDOMAdapter = class {
|
|
2338
|
+
constructor() {
|
|
2339
|
+
this.jsdom = null;
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Parse HTML string into a document fragment
|
|
2343
|
+
*/
|
|
2344
|
+
parseHTML(html) {
|
|
2345
|
+
const dom = this.getOrCreateJsdom();
|
|
2346
|
+
const template = dom.window.document.createElement("template");
|
|
2347
|
+
template.innerHTML = html;
|
|
2348
|
+
return template.content;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Serialize a node to HTML string
|
|
2352
|
+
*/
|
|
2353
|
+
serializeHTML(node) {
|
|
2354
|
+
const dom = this.getOrCreateJsdom();
|
|
2355
|
+
if (node.nodeType === 11) {
|
|
2356
|
+
const container = dom.window.document.createElement("div");
|
|
2357
|
+
const clone = node.cloneNode(true);
|
|
2358
|
+
container.appendChild(clone);
|
|
2359
|
+
return container.innerHTML;
|
|
2360
|
+
}
|
|
2361
|
+
if (node.nodeType === 1) {
|
|
2362
|
+
return node.outerHTML;
|
|
2363
|
+
}
|
|
2364
|
+
if (node.nodeType === 3) {
|
|
2365
|
+
return String(node.textContent ?? "");
|
|
2366
|
+
}
|
|
2367
|
+
return "";
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Create a new document for building DOM structures
|
|
2371
|
+
*/
|
|
2372
|
+
createDocument() {
|
|
2373
|
+
const dom = this.getOrCreateJsdom();
|
|
2374
|
+
return dom.window.document;
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Check if jsdom is available
|
|
2378
|
+
*/
|
|
2379
|
+
isAvailable() {
|
|
2380
|
+
return isJsdomAvailable();
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Initialize the adapter with jsdom (async)
|
|
2384
|
+
*
|
|
2385
|
+
* Call this before using the adapter if you want to handle
|
|
2386
|
+
* loading errors gracefully.
|
|
2387
|
+
*/
|
|
2388
|
+
async initialize() {
|
|
2389
|
+
const mod = await loadJsdom();
|
|
2390
|
+
this.jsdom = new mod.JSDOM("<!DOCTYPE html><html><body></body></html>");
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Get or create jsdom instance (sync, throws if not available)
|
|
2394
|
+
*/
|
|
2395
|
+
getOrCreateJsdom() {
|
|
2396
|
+
if (this.jsdom) {
|
|
2397
|
+
return this.jsdom;
|
|
2398
|
+
}
|
|
2399
|
+
if (!isJsdomAvailable()) {
|
|
2400
|
+
throw new Error("jsdom is not available. Install it with: pnpm add jsdom");
|
|
2401
|
+
}
|
|
2402
|
+
const mod = __require("jsdom");
|
|
2403
|
+
this.jsdom = new mod.JSDOM("<!DOCTYPE html><html><body></body></html>");
|
|
2404
|
+
return this.jsdom;
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
var nodeAdapter = new NodeDOMAdapter();
|
|
2408
|
+
|
|
2409
|
+
// src/conversion/adapters/index.ts
|
|
2410
|
+
function getAdapter() {
|
|
2411
|
+
if (browserAdapter.isAvailable()) {
|
|
2412
|
+
return browserAdapter;
|
|
2413
|
+
}
|
|
2414
|
+
if (nodeAdapter.isAvailable()) {
|
|
2415
|
+
return nodeAdapter;
|
|
2416
|
+
}
|
|
2417
|
+
throw new Error("No DOM adapter available. In Node.js, install jsdom: pnpm add jsdom");
|
|
2418
|
+
}
|
|
2419
|
+
function isAdapterAvailable() {
|
|
2420
|
+
return browserAdapter.isAvailable() || nodeAdapter.isAvailable();
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// src/conversion/html/delta-to-html.ts
|
|
2424
|
+
import { Delta as Delta7, isInsert as isInsert2, isEmbedInsert as isEmbedInsert2 } from "@scrider/delta";
|
|
2425
|
+
|
|
2426
|
+
// src/conversion/utils/slugify.ts
|
|
2427
|
+
function slugify(text) {
|
|
2428
|
+
return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s\-_]/gu, "").replace(/\s+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
2429
|
+
}
|
|
2430
|
+
function slugifyWithDedup(text, usedSlugs) {
|
|
2431
|
+
const base = slugify(text);
|
|
2432
|
+
const count = usedSlugs.get(base) ?? 0;
|
|
2433
|
+
usedSlugs.set(base, count + 1);
|
|
2434
|
+
if (count === 0) {
|
|
2435
|
+
return base;
|
|
2436
|
+
}
|
|
2437
|
+
return `${base}-${count}`;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/conversion/html/delta-to-html.ts
|
|
2441
|
+
function deltaToHtml(delta, options = {}) {
|
|
2442
|
+
const lines = splitIntoLines(delta);
|
|
2443
|
+
const embedRenderers = { ...EMBED_RENDERERS, ...options.embedRenderers };
|
|
2444
|
+
const pretty = options.pretty ?? false;
|
|
2445
|
+
const hierarchicalNumbers = options.hierarchicalNumbers ?? false;
|
|
2446
|
+
const blockHandlers = options.blockHandlers;
|
|
2447
|
+
const anchorLinks = options.anchorLinks ?? false;
|
|
2448
|
+
let html = "";
|
|
2449
|
+
let listStack = [];
|
|
2450
|
+
let counters = [];
|
|
2451
|
+
const slugUsageMap = /* @__PURE__ */ new Map();
|
|
2452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2453
|
+
const line = lines[i];
|
|
2454
|
+
if (!line) continue;
|
|
2455
|
+
if (isTableLine(line)) {
|
|
2456
|
+
html += closeAllLists(listStack, pretty);
|
|
2457
|
+
listStack = [];
|
|
2458
|
+
counters = [];
|
|
2459
|
+
const tableLines = collectTableLines(lines, i);
|
|
2460
|
+
html += renderTable(tableLines, embedRenderers, pretty, blockHandlers, options);
|
|
2461
|
+
i += tableLines.length - 1;
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
const { tag, isList, listType, indent, isCodeBlock } = getBlockInfo(line.attributes);
|
|
2465
|
+
if (isCodeBlock) {
|
|
2466
|
+
html += closeAllLists(listStack, pretty);
|
|
2467
|
+
listStack = [];
|
|
2468
|
+
counters = [];
|
|
2469
|
+
const codeLines = collectCodeBlockLines(lines, i);
|
|
2470
|
+
const language = getCodeBlockLanguage(line.attributes);
|
|
2471
|
+
html += renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options);
|
|
2472
|
+
i += codeLines.length - 1;
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (!isList && isBlockLevelEmbedLine(line)) {
|
|
2476
|
+
html += closeAllLists(listStack, pretty);
|
|
2477
|
+
listStack = [];
|
|
2478
|
+
counters = [];
|
|
2479
|
+
html += renderLineContent(line.ops, embedRenderers, blockHandlers, options);
|
|
2480
|
+
if (pretty) html += "\n";
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
if (isList) {
|
|
2484
|
+
html += handleListOpen(listStack, listType, indent, pretty);
|
|
2485
|
+
if (hierarchicalNumbers && listType === "ordered") {
|
|
2486
|
+
if (counters.length > indent + 1) {
|
|
2487
|
+
counters = counters.slice(0, indent + 1);
|
|
2488
|
+
}
|
|
2489
|
+
while (counters.length < indent) {
|
|
2490
|
+
counters.push(1);
|
|
2491
|
+
}
|
|
2492
|
+
if (counters.length === indent) {
|
|
2493
|
+
counters.push(0);
|
|
2494
|
+
}
|
|
2495
|
+
counters[indent] = (counters[indent] || 0) + 1;
|
|
2496
|
+
}
|
|
2497
|
+
} else {
|
|
2498
|
+
html += closeAllLists(listStack, pretty);
|
|
2499
|
+
listStack = [];
|
|
2500
|
+
counters = [];
|
|
2501
|
+
}
|
|
2502
|
+
const content = renderLineContent(line.ops, embedRenderers, blockHandlers, options);
|
|
2503
|
+
if (isList) {
|
|
2504
|
+
const listItemAttrs = getListItemAttributes(line.attributes);
|
|
2505
|
+
const indentLevel = listStack.length;
|
|
2506
|
+
let hierarchicalNumber;
|
|
2507
|
+
if (hierarchicalNumbers && listType === "ordered") {
|
|
2508
|
+
hierarchicalNumber = counters.slice(0, indent + 1).join(".");
|
|
2509
|
+
}
|
|
2510
|
+
html += renderListItem(content, listItemAttrs, pretty, indentLevel, hierarchicalNumber);
|
|
2511
|
+
} else {
|
|
2512
|
+
let headingId;
|
|
2513
|
+
if (line.attributes?.header) {
|
|
2514
|
+
const customId = line.attributes["header-id"];
|
|
2515
|
+
if (typeof customId === "string" && customId.length > 0) {
|
|
2516
|
+
headingId = customId;
|
|
2517
|
+
} else if (anchorLinks) {
|
|
2518
|
+
const plainText = extractPlainText(line.ops);
|
|
2519
|
+
headingId = slugifyWithDedup(plainText, slugUsageMap);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
html += renderBlock(content, tag, line.attributes, pretty, headingId);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
html += closeAllLists(listStack, pretty);
|
|
2526
|
+
if (options.wrapper) {
|
|
2527
|
+
html = `<${options.wrapper}>${html}</${options.wrapper}>`;
|
|
2528
|
+
}
|
|
2529
|
+
return html;
|
|
2530
|
+
}
|
|
2531
|
+
function getIndent(level) {
|
|
2532
|
+
return " ".repeat(level);
|
|
2533
|
+
}
|
|
2534
|
+
function splitIntoLines(delta) {
|
|
2535
|
+
const lines = [];
|
|
2536
|
+
let currentOps = [];
|
|
2537
|
+
for (const op of delta.ops) {
|
|
2538
|
+
if (!isInsert2(op)) continue;
|
|
2539
|
+
if (isEmbedInsert2(op)) {
|
|
2540
|
+
currentOps.push(op);
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
const text = op.insert;
|
|
2544
|
+
const parts = text.split("\n");
|
|
2545
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2546
|
+
const part = parts[i];
|
|
2547
|
+
if (part === void 0) continue;
|
|
2548
|
+
if (part.length > 0) {
|
|
2549
|
+
currentOps.push({
|
|
2550
|
+
insert: part,
|
|
2551
|
+
...op.attributes && { attributes: op.attributes }
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
if (i < parts.length - 1) {
|
|
2555
|
+
lines.push({
|
|
2556
|
+
ops: currentOps,
|
|
2557
|
+
attributes: op.attributes
|
|
2558
|
+
// Line attributes come from the \n op
|
|
2559
|
+
});
|
|
2560
|
+
currentOps = [];
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (currentOps.length > 0) {
|
|
2565
|
+
lines.push({
|
|
2566
|
+
ops: currentOps,
|
|
2567
|
+
attributes: void 0
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
return lines;
|
|
2571
|
+
}
|
|
2572
|
+
var BLOCK_LEVEL_EMBEDS = /* @__PURE__ */ new Set(["divider", "block"]);
|
|
2573
|
+
function isBlockLevelEmbedLine(line) {
|
|
2574
|
+
if (line.ops.length !== 1) return false;
|
|
2575
|
+
const op = line.ops[0];
|
|
2576
|
+
if (!op || !isEmbedInsert2(op)) return false;
|
|
2577
|
+
const embed = op.insert;
|
|
2578
|
+
const embedType = Object.keys(embed)[0];
|
|
2579
|
+
if (!!embedType && BLOCK_LEVEL_EMBEDS.has(embedType)) return true;
|
|
2580
|
+
const attrs = op.attributes;
|
|
2581
|
+
if (attrs && typeof attrs.float === "string" && attrs.float !== "none") return true;
|
|
2582
|
+
return false;
|
|
2583
|
+
}
|
|
2584
|
+
function isTableLine(line) {
|
|
2585
|
+
return line.attributes != null && typeof line.attributes["table-row"] === "number" && typeof line.attributes["table-col"] === "number";
|
|
2586
|
+
}
|
|
2587
|
+
function collectTableLines(lines, startIndex) {
|
|
2588
|
+
const result = [];
|
|
2589
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
2590
|
+
const line = lines[i];
|
|
2591
|
+
if (!line || !isTableLine(line)) break;
|
|
2592
|
+
result.push(line);
|
|
2593
|
+
}
|
|
2594
|
+
return result;
|
|
2595
|
+
}
|
|
2596
|
+
function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options) {
|
|
2597
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2598
|
+
for (const line of tableLines) {
|
|
2599
|
+
const attrs = line.attributes;
|
|
2600
|
+
const rowIdx = attrs["table-row"];
|
|
2601
|
+
const colIdx = attrs["table-col"];
|
|
2602
|
+
if (!rows.has(rowIdx)) {
|
|
2603
|
+
rows.set(rowIdx, { isHeader: !!attrs["table-header"], cells: /* @__PURE__ */ new Map() });
|
|
2604
|
+
}
|
|
2605
|
+
const row = rows.get(rowIdx);
|
|
2606
|
+
if (attrs["table-header"]) row.isHeader = true;
|
|
2607
|
+
row.cells.set(colIdx, {
|
|
2608
|
+
ops: line.ops,
|
|
2609
|
+
colAlign: typeof attrs["table-col-align"] === "string" ? attrs["table-col-align"] : void 0
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
const sortedRows = [...rows.entries()].sort((a, b) => a[0] - b[0]);
|
|
2613
|
+
let maxCol = 0;
|
|
2614
|
+
for (const [, row] of sortedRows) {
|
|
2615
|
+
for (const colIdx of row.cells.keys()) {
|
|
2616
|
+
if (colIdx > maxCol) maxCol = colIdx;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
const headerRows = sortedRows.filter(([, r]) => r.isHeader);
|
|
2620
|
+
const bodyRows = sortedRows.filter(([, r]) => !r.isHeader);
|
|
2621
|
+
const indent = pretty ? " " : "";
|
|
2622
|
+
const nl = pretty ? "\n" : "";
|
|
2623
|
+
let html = `<table>${nl}`;
|
|
2624
|
+
if (headerRows.length > 0) {
|
|
2625
|
+
html += `${indent}<thead>${nl}`;
|
|
2626
|
+
for (const [, row] of headerRows) {
|
|
2627
|
+
html += renderTableRow(
|
|
2628
|
+
row.cells,
|
|
2629
|
+
maxCol,
|
|
2630
|
+
"th",
|
|
2631
|
+
embedRenderers,
|
|
2632
|
+
pretty,
|
|
2633
|
+
2,
|
|
2634
|
+
blockHandlers,
|
|
2635
|
+
options
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
html += `${indent}</thead>${nl}`;
|
|
2639
|
+
}
|
|
2640
|
+
if (bodyRows.length > 0) {
|
|
2641
|
+
html += `${indent}<tbody>${nl}`;
|
|
2642
|
+
for (const [, row] of bodyRows) {
|
|
2643
|
+
html += renderTableRow(
|
|
2644
|
+
row.cells,
|
|
2645
|
+
maxCol,
|
|
2646
|
+
"td",
|
|
2647
|
+
embedRenderers,
|
|
2648
|
+
pretty,
|
|
2649
|
+
2,
|
|
2650
|
+
blockHandlers,
|
|
2651
|
+
options
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
html += `${indent}</tbody>${nl}`;
|
|
2655
|
+
}
|
|
2656
|
+
html += `</table>`;
|
|
2657
|
+
if (pretty) html += "\n";
|
|
2658
|
+
return html;
|
|
2659
|
+
}
|
|
2660
|
+
function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options) {
|
|
2661
|
+
const indent = pretty ? " ".repeat(depth) : "";
|
|
2662
|
+
const cellIndent = pretty ? " ".repeat(depth + 1) : "";
|
|
2663
|
+
const nl = pretty ? "\n" : "";
|
|
2664
|
+
let html = `${indent}<tr>${nl}`;
|
|
2665
|
+
for (let col = 0; col <= maxCol; col++) {
|
|
2666
|
+
const cell = cells.get(col);
|
|
2667
|
+
const content = cell ? renderLineContent(cell.ops, embedRenderers, blockHandlers, options) : "";
|
|
2668
|
+
const alignStyle = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
|
|
2669
|
+
html += `${cellIndent}<${cellTag}${alignStyle}>${content}</${cellTag}>${nl}`;
|
|
2670
|
+
}
|
|
2671
|
+
html += `${indent}</tr>${nl}`;
|
|
2672
|
+
return html;
|
|
2673
|
+
}
|
|
2674
|
+
function getBlockInfo(attributes) {
|
|
2675
|
+
if (!attributes) {
|
|
2676
|
+
return { tag: "p", isList: false, isCodeBlock: false, listType: void 0, indent: 0 };
|
|
2677
|
+
}
|
|
2678
|
+
const indent = typeof attributes.indent === "number" ? attributes.indent : 0;
|
|
2679
|
+
if (attributes["code-block"]) {
|
|
2680
|
+
return { tag: "pre", isList: false, isCodeBlock: true, listType: void 0, indent };
|
|
2681
|
+
}
|
|
2682
|
+
if (attributes.list) {
|
|
2683
|
+
const listVal = attributes.list;
|
|
2684
|
+
const listType = typeof listVal === "string" ? listVal : "bullet";
|
|
2685
|
+
return { tag: "li", isList: true, isCodeBlock: false, listType, indent };
|
|
2686
|
+
}
|
|
2687
|
+
for (const [format, tagOrFn] of Object.entries(BLOCK_FORMAT_TAGS)) {
|
|
2688
|
+
if (format in attributes && format !== "list" && format !== "code-block") {
|
|
2689
|
+
const tag = typeof tagOrFn === "function" ? tagOrFn(attributes[format]) : tagOrFn;
|
|
2690
|
+
return { tag, isList: false, isCodeBlock: false, listType: void 0, indent };
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return { tag: "p", isList: false, isCodeBlock: false, listType: void 0, indent };
|
|
2694
|
+
}
|
|
2695
|
+
function collectCodeBlockLines(lines, startIndex) {
|
|
2696
|
+
const codeLines = [];
|
|
2697
|
+
const startLine = lines[startIndex];
|
|
2698
|
+
if (!startLine) return codeLines;
|
|
2699
|
+
const startLang = getCodeBlockLanguage(startLine.attributes);
|
|
2700
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
2701
|
+
const line = lines[i];
|
|
2702
|
+
if (!line || !line.attributes?.["code-block"]) break;
|
|
2703
|
+
const lang = getCodeBlockLanguage(line.attributes);
|
|
2704
|
+
if (i > startIndex && lang !== startLang) break;
|
|
2705
|
+
codeLines.push(line);
|
|
2706
|
+
}
|
|
2707
|
+
return codeLines;
|
|
2708
|
+
}
|
|
2709
|
+
function getCodeBlockLanguage(attributes) {
|
|
2710
|
+
if (!attributes) return void 0;
|
|
2711
|
+
const codeBlock = attributes["code-block"];
|
|
2712
|
+
if (typeof codeBlock === "string" && codeBlock !== "true") {
|
|
2713
|
+
return codeBlock;
|
|
2714
|
+
}
|
|
2715
|
+
return void 0;
|
|
2716
|
+
}
|
|
2717
|
+
function renderCodeBlock(codeLines, language, embedRenderers, pretty, blockHandlers, options) {
|
|
2718
|
+
const lineContents = codeLines.map(
|
|
2719
|
+
(line) => renderLineContent(line.ops, embedRenderers, blockHandlers, options)
|
|
2720
|
+
);
|
|
2721
|
+
const code = lineContents.join("\n");
|
|
2722
|
+
const langClass = language ? ` class="language-${escapeHtml(language)}"` : "";
|
|
2723
|
+
const langAttr = language ? ` data-language="${escapeHtml(language)}"` : "";
|
|
2724
|
+
const html = `<pre${langAttr}><code${langClass}>${code}
|
|
2725
|
+
</code></pre>`;
|
|
2726
|
+
return pretty ? html + "\n" : html;
|
|
2727
|
+
}
|
|
2728
|
+
function handleListOpen(stack, listType, indent, pretty) {
|
|
2729
|
+
let html = "";
|
|
2730
|
+
const wrapperTag = LIST_WRAPPER_TAGS[listType] || "ul";
|
|
2731
|
+
while (stack.length > 0) {
|
|
2732
|
+
const last = stack[stack.length - 1];
|
|
2733
|
+
if (!last || last.indent <= indent) break;
|
|
2734
|
+
const closed = stack.pop();
|
|
2735
|
+
if (closed) {
|
|
2736
|
+
const closeTag = LIST_WRAPPER_TAGS[closed.type] || "ul";
|
|
2737
|
+
if (pretty) html += getIndent(stack.length);
|
|
2738
|
+
html += `</${closeTag}>`;
|
|
2739
|
+
if (pretty) html += "\n";
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
const top = stack[stack.length - 1];
|
|
2743
|
+
if (top && top.indent === indent && top.type !== listType) {
|
|
2744
|
+
const closed = stack.pop();
|
|
2745
|
+
if (closed) {
|
|
2746
|
+
const closeTag = LIST_WRAPPER_TAGS[closed.type] || "ul";
|
|
2747
|
+
if (pretty) html += getIndent(stack.length);
|
|
2748
|
+
html += `</${closeTag}>`;
|
|
2749
|
+
if (pretty) html += "\n";
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
while (true) {
|
|
2753
|
+
const last = stack[stack.length - 1];
|
|
2754
|
+
const currentIndent = last ? last.indent + 1 : 0;
|
|
2755
|
+
if (currentIndent > indent) break;
|
|
2756
|
+
if (pretty) html += getIndent(stack.length);
|
|
2757
|
+
html += `<${wrapperTag}>`;
|
|
2758
|
+
if (pretty) html += "\n";
|
|
2759
|
+
stack.push({ type: listType, indent: currentIndent });
|
|
2760
|
+
if (currentIndent >= indent) break;
|
|
2761
|
+
}
|
|
2762
|
+
const current = stack[stack.length - 1];
|
|
2763
|
+
if (!current || current.indent < indent) {
|
|
2764
|
+
if (pretty) html += getIndent(stack.length);
|
|
2765
|
+
html += `<${wrapperTag}>`;
|
|
2766
|
+
if (pretty) html += "\n";
|
|
2767
|
+
stack.push({ type: listType, indent });
|
|
2768
|
+
}
|
|
2769
|
+
return html;
|
|
2770
|
+
}
|
|
2771
|
+
function closeAllLists(stack, pretty) {
|
|
2772
|
+
let html = "";
|
|
2773
|
+
while (stack.length > 0) {
|
|
2774
|
+
const closed = stack.pop();
|
|
2775
|
+
const closeTag = LIST_WRAPPER_TAGS[closed.type] || "ul";
|
|
2776
|
+
if (pretty) html += getIndent(stack.length);
|
|
2777
|
+
html += `</${closeTag}>`;
|
|
2778
|
+
if (pretty) html += "\n";
|
|
2779
|
+
}
|
|
2780
|
+
return html;
|
|
2781
|
+
}
|
|
2782
|
+
function getListItemAttributes(attributes) {
|
|
2783
|
+
if (!attributes) return "";
|
|
2784
|
+
const listType = attributes.list;
|
|
2785
|
+
if (listType === "checked") {
|
|
2786
|
+
return ' data-checked="true"';
|
|
2787
|
+
}
|
|
2788
|
+
if (listType === "unchecked") {
|
|
2789
|
+
return ' data-checked="false"';
|
|
2790
|
+
}
|
|
2791
|
+
return "";
|
|
2792
|
+
}
|
|
2793
|
+
function renderListItem(content, attrs, pretty, indentLevel, hierarchicalNumber) {
|
|
2794
|
+
const indent = pretty ? getIndent(indentLevel) : "";
|
|
2795
|
+
const innerContent = content || "<br>";
|
|
2796
|
+
let fullAttrs = attrs;
|
|
2797
|
+
if (hierarchicalNumber) {
|
|
2798
|
+
fullAttrs += ` data-number="${hierarchicalNumber}"`;
|
|
2799
|
+
}
|
|
2800
|
+
const html = `${indent}<li${fullAttrs}>${innerContent}</li>`;
|
|
2801
|
+
return pretty ? html + "\n" : html;
|
|
2802
|
+
}
|
|
2803
|
+
function renderBlock(content, tag, attributes, pretty, id) {
|
|
2804
|
+
const idAttr = id ? ` id="${escapeHtml(id)}"` : "";
|
|
2805
|
+
const styleAttr = getBlockStyleAttribute(attributes);
|
|
2806
|
+
const innerContent = content || "<br>";
|
|
2807
|
+
const html = `<${tag}${idAttr}${styleAttr}>${innerContent}</${tag}>`;
|
|
2808
|
+
return pretty ? html + "\n" : html;
|
|
2809
|
+
}
|
|
2810
|
+
function getBlockStyleAttribute(attributes) {
|
|
2811
|
+
if (!attributes) return "";
|
|
2812
|
+
const styles = [];
|
|
2813
|
+
const alignVal = attributes.align;
|
|
2814
|
+
if (alignVal && typeof alignVal === "string" && alignVal !== "left") {
|
|
2815
|
+
styles.push(`text-align: ${alignVal}`);
|
|
2816
|
+
}
|
|
2817
|
+
if (attributes.indent && typeof attributes.indent === "number") {
|
|
2818
|
+
if (!attributes.list) {
|
|
2819
|
+
styles.push(`margin-left: ${attributes.indent * 2}em`);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
return styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
|
|
2823
|
+
}
|
|
2824
|
+
function extractPlainText(ops) {
|
|
2825
|
+
let text = "";
|
|
2826
|
+
for (const op of ops) {
|
|
2827
|
+
if (isInsert2(op) && typeof op.insert === "string") {
|
|
2828
|
+
text += op.insert;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return text;
|
|
2832
|
+
}
|
|
2833
|
+
function renderLineContent(ops, embedRenderers, blockHandlers, options) {
|
|
2834
|
+
let html = "";
|
|
2835
|
+
for (const op of ops) {
|
|
2836
|
+
if (!isInsert2(op)) continue;
|
|
2837
|
+
if (isEmbedInsert2(op)) {
|
|
2838
|
+
html += renderEmbed(
|
|
2839
|
+
op.insert,
|
|
2840
|
+
op.attributes,
|
|
2841
|
+
embedRenderers,
|
|
2842
|
+
blockHandlers,
|
|
2843
|
+
options
|
|
2844
|
+
);
|
|
2845
|
+
} else {
|
|
2846
|
+
html += renderInlineText(op.insert, op.attributes);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return html;
|
|
2850
|
+
}
|
|
2851
|
+
function renderInlineText(text, attributes) {
|
|
2852
|
+
if (!text) return "";
|
|
2853
|
+
let html = escapeHtml(text);
|
|
2854
|
+
if (!attributes) return html;
|
|
2855
|
+
const styles = [];
|
|
2856
|
+
for (const [format, cssProperty] of Object.entries(INLINE_STYLE_FORMATS)) {
|
|
2857
|
+
if (format in attributes) {
|
|
2858
|
+
styles.push(`${cssProperty}: ${String(attributes[format])}`);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
if (styles.length > 0) {
|
|
2862
|
+
html = `<span style="${styles.join("; ")}">${html}</span>`;
|
|
2863
|
+
}
|
|
2864
|
+
for (let i = INLINE_FORMAT_ORDER.length - 1; i >= 0; i--) {
|
|
2865
|
+
const format = INLINE_FORMAT_ORDER[i];
|
|
2866
|
+
if (!format) continue;
|
|
2867
|
+
if (!(format in attributes)) continue;
|
|
2868
|
+
const tag = INLINE_FORMAT_TAGS[format];
|
|
2869
|
+
if (!tag) continue;
|
|
2870
|
+
if (format === "link") {
|
|
2871
|
+
const href = escapeHtml(String(attributes.link));
|
|
2872
|
+
html = `<a href="${href}">${html}</a>`;
|
|
2873
|
+
} else {
|
|
2874
|
+
html = `<${tag}>${html}</${tag}>`;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
return html;
|
|
2878
|
+
}
|
|
2879
|
+
function renderEmbed(value, attributes, renderers, blockHandlers, options) {
|
|
2880
|
+
const embedType = Object.keys(value)[0];
|
|
2881
|
+
if (!embedType) return "";
|
|
2882
|
+
if (embedType === "block" && blockHandlers) {
|
|
2883
|
+
const blockData = value.block;
|
|
2884
|
+
if (blockData && typeof blockData.type === "string") {
|
|
2885
|
+
const handler = blockHandlers.get(blockData.type);
|
|
2886
|
+
if (handler) {
|
|
2887
|
+
if (handler.validate && !handler.validate(blockData)) {
|
|
2888
|
+
return "";
|
|
2889
|
+
}
|
|
2890
|
+
const context = {
|
|
2891
|
+
registry: null,
|
|
2892
|
+
options: { pretty: options?.pretty ?? false },
|
|
2893
|
+
renderDelta: (ops) => deltaToHtml(new Delta7(ops), options ?? {}),
|
|
2894
|
+
...attributes ? { opAttributes: attributes } : {}
|
|
2895
|
+
};
|
|
2896
|
+
return handler.toHtml(blockData, context);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return "";
|
|
2900
|
+
}
|
|
2901
|
+
const embedValue = value[embedType];
|
|
2902
|
+
const registry = options?.registry;
|
|
2903
|
+
if (registry) {
|
|
2904
|
+
const format = registry.get(embedType);
|
|
2905
|
+
if (format?.render) {
|
|
2906
|
+
return format.render(embedValue, attributes);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
const renderer = renderers[embedType];
|
|
2910
|
+
if (renderer) {
|
|
2911
|
+
return renderer(embedValue, attributes);
|
|
2912
|
+
}
|
|
2913
|
+
return `<span data-embed="${escapeHtml(embedType)}" data-value="${escapeHtml(String(embedValue))}"></span>`;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
// src/conversion/html/html-to-delta.ts
|
|
2917
|
+
import { Delta as Delta8 } from "@scrider/delta";
|
|
2918
|
+
function htmlToDelta(html, options = {}) {
|
|
2919
|
+
const adapter = options.adapter ?? getAdapter();
|
|
2920
|
+
const normalizeWhitespace = options.normalizeWhitespace ?? true;
|
|
2921
|
+
const tagHandlers = { ...DEFAULT_TAG_HANDLERS, ...options.tagHandlers };
|
|
2922
|
+
const fragment = adapter.parseHTML(html);
|
|
2923
|
+
const delta = new Delta8();
|
|
2924
|
+
let currentAttributes = {};
|
|
2925
|
+
let currentBlockAttributes = {};
|
|
2926
|
+
let pendingText = "";
|
|
2927
|
+
let atLineStart = true;
|
|
2928
|
+
const context = {
|
|
2929
|
+
delta,
|
|
2930
|
+
get attributes() {
|
|
2931
|
+
return { ...currentAttributes };
|
|
2932
|
+
},
|
|
2933
|
+
get blockAttributes() {
|
|
2934
|
+
return { ...currentBlockAttributes };
|
|
2935
|
+
},
|
|
2936
|
+
pushText(text) {
|
|
2937
|
+
if (normalizeWhitespace) {
|
|
2938
|
+
text = normalizeText(text, pendingText, atLineStart);
|
|
2939
|
+
}
|
|
2940
|
+
if (text) {
|
|
2941
|
+
pendingText += text;
|
|
2942
|
+
atLineStart = false;
|
|
2943
|
+
}
|
|
2944
|
+
},
|
|
2945
|
+
pushEmbed(embed, attrs) {
|
|
2946
|
+
flushText();
|
|
2947
|
+
const finalAttrs = { ...currentAttributes, ...attrs };
|
|
2948
|
+
if (Object.keys(finalAttrs).length > 0) {
|
|
2949
|
+
delta.insert(embed, finalAttrs);
|
|
2950
|
+
} else {
|
|
2951
|
+
delta.insert(embed);
|
|
2952
|
+
}
|
|
2953
|
+
atLineStart = false;
|
|
2954
|
+
},
|
|
2955
|
+
pushNewline() {
|
|
2956
|
+
flushText();
|
|
2957
|
+
const attrs = { ...currentBlockAttributes };
|
|
2958
|
+
if (Object.keys(attrs).length > 0) {
|
|
2959
|
+
delta.insert("\n", attrs);
|
|
2960
|
+
} else {
|
|
2961
|
+
delta.insert("\n");
|
|
2962
|
+
}
|
|
2963
|
+
currentBlockAttributes = {};
|
|
2964
|
+
atLineStart = true;
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
function flushText() {
|
|
2968
|
+
if (pendingText) {
|
|
2969
|
+
const attrs = { ...currentAttributes };
|
|
2970
|
+
if (Object.keys(attrs).length > 0) {
|
|
2971
|
+
delta.insert(pendingText, attrs);
|
|
2972
|
+
} else {
|
|
2973
|
+
delta.insert(pendingText);
|
|
2974
|
+
}
|
|
2975
|
+
pendingText = "";
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
function processNode(node) {
|
|
2979
|
+
if (node.nodeType === NODE_TYPE.TEXT_NODE) {
|
|
2980
|
+
const text = node.textContent ?? "";
|
|
2981
|
+
context.pushText(text);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
if (!isElement(node)) return;
|
|
2985
|
+
const tagName = node.tagName.toLowerCase();
|
|
2986
|
+
const handler = findTagHandler(tagHandlers, node, tagName);
|
|
2987
|
+
if (handler) {
|
|
2988
|
+
handler(node, context);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const blockFormat2 = TAG_TO_BLOCK_FORMAT[tagName];
|
|
2992
|
+
if (blockFormat2) {
|
|
2993
|
+
processBlockElement(node, blockFormat2);
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (tagName === "ul" || tagName === "ol") {
|
|
2997
|
+
processListElement(node, tagName);
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
const inlineFormat = TAG_TO_INLINE_FORMAT[tagName];
|
|
3001
|
+
if (inlineFormat) {
|
|
3002
|
+
processInlineElement(node, inlineFormat);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
if (tagName === "a") {
|
|
3006
|
+
processLinkElement(node);
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
if (tagName === "span") {
|
|
3010
|
+
processSpanElement(node);
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (tagName === "table") {
|
|
3014
|
+
processTableElement(node);
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
if (options.registry) {
|
|
3018
|
+
const embedFormats = options.registry.getByScope("embed");
|
|
3019
|
+
for (const format of embedFormats) {
|
|
3020
|
+
if (format.match) {
|
|
3021
|
+
const result = format.match(node);
|
|
3022
|
+
if (result != null) {
|
|
3023
|
+
context.pushEmbed({ [format.name]: result.value }, result.attributes);
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
if (tagName === "img") {
|
|
3030
|
+
processImageElement(node);
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
if (tagName === "video" || tagName === "iframe") {
|
|
3034
|
+
processVideoElement(node);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
if (tagName === "hr") {
|
|
3038
|
+
context.pushEmbed({ divider: true });
|
|
3039
|
+
context.pushNewline();
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
if (tagName === "section" || tagName === "div") {
|
|
3043
|
+
const className = node.getAttribute("class") || "";
|
|
3044
|
+
if (className.includes("footnotes")) {
|
|
3045
|
+
processFootnotesSection(node);
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
if (className.includes("markdown-alert")) {
|
|
3049
|
+
processAlertElement(node);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
if (/\bcolumns\b/.test(className)) {
|
|
3053
|
+
processColumnsElement(node);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
if (className.includes("inline-box")) {
|
|
3057
|
+
processBoxElement(node);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (tagName === "p" || tagName === "div") {
|
|
3062
|
+
processDefaultBlock(node);
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
if (tagName === "br") {
|
|
3066
|
+
context.pushNewline();
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
processChildren(node);
|
|
3070
|
+
}
|
|
3071
|
+
function processChildren(node) {
|
|
3072
|
+
const children2 = node.childNodes;
|
|
3073
|
+
for (let i = 0; i < children2.length; i++) {
|
|
3074
|
+
const child = children2[i];
|
|
3075
|
+
if (child) processNode(child);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
function processBlockElement(element, format) {
|
|
3079
|
+
if (format.format === "code-block") {
|
|
3080
|
+
processCodeBlockElement(element);
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
const prevBlockAttrs = { ...currentBlockAttributes };
|
|
3084
|
+
currentBlockAttributes[format.format] = format.value;
|
|
3085
|
+
const align = getAlignment(element);
|
|
3086
|
+
if (align) {
|
|
3087
|
+
currentBlockAttributes.align = align;
|
|
3088
|
+
}
|
|
3089
|
+
if (format.format === "header") {
|
|
3090
|
+
const id = element.getAttribute("id");
|
|
3091
|
+
if (id) {
|
|
3092
|
+
const text = element.textContent || "";
|
|
3093
|
+
const computedSlug = slugify(text);
|
|
3094
|
+
if (id !== computedSlug) {
|
|
3095
|
+
currentBlockAttributes["header-id"] = id;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
processChildren(element);
|
|
3100
|
+
context.pushNewline();
|
|
3101
|
+
currentBlockAttributes = prevBlockAttrs;
|
|
3102
|
+
}
|
|
3103
|
+
function processCodeBlockElement(element) {
|
|
3104
|
+
const prevBlockAttrs = { ...currentBlockAttributes };
|
|
3105
|
+
let language;
|
|
3106
|
+
const dataLang = element.getAttribute("data-language");
|
|
3107
|
+
if (dataLang) {
|
|
3108
|
+
language = dataLang;
|
|
3109
|
+
}
|
|
3110
|
+
const children2 = element.childNodes;
|
|
3111
|
+
let codeElement = null;
|
|
3112
|
+
for (let i = 0; i < children2.length; i++) {
|
|
3113
|
+
const child = children2[i];
|
|
3114
|
+
if (child && isElement(child) && child.tagName.toLowerCase() === "code") {
|
|
3115
|
+
codeElement = child;
|
|
3116
|
+
break;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
if (codeElement && !language) {
|
|
3120
|
+
const className = codeElement.getAttribute("class") || "";
|
|
3121
|
+
const match = className.match(/language-(\S+)/);
|
|
3122
|
+
if (match?.[1]) {
|
|
3123
|
+
language = match[1];
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
const codeBlockValue = language || true;
|
|
3127
|
+
currentBlockAttributes["code-block"] = codeBlockValue;
|
|
3128
|
+
const sourceElement = codeElement || element;
|
|
3129
|
+
const rawText = sourceElement.textContent ?? "";
|
|
3130
|
+
const text = rawText.endsWith("\n") ? rawText.slice(0, -1) : rawText;
|
|
3131
|
+
const codeLines = text.split("\n");
|
|
3132
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
3133
|
+
const line = codeLines[i];
|
|
3134
|
+
if (line !== void 0 && line.length > 0) {
|
|
3135
|
+
flushText();
|
|
3136
|
+
pendingText = line;
|
|
3137
|
+
}
|
|
3138
|
+
flushText();
|
|
3139
|
+
const attrs = { ...currentBlockAttributes };
|
|
3140
|
+
if (Object.keys(attrs).length > 0) {
|
|
3141
|
+
delta.insert("\n", attrs);
|
|
3142
|
+
} else {
|
|
3143
|
+
delta.insert("\n");
|
|
3144
|
+
}
|
|
3145
|
+
atLineStart = true;
|
|
3146
|
+
}
|
|
3147
|
+
currentBlockAttributes = prevBlockAttrs;
|
|
3148
|
+
}
|
|
3149
|
+
function processListElement(element, listTag, indent = 0) {
|
|
3150
|
+
const children2 = element.childNodes;
|
|
3151
|
+
for (let i = 0; i < children2.length; i++) {
|
|
3152
|
+
const child = children2[i];
|
|
3153
|
+
if (!child || !isElement(child)) continue;
|
|
3154
|
+
const childTag = child.tagName.toLowerCase();
|
|
3155
|
+
if (childTag === "li") {
|
|
3156
|
+
processListItem(child, listTag, indent);
|
|
3157
|
+
} else if (childTag === "ul" || childTag === "ol") {
|
|
3158
|
+
processListElement(child, childTag, indent + 1);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
function processListItem(element, listTag, indent) {
|
|
3163
|
+
const prevBlockAttrs = { ...currentBlockAttributes };
|
|
3164
|
+
let listType = listTag === "ol" ? "ordered" : "bullet";
|
|
3165
|
+
const dataChecked = element.getAttribute("data-checked");
|
|
3166
|
+
if (dataChecked === "true") {
|
|
3167
|
+
listType = "checked";
|
|
3168
|
+
} else if (dataChecked === "false") {
|
|
3169
|
+
listType = "unchecked";
|
|
3170
|
+
}
|
|
3171
|
+
currentBlockAttributes.list = listType;
|
|
3172
|
+
if (indent > 0) {
|
|
3173
|
+
currentBlockAttributes.indent = indent;
|
|
3174
|
+
}
|
|
3175
|
+
const children2 = element.childNodes;
|
|
3176
|
+
let hasNestedList = false;
|
|
3177
|
+
const firstChild = children2[0];
|
|
3178
|
+
const isBrOnlyListItem = children2.length === 1 && firstChild !== void 0 && isElement(firstChild) && firstChild.tagName.toLowerCase() === "br";
|
|
3179
|
+
for (let i = 0; i < children2.length; i++) {
|
|
3180
|
+
const child = children2[i];
|
|
3181
|
+
if (!child) continue;
|
|
3182
|
+
if (isElement(child)) {
|
|
3183
|
+
const childTag = child.tagName.toLowerCase();
|
|
3184
|
+
if (childTag === "ul" || childTag === "ol") {
|
|
3185
|
+
if (!hasNestedList) {
|
|
3186
|
+
context.pushNewline();
|
|
3187
|
+
hasNestedList = true;
|
|
3188
|
+
}
|
|
3189
|
+
processNestedList(child, childTag, indent + 1);
|
|
3190
|
+
continue;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
if (!hasNestedList && !isBrOnlyListItem) {
|
|
3194
|
+
processNode(child);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
if (!hasNestedList) {
|
|
3198
|
+
context.pushNewline();
|
|
3199
|
+
}
|
|
3200
|
+
currentBlockAttributes = prevBlockAttrs;
|
|
3201
|
+
}
|
|
3202
|
+
function processNestedList(element, listTag, indent) {
|
|
3203
|
+
const children2 = element.childNodes;
|
|
3204
|
+
for (let i = 0; i < children2.length; i++) {
|
|
3205
|
+
const child = children2[i];
|
|
3206
|
+
if (!child || !isElement(child)) continue;
|
|
3207
|
+
const childTag = child.tagName.toLowerCase();
|
|
3208
|
+
if (childTag === "li") {
|
|
3209
|
+
processListItem(child, listTag, indent);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
function processInlineElement(element, format) {
|
|
3214
|
+
flushText();
|
|
3215
|
+
const prevAttrs = { ...currentAttributes };
|
|
3216
|
+
currentAttributes[format.format] = format.value;
|
|
3217
|
+
processChildren(element);
|
|
3218
|
+
flushText();
|
|
3219
|
+
currentAttributes = prevAttrs;
|
|
3220
|
+
}
|
|
3221
|
+
function processLinkElement(element) {
|
|
3222
|
+
const href = element.getAttribute("href");
|
|
3223
|
+
if (!href) {
|
|
3224
|
+
processChildren(element);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
flushText();
|
|
3228
|
+
const prevAttrs = { ...currentAttributes };
|
|
3229
|
+
currentAttributes.link = href;
|
|
3230
|
+
processChildren(element);
|
|
3231
|
+
flushText();
|
|
3232
|
+
currentAttributes = prevAttrs;
|
|
3233
|
+
}
|
|
3234
|
+
function processSpanElement(element) {
|
|
3235
|
+
flushText();
|
|
3236
|
+
const prevAttrs = { ...currentAttributes };
|
|
3237
|
+
const color = element.style?.color || element.style?.getPropertyValue?.("color");
|
|
3238
|
+
if (color) {
|
|
3239
|
+
currentAttributes.color = color;
|
|
3240
|
+
}
|
|
3241
|
+
const bg = element.style?.backgroundColor || element.style?.getPropertyValue?.("background-color");
|
|
3242
|
+
if (bg) {
|
|
3243
|
+
currentAttributes.background = bg;
|
|
3244
|
+
}
|
|
3245
|
+
processChildren(element);
|
|
3246
|
+
flushText();
|
|
3247
|
+
currentAttributes = prevAttrs;
|
|
3248
|
+
}
|
|
3249
|
+
function processImageElement(element) {
|
|
3250
|
+
const src = element.getAttribute("src");
|
|
3251
|
+
if (!src) return;
|
|
3252
|
+
const attrs = {};
|
|
3253
|
+
const alt = element.getAttribute("alt");
|
|
3254
|
+
const width = element.getAttribute("width");
|
|
3255
|
+
const height = element.getAttribute("height");
|
|
3256
|
+
const float = element.getAttribute("data-float");
|
|
3257
|
+
if (alt) attrs.alt = alt;
|
|
3258
|
+
if (width) attrs.width = parseInt(width, 10);
|
|
3259
|
+
if (height) attrs.height = parseInt(height, 10);
|
|
3260
|
+
if (float) attrs.float = float;
|
|
3261
|
+
context.pushEmbed({ image: src }, attrs);
|
|
3262
|
+
}
|
|
3263
|
+
function processVideoElement(element) {
|
|
3264
|
+
const src = element.getAttribute("src");
|
|
3265
|
+
if (!src) return;
|
|
3266
|
+
const attrs = {};
|
|
3267
|
+
const float = element.getAttribute("data-float");
|
|
3268
|
+
const style = element.getAttribute("style") || "";
|
|
3269
|
+
if (float) attrs.float = float;
|
|
3270
|
+
const widthMatch = style.match(/(?:^|;\s*)width:\s*([^;]+)/);
|
|
3271
|
+
if (widthMatch?.[1]) attrs.width = widthMatch[1].trim().replace(/px$/, "");
|
|
3272
|
+
const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
|
|
3273
|
+
if (heightMatch?.[1]) attrs.height = heightMatch[1].trim().replace(/px$/, "");
|
|
3274
|
+
const embedAttrs = Object.keys(attrs).length > 0 ? attrs : void 0;
|
|
3275
|
+
context.pushEmbed({ video: fromVideoEmbedUrl(src) }, embedAttrs);
|
|
3276
|
+
}
|
|
3277
|
+
function processFootnotesSection(section) {
|
|
3278
|
+
const footnotesHandler = options.blockHandlers?.get("footnotes");
|
|
3279
|
+
if (footnotesHandler) {
|
|
3280
|
+
const blockContext = {
|
|
3281
|
+
registry: void 0,
|
|
3282
|
+
// Registry not needed for fromHtml parsing
|
|
3283
|
+
parseElement: (el) => {
|
|
3284
|
+
const innerHtml = el.innerHTML ?? "";
|
|
3285
|
+
if (!innerHtml) return [{ insert: "\n" }];
|
|
3286
|
+
return htmlToDelta(innerHtml, options).ops;
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
const data = footnotesHandler.fromHtml(section, blockContext);
|
|
3290
|
+
if (data) {
|
|
3291
|
+
flushText();
|
|
3292
|
+
delta.insert({ block: data });
|
|
3293
|
+
delta.insert("\n");
|
|
3294
|
+
atLineStart = true;
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
processChildren(section);
|
|
3299
|
+
}
|
|
3300
|
+
function processAlertElement(element) {
|
|
3301
|
+
const alertHandler = options.blockHandlers?.get("alert");
|
|
3302
|
+
if (alertHandler) {
|
|
3303
|
+
const blockContext = {
|
|
3304
|
+
registry: void 0,
|
|
3305
|
+
parseElement: (el) => {
|
|
3306
|
+
const innerHtml = el.innerHTML ?? "";
|
|
3307
|
+
if (!innerHtml) return [{ insert: "\n" }];
|
|
3308
|
+
return htmlToDelta(innerHtml, options).ops;
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
3311
|
+
const data = alertHandler.fromHtml(element, blockContext);
|
|
3312
|
+
if (data) {
|
|
3313
|
+
flushText();
|
|
3314
|
+
delta.insert({ block: data });
|
|
3315
|
+
delta.insert("\n");
|
|
3316
|
+
atLineStart = true;
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
processChildren(element);
|
|
3321
|
+
}
|
|
3322
|
+
function processColumnsElement(element) {
|
|
3323
|
+
const columnsHandler = options.blockHandlers?.get("columns");
|
|
3324
|
+
if (columnsHandler) {
|
|
3325
|
+
const blockContext = {
|
|
3326
|
+
registry: void 0,
|
|
3327
|
+
parseElement: (el) => {
|
|
3328
|
+
const innerHtml = el.innerHTML ?? "";
|
|
3329
|
+
if (!innerHtml) return [{ insert: "\n" }];
|
|
3330
|
+
return htmlToDelta(innerHtml, options).ops;
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
3333
|
+
const data = columnsHandler.fromHtml(element, blockContext);
|
|
3334
|
+
if (data) {
|
|
3335
|
+
flushText();
|
|
3336
|
+
delta.insert({ block: data });
|
|
3337
|
+
delta.insert("\n");
|
|
3338
|
+
atLineStart = true;
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
processChildren(element);
|
|
3343
|
+
}
|
|
3344
|
+
function processBoxElement(element) {
|
|
3345
|
+
const boxHandler = options.blockHandlers?.get("box");
|
|
3346
|
+
if (boxHandler) {
|
|
3347
|
+
const blockContext = {
|
|
3348
|
+
registry: void 0,
|
|
3349
|
+
parseElement: (el) => {
|
|
3350
|
+
const innerHtml = el.innerHTML ?? "";
|
|
3351
|
+
if (!innerHtml) return [{ insert: "\n" }];
|
|
3352
|
+
return htmlToDelta(innerHtml, options).ops;
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3355
|
+
const data = boxHandler.fromHtml(element, blockContext);
|
|
3356
|
+
if (data) {
|
|
3357
|
+
flushText();
|
|
3358
|
+
const opAttrs = {};
|
|
3359
|
+
const dataFloat = element.getAttribute("data-float");
|
|
3360
|
+
if (dataFloat) opAttrs.float = dataFloat;
|
|
3361
|
+
const dataOverflow = element.getAttribute("data-overflow");
|
|
3362
|
+
if (dataOverflow) opAttrs.overflow = dataOverflow;
|
|
3363
|
+
const style = element.getAttribute("style") || "";
|
|
3364
|
+
const widthMatch = style.match(/(?:^|;\s*)width:\s*([^;]+)/);
|
|
3365
|
+
if (widthMatch?.[1]) opAttrs.width = widthMatch[1].trim();
|
|
3366
|
+
const heightMatch = style.match(/(?:^|;\s*)height:\s*([^;]+)/);
|
|
3367
|
+
if (heightMatch?.[1]) opAttrs.height = heightMatch[1].trim();
|
|
3368
|
+
const hasAttrs = Object.keys(opAttrs).length > 0;
|
|
3369
|
+
delta.insert({ block: data }, hasAttrs ? opAttrs : void 0);
|
|
3370
|
+
delta.insert("\n");
|
|
3371
|
+
atLineStart = true;
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
processChildren(element);
|
|
3376
|
+
}
|
|
3377
|
+
function processTableElement(table) {
|
|
3378
|
+
const tableHandler = options.blockHandlers?.get("table");
|
|
3379
|
+
if (tableHandler) {
|
|
3380
|
+
const blockContext = {
|
|
3381
|
+
registry: void 0,
|
|
3382
|
+
// Registry not needed for fromHtml parsing
|
|
3383
|
+
parseElement: (el) => {
|
|
3384
|
+
const innerHtml = el.innerHTML ?? "";
|
|
3385
|
+
if (!innerHtml) return [{ insert: "\n" }];
|
|
3386
|
+
return htmlToDelta(innerHtml, options).ops;
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
const data = tableHandler.fromHtml(table, blockContext);
|
|
3390
|
+
if (data) {
|
|
3391
|
+
flushText();
|
|
3392
|
+
delta.insert({ block: data });
|
|
3393
|
+
delta.insert("\n");
|
|
3394
|
+
atLineStart = true;
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
let rowIdx = 0;
|
|
3399
|
+
const tableChildren = table.childNodes;
|
|
3400
|
+
for (let i = 0; i < tableChildren.length; i++) {
|
|
3401
|
+
const section = tableChildren[i];
|
|
3402
|
+
if (!section || !isElement(section)) continue;
|
|
3403
|
+
const sectionTag = section.tagName.toLowerCase();
|
|
3404
|
+
if (sectionTag === "thead" || sectionTag === "tbody" || sectionTag === "tfoot") {
|
|
3405
|
+
const isHeader = sectionTag === "thead";
|
|
3406
|
+
const sectionChildren = section.childNodes;
|
|
3407
|
+
for (let j = 0; j < sectionChildren.length; j++) {
|
|
3408
|
+
const row = sectionChildren[j];
|
|
3409
|
+
if (!row || !isElement(row) || row.tagName.toLowerCase() !== "tr") continue;
|
|
3410
|
+
processTableRow(row, rowIdx, isHeader);
|
|
3411
|
+
rowIdx++;
|
|
3412
|
+
}
|
|
3413
|
+
} else if (sectionTag === "tr") {
|
|
3414
|
+
processTableRow(section, rowIdx, false);
|
|
3415
|
+
rowIdx++;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
function processTableRow(tr, rowIdx, isHeader) {
|
|
3420
|
+
let colIdx = 0;
|
|
3421
|
+
const cells = tr.childNodes;
|
|
3422
|
+
for (let i = 0; i < cells.length; i++) {
|
|
3423
|
+
const cell = cells[i];
|
|
3424
|
+
if (!cell || !isElement(cell)) continue;
|
|
3425
|
+
const cellTag = cell.tagName.toLowerCase();
|
|
3426
|
+
if (cellTag !== "td" && cellTag !== "th") continue;
|
|
3427
|
+
const cellIsHeader = isHeader || cellTag === "th";
|
|
3428
|
+
const textAlign = cell.style?.textAlign || cell.style?.getPropertyValue?.("text-align");
|
|
3429
|
+
const colAlign = textAlign && (textAlign === "left" || textAlign === "center" || textAlign === "right") ? textAlign : void 0;
|
|
3430
|
+
const prevBlockAttrs = { ...currentBlockAttributes };
|
|
3431
|
+
currentBlockAttributes["table-row"] = rowIdx;
|
|
3432
|
+
currentBlockAttributes["table-col"] = colIdx;
|
|
3433
|
+
if (cellIsHeader) {
|
|
3434
|
+
currentBlockAttributes["table-header"] = true;
|
|
3435
|
+
}
|
|
3436
|
+
if (colAlign) {
|
|
3437
|
+
currentBlockAttributes["table-col-align"] = colAlign;
|
|
3438
|
+
}
|
|
3439
|
+
processChildren(cell);
|
|
3440
|
+
context.pushNewline();
|
|
3441
|
+
currentBlockAttributes = prevBlockAttrs;
|
|
3442
|
+
colIdx++;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
function processDefaultBlock(element) {
|
|
3446
|
+
const prevBlockAttrs = { ...currentBlockAttributes };
|
|
3447
|
+
const align = getAlignment(element);
|
|
3448
|
+
if (align) {
|
|
3449
|
+
currentBlockAttributes.align = align;
|
|
3450
|
+
}
|
|
3451
|
+
const children2 = element.childNodes;
|
|
3452
|
+
const firstChild = children2[0];
|
|
3453
|
+
const isBrOnlyParagraph = children2.length === 1 && firstChild !== void 0 && isElement(firstChild) && firstChild.tagName.toLowerCase() === "br";
|
|
3454
|
+
if (!isBrOnlyParagraph) {
|
|
3455
|
+
processChildren(element);
|
|
3456
|
+
}
|
|
3457
|
+
context.pushNewline();
|
|
3458
|
+
currentBlockAttributes = prevBlockAttrs;
|
|
3459
|
+
}
|
|
3460
|
+
function getAlignment(element) {
|
|
3461
|
+
const textAlign = element.style?.textAlign || element.style?.getPropertyValue?.("text-align");
|
|
3462
|
+
if (textAlign && CSS_ALIGN_TO_FORMAT[textAlign]) {
|
|
3463
|
+
return CSS_ALIGN_TO_FORMAT[textAlign];
|
|
3464
|
+
}
|
|
3465
|
+
return null;
|
|
3466
|
+
}
|
|
3467
|
+
const children = fragment.childNodes;
|
|
3468
|
+
for (let i = 0; i < children.length; i++) {
|
|
3469
|
+
const child = children[i];
|
|
3470
|
+
if (child) processNode(child);
|
|
3471
|
+
}
|
|
3472
|
+
flushText();
|
|
3473
|
+
if (delta.ops.length > 0) {
|
|
3474
|
+
const lastOp = delta.ops[delta.ops.length - 1];
|
|
3475
|
+
if (lastOp && "insert" in lastOp) {
|
|
3476
|
+
const lastInsert = lastOp.insert;
|
|
3477
|
+
if (typeof lastInsert === "string" && !lastInsert.endsWith("\n")) {
|
|
3478
|
+
delta.insert("\n");
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return delta;
|
|
3483
|
+
}
|
|
3484
|
+
function normalizeText(text, pendingText, atLineStart) {
|
|
3485
|
+
text = text.replace(/[\t\n\r]+/g, " ");
|
|
3486
|
+
text = text.replace(/ +/g, " ");
|
|
3487
|
+
if (atLineStart && pendingText === "" && text.startsWith(" ")) {
|
|
3488
|
+
text = text.slice(1);
|
|
3489
|
+
}
|
|
3490
|
+
return text;
|
|
3491
|
+
}
|
|
3492
|
+
function findTagHandler(handlers, element, tagName) {
|
|
3493
|
+
const className = element.getAttribute("class");
|
|
3494
|
+
if (className) {
|
|
3495
|
+
const classes = className.split(/\s+/);
|
|
3496
|
+
for (const cls of classes) {
|
|
3497
|
+
const key = `${tagName}.${cls}`;
|
|
3498
|
+
if (handlers[key]) {
|
|
3499
|
+
return handlers[key];
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
if (handlers[tagName]) {
|
|
3504
|
+
return handlers[tagName];
|
|
3505
|
+
}
|
|
3506
|
+
return null;
|
|
3507
|
+
}
|
|
3508
|
+
var DEFAULT_TAG_HANDLERS = {
|
|
3509
|
+
// Formula span
|
|
3510
|
+
"span.formula": (element, context) => {
|
|
3511
|
+
const formula = element.getAttribute("data-formula");
|
|
3512
|
+
if (formula) {
|
|
3513
|
+
context.pushEmbed({ formula });
|
|
3514
|
+
}
|
|
3515
|
+
},
|
|
3516
|
+
// Diagram (Mermaid) span — inline mode
|
|
3517
|
+
"span.diagram": (element, context) => {
|
|
3518
|
+
const diagram = element.getAttribute("data-diagram");
|
|
3519
|
+
if (diagram) {
|
|
3520
|
+
context.pushEmbed({ diagram });
|
|
3521
|
+
}
|
|
3522
|
+
},
|
|
3523
|
+
// Draw.io diagram span — file reference
|
|
3524
|
+
"span.drawio": (element, context) => {
|
|
3525
|
+
const src = element.getAttribute("data-drawio-src");
|
|
3526
|
+
if (src) {
|
|
3527
|
+
const attrs = {};
|
|
3528
|
+
const alt = element.getAttribute("data-alt");
|
|
3529
|
+
if (alt) attrs.alt = alt;
|
|
3530
|
+
context.pushEmbed({ drawio: src }, Object.keys(attrs).length > 0 ? attrs : void 0);
|
|
3531
|
+
}
|
|
3532
|
+
},
|
|
3533
|
+
// Footnote reference: <sup class="footnote-ref"><a href="#fn-{id}">{label}</a></sup>
|
|
3534
|
+
"sup.footnote-ref": (element, context) => {
|
|
3535
|
+
const children = element.childNodes;
|
|
3536
|
+
for (let i = 0; i < children.length; i++) {
|
|
3537
|
+
const child = children[i];
|
|
3538
|
+
if (child && isElement(child) && child.tagName.toLowerCase() === "a") {
|
|
3539
|
+
const href = child.getAttribute("href") || "";
|
|
3540
|
+
const match = href.match(/#fn-(.+)/);
|
|
3541
|
+
if (match?.[1]) {
|
|
3542
|
+
context.pushEmbed({ "footnote-ref": match[1] });
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
const id = element.getAttribute("id") || "";
|
|
3548
|
+
const refMatch = id.match(/^fnref-(.+)/);
|
|
3549
|
+
if (refMatch?.[1]) {
|
|
3550
|
+
context.pushEmbed({ "footnote-ref": refMatch[1] });
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
|
|
3555
|
+
// src/conversion/markdown/delta-to-markdown.ts
|
|
3556
|
+
import { Delta as Delta9, isInsert as isInsert3, isTextInsert as isTextInsert2, isEmbedInsert as isEmbedInsert3 } from "@scrider/delta";
|
|
3557
|
+
|
|
3558
|
+
// src/conversion/markdown/config.ts
|
|
3559
|
+
var MARKDOWN_ESCAPE_CHARS = /[\\`*_[\]<>#]/g;
|
|
3560
|
+
function escapeMarkdown(text) {
|
|
3561
|
+
return text.replace(MARKDOWN_ESCAPE_CHARS, "\\$&");
|
|
3562
|
+
}
|
|
3563
|
+
var INLINE_FORMAT_SYNTAX = {
|
|
3564
|
+
bold: { prefix: "**", suffix: "**" },
|
|
3565
|
+
italic: { prefix: "_", suffix: "_" },
|
|
3566
|
+
underline: { prefix: "<u>", suffix: "</u>" },
|
|
3567
|
+
strike: { prefix: "~~", suffix: "~~" },
|
|
3568
|
+
subscript: { prefix: "<sub>", suffix: "</sub>" },
|
|
3569
|
+
superscript: { prefix: "<sup>", suffix: "</sup>" },
|
|
3570
|
+
code: { prefix: "`", suffix: "`" },
|
|
3571
|
+
mark: { prefix: "<mark>", suffix: "</mark>" },
|
|
3572
|
+
kbd: { prefix: "<kbd>", suffix: "</kbd>" }
|
|
3573
|
+
};
|
|
3574
|
+
var MD_INLINE_FORMAT_ORDER = [
|
|
3575
|
+
"bold",
|
|
3576
|
+
"italic",
|
|
3577
|
+
"underline",
|
|
3578
|
+
"strike",
|
|
3579
|
+
"subscript",
|
|
3580
|
+
"superscript",
|
|
3581
|
+
"code",
|
|
3582
|
+
"mark",
|
|
3583
|
+
"kbd"
|
|
3584
|
+
];
|
|
3585
|
+
var BLOCK_FORMAT_PREFIX = {
|
|
3586
|
+
header: (value) => {
|
|
3587
|
+
const level = typeof value === "number" ? value : 1;
|
|
3588
|
+
return "#".repeat(Math.min(Math.max(level, 1), 6)) + " ";
|
|
3589
|
+
},
|
|
3590
|
+
blockquote: "> ",
|
|
3591
|
+
"code-block": ""
|
|
3592
|
+
// Handled separately with fences
|
|
3593
|
+
};
|
|
3594
|
+
var LIST_TYPE_PREFIX = {
|
|
3595
|
+
bullet: "- ",
|
|
3596
|
+
ordered: (index) => `${index + 1}. `,
|
|
3597
|
+
checked: "- [x] ",
|
|
3598
|
+
unchecked: "- [ ] "
|
|
3599
|
+
};
|
|
3600
|
+
function getIndentPrefix(level) {
|
|
3601
|
+
return " ".repeat(level);
|
|
3602
|
+
}
|
|
3603
|
+
function renderImage(src, alt, _title) {
|
|
3604
|
+
const altText = alt ?? "";
|
|
3605
|
+
return ``;
|
|
3606
|
+
}
|
|
3607
|
+
function renderLink(text, href, _title) {
|
|
3608
|
+
return `[${text}](${href})`;
|
|
3609
|
+
}
|
|
3610
|
+
function renderCodeBlock2(code, language) {
|
|
3611
|
+
const lang = language ?? "";
|
|
3612
|
+
return `\`\`\`${lang}
|
|
3613
|
+
${code}
|
|
3614
|
+
\`\`\``;
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
// src/conversion/markdown/delta-to-markdown.ts
|
|
3618
|
+
function deltaToMarkdown(delta, options = {}) {
|
|
3619
|
+
const {
|
|
3620
|
+
strict = false,
|
|
3621
|
+
preserveEmptyLines = false,
|
|
3622
|
+
mathSyntax = "dollar",
|
|
3623
|
+
mathBlock = true,
|
|
3624
|
+
embedRenderers = {},
|
|
3625
|
+
blockHandlers,
|
|
3626
|
+
prettyHtml = false,
|
|
3627
|
+
registry
|
|
3628
|
+
} = options;
|
|
3629
|
+
const useLatexDelimiters = mathSyntax === "latex";
|
|
3630
|
+
const lines = splitIntoLines2(delta.ops);
|
|
3631
|
+
let orderedListIndex = 0;
|
|
3632
|
+
let lastListType = null;
|
|
3633
|
+
let lastIndent = 0;
|
|
3634
|
+
let lastWasBlockquote = false;
|
|
3635
|
+
const result = [];
|
|
3636
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3637
|
+
const line = lines[i];
|
|
3638
|
+
if (!line) continue;
|
|
3639
|
+
const attrs = line.attributes;
|
|
3640
|
+
const isBlockquote = !!attrs.blockquote;
|
|
3641
|
+
if (typeof attrs["table-row"] === "number" && typeof attrs["table-col"] === "number") {
|
|
3642
|
+
const tableLines = collectTableLines2(lines, i);
|
|
3643
|
+
result.push(renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters, registry));
|
|
3644
|
+
result.push("");
|
|
3645
|
+
i += tableLines.length - 1;
|
|
3646
|
+
lastListType = null;
|
|
3647
|
+
orderedListIndex = 0;
|
|
3648
|
+
lastWasBlockquote = false;
|
|
3649
|
+
continue;
|
|
3650
|
+
}
|
|
3651
|
+
if (attrs["code-block"]) {
|
|
3652
|
+
if (lastWasBlockquote) {
|
|
3653
|
+
result.push("");
|
|
3654
|
+
lastWasBlockquote = false;
|
|
3655
|
+
}
|
|
3656
|
+
const codeLines = collectCodeBlock(lines, i);
|
|
3657
|
+
const language = getCodeBlockLanguage2(attrs);
|
|
3658
|
+
const code = codeLines.map(
|
|
3659
|
+
(l) => renderLineContent2(l.ops, embedRenderers, true, false, blockHandlers, false, registry)
|
|
3660
|
+
).join("\n");
|
|
3661
|
+
if (language === "math") {
|
|
3662
|
+
if (mathBlock === false) {
|
|
3663
|
+
result.push("");
|
|
3664
|
+
result.push(`$${code}$`);
|
|
3665
|
+
result.push("");
|
|
3666
|
+
} else if (useLatexDelimiters) {
|
|
3667
|
+
result.push(`\\[
|
|
3668
|
+
${code}
|
|
3669
|
+
\\]`);
|
|
3670
|
+
} else {
|
|
3671
|
+
result.push(renderCodeBlock2(code, language));
|
|
3672
|
+
}
|
|
3673
|
+
} else {
|
|
3674
|
+
result.push(renderCodeBlock2(code, language));
|
|
3675
|
+
}
|
|
3676
|
+
i += codeLines.length - 1;
|
|
3677
|
+
lastListType = null;
|
|
3678
|
+
orderedListIndex = 0;
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
if (lastWasBlockquote && !isBlockquote) {
|
|
3682
|
+
result.push("");
|
|
3683
|
+
} else if (!lastWasBlockquote && isBlockquote && result.length > 0) {
|
|
3684
|
+
const lastLine = result[result.length - 1];
|
|
3685
|
+
if (lastLine !== void 0 && lastLine !== "") {
|
|
3686
|
+
result.push("");
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
const listType = typeof attrs.list === "string" ? attrs.list : void 0;
|
|
3690
|
+
const indent = typeof attrs.indent === "number" ? attrs.indent : 0;
|
|
3691
|
+
if (listType) {
|
|
3692
|
+
if (listType !== lastListType || indent !== lastIndent) {
|
|
3693
|
+
orderedListIndex = 0;
|
|
3694
|
+
}
|
|
3695
|
+
if (listType === "ordered") {
|
|
3696
|
+
orderedListIndex++;
|
|
3697
|
+
}
|
|
3698
|
+
} else {
|
|
3699
|
+
lastListType = null;
|
|
3700
|
+
orderedListIndex = 0;
|
|
3701
|
+
}
|
|
3702
|
+
const content = renderLineContent2(
|
|
3703
|
+
line.ops,
|
|
3704
|
+
embedRenderers,
|
|
3705
|
+
false,
|
|
3706
|
+
useLatexDelimiters,
|
|
3707
|
+
blockHandlers,
|
|
3708
|
+
prettyHtml,
|
|
3709
|
+
registry
|
|
3710
|
+
);
|
|
3711
|
+
if (!content && !hasBlockFormat(attrs)) {
|
|
3712
|
+
result.push(preserveEmptyLines ? "<br>" : "");
|
|
3713
|
+
lastWasBlockquote = false;
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
const markdown = renderBlockFormat(content, attrs, orderedListIndex, strict);
|
|
3717
|
+
result.push(markdown);
|
|
3718
|
+
if (/^<img\s/.test(content) && !hasBlockFormat(attrs)) {
|
|
3719
|
+
result.push("");
|
|
3720
|
+
}
|
|
3721
|
+
lastListType = listType ?? null;
|
|
3722
|
+
lastIndent = indent;
|
|
3723
|
+
lastWasBlockquote = isBlockquote;
|
|
3724
|
+
}
|
|
3725
|
+
return result.join("\n");
|
|
3726
|
+
}
|
|
3727
|
+
function hasBlockFormat(attrs) {
|
|
3728
|
+
return !!(attrs.header || attrs.list || attrs.blockquote || attrs["code-block"] || attrs.align || attrs.indent);
|
|
3729
|
+
}
|
|
3730
|
+
function splitIntoLines2(ops) {
|
|
3731
|
+
const lines = [];
|
|
3732
|
+
let currentOps = [];
|
|
3733
|
+
for (const op of ops) {
|
|
3734
|
+
if (!isInsert3(op)) continue;
|
|
3735
|
+
const opAttrs = op.attributes ?? {};
|
|
3736
|
+
if (isTextInsert2(op)) {
|
|
3737
|
+
const text = op.insert;
|
|
3738
|
+
const parts = text.split("\n");
|
|
3739
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3740
|
+
const part = parts[i];
|
|
3741
|
+
if (part === void 0) continue;
|
|
3742
|
+
if (part.length > 0) {
|
|
3743
|
+
const newOp = { insert: part };
|
|
3744
|
+
const inlineAttrs = {};
|
|
3745
|
+
for (const [key, value] of Object.entries(opAttrs)) {
|
|
3746
|
+
if (![
|
|
3747
|
+
"header",
|
|
3748
|
+
"blockquote",
|
|
3749
|
+
"code-block",
|
|
3750
|
+
"list",
|
|
3751
|
+
"indent",
|
|
3752
|
+
"align",
|
|
3753
|
+
"direction",
|
|
3754
|
+
"table-row",
|
|
3755
|
+
"table-col",
|
|
3756
|
+
"table-header",
|
|
3757
|
+
"table-col-align"
|
|
3758
|
+
].includes(key)) {
|
|
3759
|
+
inlineAttrs[key] = value;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
if (Object.keys(inlineAttrs).length > 0) {
|
|
3763
|
+
newOp.attributes = inlineAttrs;
|
|
3764
|
+
}
|
|
3765
|
+
currentOps.push(newOp);
|
|
3766
|
+
}
|
|
3767
|
+
if (i < parts.length - 1) {
|
|
3768
|
+
lines.push({
|
|
3769
|
+
ops: currentOps,
|
|
3770
|
+
attributes: opAttrs
|
|
3771
|
+
// Line attributes come from the \n op
|
|
3772
|
+
});
|
|
3773
|
+
currentOps = [];
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
} else {
|
|
3777
|
+
currentOps.push(op);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
if (currentOps.length > 0) {
|
|
3781
|
+
lines.push({ ops: currentOps, attributes: {} });
|
|
3782
|
+
}
|
|
3783
|
+
return lines;
|
|
3784
|
+
}
|
|
3785
|
+
function collectCodeBlock(lines, startIndex) {
|
|
3786
|
+
const codeLines = [];
|
|
3787
|
+
const startLine = lines[startIndex];
|
|
3788
|
+
if (!startLine) return codeLines;
|
|
3789
|
+
const startLang = getCodeBlockLanguage2(startLine.attributes);
|
|
3790
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
3791
|
+
const line = lines[i];
|
|
3792
|
+
if (!line || !line.attributes["code-block"]) break;
|
|
3793
|
+
const lang = getCodeBlockLanguage2(line.attributes);
|
|
3794
|
+
if (i > startIndex && lang !== startLang) break;
|
|
3795
|
+
codeLines.push(line);
|
|
3796
|
+
}
|
|
3797
|
+
return codeLines;
|
|
3798
|
+
}
|
|
3799
|
+
function collectTableLines2(lines, startIndex) {
|
|
3800
|
+
const result = [];
|
|
3801
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
3802
|
+
const line = lines[i];
|
|
3803
|
+
if (!line || typeof line.attributes["table-row"] !== "number" || typeof line.attributes["table-col"] !== "number") {
|
|
3804
|
+
break;
|
|
3805
|
+
}
|
|
3806
|
+
result.push(line);
|
|
3807
|
+
}
|
|
3808
|
+
return result;
|
|
3809
|
+
}
|
|
3810
|
+
function renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters = false, registry) {
|
|
3811
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3812
|
+
for (const line of tableLines) {
|
|
3813
|
+
const attrs = line.attributes;
|
|
3814
|
+
const rowIdx = attrs["table-row"];
|
|
3815
|
+
const colIdx = attrs["table-col"];
|
|
3816
|
+
if (!rows.has(rowIdx)) {
|
|
3817
|
+
rows.set(rowIdx, { isHeader: !!attrs["table-header"], cells: /* @__PURE__ */ new Map() });
|
|
3818
|
+
}
|
|
3819
|
+
const row = rows.get(rowIdx);
|
|
3820
|
+
if (attrs["table-header"]) row.isHeader = true;
|
|
3821
|
+
row.cells.set(colIdx, {
|
|
3822
|
+
ops: line.ops,
|
|
3823
|
+
colAlign: typeof attrs["table-col-align"] === "string" ? attrs["table-col-align"] : void 0
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
const sortedRows = [...rows.entries()].sort((a, b) => a[0] - b[0]);
|
|
3827
|
+
let maxCol = 0;
|
|
3828
|
+
for (const [, row] of sortedRows) {
|
|
3829
|
+
for (const colIdx of row.cells.keys()) {
|
|
3830
|
+
if (colIdx > maxCol) maxCol = colIdx;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
const headerRows = sortedRows.filter(([, r]) => r.isHeader);
|
|
3834
|
+
const bodyRows = sortedRows.filter(([, r]) => !r.isHeader);
|
|
3835
|
+
const colAligns = [];
|
|
3836
|
+
const alignSource = headerRows.length > 0 ? headerRows : bodyRows;
|
|
3837
|
+
if (alignSource.length > 0) {
|
|
3838
|
+
const firstRow = alignSource[0];
|
|
3839
|
+
for (let col = 0; col <= maxCol; col++) {
|
|
3840
|
+
const cell = firstRow[1].cells.get(col);
|
|
3841
|
+
colAligns.push(cell?.colAlign);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
const mdLines = [];
|
|
3845
|
+
if (headerRows.length > 0) {
|
|
3846
|
+
for (const [, row] of headerRows) {
|
|
3847
|
+
mdLines.push(renderMdRow(row.cells, maxCol, embedRenderers, useLatexDelimiters, registry));
|
|
3848
|
+
}
|
|
3849
|
+
mdLines.push(renderMdSeparator(maxCol, colAligns));
|
|
3850
|
+
} else {
|
|
3851
|
+
const emptyRow = /* @__PURE__ */ new Map();
|
|
3852
|
+
for (let col = 0; col <= maxCol; col++) {
|
|
3853
|
+
emptyRow.set(col, { ops: [] });
|
|
3854
|
+
}
|
|
3855
|
+
mdLines.push(renderMdRow(emptyRow, maxCol, embedRenderers, useLatexDelimiters, registry));
|
|
3856
|
+
mdLines.push(renderMdSeparator(maxCol, colAligns));
|
|
3857
|
+
}
|
|
3858
|
+
for (const [, row] of bodyRows) {
|
|
3859
|
+
mdLines.push(renderMdRow(row.cells, maxCol, embedRenderers, useLatexDelimiters, registry));
|
|
3860
|
+
}
|
|
3861
|
+
return mdLines.join("\n");
|
|
3862
|
+
}
|
|
3863
|
+
function renderMdRow(cells, maxCol, embedRenderers, useLatexDelimiters = false, registry) {
|
|
3864
|
+
const parts = [];
|
|
3865
|
+
for (let col = 0; col <= maxCol; col++) {
|
|
3866
|
+
const cell = cells.get(col);
|
|
3867
|
+
const content = cell ? renderLineContent2(
|
|
3868
|
+
cell.ops,
|
|
3869
|
+
embedRenderers,
|
|
3870
|
+
false,
|
|
3871
|
+
useLatexDelimiters,
|
|
3872
|
+
void 0,
|
|
3873
|
+
false,
|
|
3874
|
+
registry
|
|
3875
|
+
) : "";
|
|
3876
|
+
parts.push(content.replace(/\|/g, "\\|"));
|
|
3877
|
+
}
|
|
3878
|
+
return "| " + parts.join(" | ") + " |";
|
|
3879
|
+
}
|
|
3880
|
+
function renderMdSeparator(maxCol, colAligns) {
|
|
3881
|
+
const parts = [];
|
|
3882
|
+
for (let col = 0; col <= maxCol; col++) {
|
|
3883
|
+
const align = colAligns[col];
|
|
3884
|
+
if (align === "center") {
|
|
3885
|
+
parts.push(":---:");
|
|
3886
|
+
} else if (align === "right") {
|
|
3887
|
+
parts.push("---:");
|
|
3888
|
+
} else if (align === "left") {
|
|
3889
|
+
parts.push(":---");
|
|
3890
|
+
} else {
|
|
3891
|
+
parts.push("---");
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
return "| " + parts.join(" | ") + " |";
|
|
3895
|
+
}
|
|
3896
|
+
function getCodeBlockLanguage2(attributes) {
|
|
3897
|
+
const codeBlock = attributes["code-block"];
|
|
3898
|
+
if (typeof codeBlock === "string" && codeBlock !== "true") {
|
|
3899
|
+
return codeBlock;
|
|
3900
|
+
}
|
|
3901
|
+
return void 0;
|
|
3902
|
+
}
|
|
3903
|
+
function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry) {
|
|
3904
|
+
let result = "";
|
|
3905
|
+
for (const op of ops) {
|
|
3906
|
+
const attrs = op.attributes;
|
|
3907
|
+
if (isTextInsert2(op)) {
|
|
3908
|
+
const text = op.insert;
|
|
3909
|
+
if (inCodeBlock) {
|
|
3910
|
+
result += text;
|
|
3911
|
+
} else {
|
|
3912
|
+
result += renderInlineText2(text, attrs);
|
|
3913
|
+
}
|
|
3914
|
+
} else if (isEmbedInsert3(op)) {
|
|
3915
|
+
const embed = op.insert;
|
|
3916
|
+
result += renderEmbed2(
|
|
3917
|
+
embed,
|
|
3918
|
+
attrs,
|
|
3919
|
+
embedRenderers,
|
|
3920
|
+
useLatexDelimiters,
|
|
3921
|
+
blockHandlers,
|
|
3922
|
+
prettyHtml,
|
|
3923
|
+
registry
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
return result;
|
|
3928
|
+
}
|
|
3929
|
+
function renderInlineText2(text, attributes) {
|
|
3930
|
+
if (!attributes || Object.keys(attributes).length === 0) {
|
|
3931
|
+
return escapeMarkdown(text);
|
|
3932
|
+
}
|
|
3933
|
+
const link = typeof attributes.link === "string" ? attributes.link : void 0;
|
|
3934
|
+
let result = attributes.code ? text : escapeMarkdown(text);
|
|
3935
|
+
for (let i = MD_INLINE_FORMAT_ORDER.length - 1; i >= 0; i--) {
|
|
3936
|
+
const format = MD_INLINE_FORMAT_ORDER[i];
|
|
3937
|
+
if (!format || !attributes[format]) continue;
|
|
3938
|
+
const syntax = INLINE_FORMAT_SYNTAX[format];
|
|
3939
|
+
if (syntax) {
|
|
3940
|
+
result = `${syntax.prefix}${result}${syntax.suffix}`;
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
if (link) {
|
|
3944
|
+
result = renderLink(result, link);
|
|
3945
|
+
}
|
|
3946
|
+
if (typeof attributes.color === "string") {
|
|
3947
|
+
result = `<span style="color: ${attributes.color}">${result}</span>`;
|
|
3948
|
+
}
|
|
3949
|
+
if (typeof attributes.background === "string") {
|
|
3950
|
+
result = `<span style="background-color: ${attributes.background}">${result}</span>`;
|
|
3951
|
+
}
|
|
3952
|
+
return result;
|
|
3953
|
+
}
|
|
3954
|
+
function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry) {
|
|
3955
|
+
const entries = Object.entries(embed);
|
|
3956
|
+
if (entries.length === 0) return "";
|
|
3957
|
+
const firstEntry = entries[0];
|
|
3958
|
+
if (!firstEntry) return "";
|
|
3959
|
+
const embedType = firstEntry[0];
|
|
3960
|
+
const embedValue = firstEntry[1];
|
|
3961
|
+
if (embedType === "block" && blockHandlers) {
|
|
3962
|
+
const blockData = embedValue;
|
|
3963
|
+
if (blockData && typeof blockData.type === "string") {
|
|
3964
|
+
const handler = blockHandlers.get(blockData.type);
|
|
3965
|
+
if (handler) {
|
|
3966
|
+
const opAttrs = attributes ? { opAttributes: attributes } : {};
|
|
3967
|
+
if (handler.toMarkdown) {
|
|
3968
|
+
const mdContext = {
|
|
3969
|
+
registry: void 0,
|
|
3970
|
+
renderDelta: (ops) => deltaToMarkdown(new Delta9(ops), { blockHandlers }),
|
|
3971
|
+
...opAttrs
|
|
3972
|
+
};
|
|
3973
|
+
const md = handler.toMarkdown(blockData, mdContext);
|
|
3974
|
+
if (md !== null) return md;
|
|
3975
|
+
}
|
|
3976
|
+
const htmlContext = {
|
|
3977
|
+
registry: void 0,
|
|
3978
|
+
...prettyHtml ? { options: { pretty: true } } : {},
|
|
3979
|
+
renderDelta: (ops) => deltaToHtml(new Delta9(ops), { blockHandlers, pretty: prettyHtml }),
|
|
3980
|
+
...opAttrs
|
|
3981
|
+
};
|
|
3982
|
+
return "\n" + handler.toHtml(blockData, htmlContext) + "\n";
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
return "";
|
|
3986
|
+
}
|
|
3987
|
+
const customRenderer = customRenderers[embedType];
|
|
3988
|
+
if (customRenderer) {
|
|
3989
|
+
return customRenderer(embedValue, attributes);
|
|
3990
|
+
}
|
|
3991
|
+
if (registry) {
|
|
3992
|
+
const format = registry.get(embedType);
|
|
3993
|
+
if (format) {
|
|
3994
|
+
if (format.toMarkdown) {
|
|
3995
|
+
const md = format.toMarkdown(embedValue, attributes);
|
|
3996
|
+
if (md !== null) return md;
|
|
3997
|
+
}
|
|
3998
|
+
if (format.render) {
|
|
3999
|
+
return format.render(embedValue, attributes);
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
if (embedType === "image") {
|
|
4004
|
+
const src = typeof embedValue === "string" ? embedValue : "";
|
|
4005
|
+
const alt = typeof attributes?.alt === "string" ? attributes.alt : void 0;
|
|
4006
|
+
const hasFloat = attributes?.float != null && typeof attributes.float === "string" && attributes.float !== "none";
|
|
4007
|
+
const hasWidth = attributes?.width != null;
|
|
4008
|
+
const hasHeight = attributes?.height != null;
|
|
4009
|
+
if (hasFloat || hasWidth || hasHeight) {
|
|
4010
|
+
const altAttr = alt ? ` alt="${escapeHtml(alt)}"` : "";
|
|
4011
|
+
const floatAttr = hasFloat ? ` data-float="${escapeHtml(String(attributes.float))}"` : "";
|
|
4012
|
+
const widthAttr = hasWidth ? ` width="${escapeHtml(String(attributes.width))}"` : "";
|
|
4013
|
+
const heightAttr = hasHeight ? ` height="${escapeHtml(String(attributes.height))}"` : "";
|
|
4014
|
+
return `<img src="${escapeHtml(src)}"${altAttr}${floatAttr}${widthAttr}${heightAttr}>`;
|
|
4015
|
+
}
|
|
4016
|
+
return renderImage(src, alt);
|
|
4017
|
+
}
|
|
4018
|
+
if (embedType === "video") {
|
|
4019
|
+
const src = typeof embedValue === "string" ? embedValue : "";
|
|
4020
|
+
const hasFloat = attributes?.float != null && typeof attributes.float === "string" && attributes.float !== "none";
|
|
4021
|
+
const hasWidth = attributes?.width != null;
|
|
4022
|
+
const hasHeight = attributes?.height != null;
|
|
4023
|
+
if (hasFloat || hasWidth || hasHeight) {
|
|
4024
|
+
const floatAttr = hasFloat ? ` data-float="${escapeHtml(String(attributes.float))}"` : "";
|
|
4025
|
+
const styles = [];
|
|
4026
|
+
if (hasWidth) {
|
|
4027
|
+
const w = typeof attributes.width === "string" || typeof attributes.width === "number" ? String(attributes.width) : "";
|
|
4028
|
+
if (w && w !== "auto") styles.push(`width: ${/^\d+$/.test(w) ? w + "px" : w}`);
|
|
4029
|
+
}
|
|
4030
|
+
if (hasHeight) {
|
|
4031
|
+
const h = typeof attributes.height === "string" || typeof attributes.height === "number" ? String(attributes.height) : "";
|
|
4032
|
+
if (h && h !== "auto") styles.push(`height: ${/^\d+$/.test(h) ? h + "px" : h}`);
|
|
4033
|
+
}
|
|
4034
|
+
const styleAttr = styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
|
|
4035
|
+
const embedSrc = toVideoEmbedUrl(src);
|
|
4036
|
+
if (embedSrc) {
|
|
4037
|
+
return `<iframe src="${escapeHtml(embedSrc)}" frameborder="0" allowfullscreen${floatAttr}${styleAttr}></iframe>`;
|
|
4038
|
+
}
|
|
4039
|
+
return `<video src="${escapeHtml(src)}" controls${floatAttr}${styleAttr}></video>`;
|
|
4040
|
+
}
|
|
4041
|
+
return ``;
|
|
4042
|
+
}
|
|
4043
|
+
if (embedType === "formula") {
|
|
4044
|
+
const latex = typeof embedValue === "string" ? embedValue : "";
|
|
4045
|
+
return useLatexDelimiters ? `\\(${latex}\\)` : `$${latex}$`;
|
|
4046
|
+
}
|
|
4047
|
+
if (embedType === "diagram") {
|
|
4048
|
+
const source = typeof embedValue === "string" ? embedValue : "";
|
|
4049
|
+
const lang = source.trimStart().startsWith("@startuml") ? "plantuml" : "mermaid";
|
|
4050
|
+
return `
|
|
4051
|
+
\`\`\`${lang}
|
|
4052
|
+
${source}
|
|
4053
|
+
\`\`\`
|
|
4054
|
+
`;
|
|
4055
|
+
}
|
|
4056
|
+
if (embedType === "drawio") {
|
|
4057
|
+
const src = typeof embedValue === "string" ? embedValue : "";
|
|
4058
|
+
const alt = typeof attributes?.alt === "string" ? attributes.alt : void 0;
|
|
4059
|
+
return renderImage(src, alt);
|
|
4060
|
+
}
|
|
4061
|
+
if (embedType === "footnote-ref") {
|
|
4062
|
+
const id = typeof embedValue === "string" ? embedValue : String(embedValue);
|
|
4063
|
+
return `[^${id}]`;
|
|
4064
|
+
}
|
|
4065
|
+
if (embedType === "divider") {
|
|
4066
|
+
return "\n---\n";
|
|
4067
|
+
}
|
|
4068
|
+
return "";
|
|
4069
|
+
}
|
|
4070
|
+
function renderBlockFormat(content, attributes, orderedIndex, _strict) {
|
|
4071
|
+
const indent = typeof attributes.indent === "number" ? attributes.indent : 0;
|
|
4072
|
+
const indentPrefix = getIndentPrefix(indent);
|
|
4073
|
+
const header = typeof attributes.header === "number" ? attributes.header : void 0;
|
|
4074
|
+
if (header) {
|
|
4075
|
+
const prefixFn = BLOCK_FORMAT_PREFIX.header;
|
|
4076
|
+
if (typeof prefixFn === "function") {
|
|
4077
|
+
let line = prefixFn(header) + content;
|
|
4078
|
+
const headerId = attributes["header-id"];
|
|
4079
|
+
if (typeof headerId === "string" && headerId.length > 0) {
|
|
4080
|
+
line += ` {#${headerId}}`;
|
|
4081
|
+
}
|
|
4082
|
+
return line;
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
if (attributes.blockquote) {
|
|
4086
|
+
return "> " + content;
|
|
4087
|
+
}
|
|
4088
|
+
const listType = typeof attributes.list === "string" ? attributes.list : void 0;
|
|
4089
|
+
if (listType) {
|
|
4090
|
+
const prefixDef = LIST_TYPE_PREFIX[listType];
|
|
4091
|
+
let prefix;
|
|
4092
|
+
if (typeof prefixDef === "function") {
|
|
4093
|
+
prefix = prefixDef(orderedIndex - 1);
|
|
4094
|
+
} else if (prefixDef) {
|
|
4095
|
+
prefix = prefixDef;
|
|
4096
|
+
} else {
|
|
4097
|
+
prefix = "- ";
|
|
4098
|
+
}
|
|
4099
|
+
return indentPrefix + prefix + content;
|
|
4100
|
+
}
|
|
4101
|
+
return content;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
// src/conversion/markdown/markdown-to-delta.ts
|
|
4105
|
+
import { Delta as Delta10 } from "@scrider/delta";
|
|
4106
|
+
var remarkParse = null;
|
|
4107
|
+
var remarkGfm = null;
|
|
4108
|
+
var remarkMath = null;
|
|
4109
|
+
var unified = null;
|
|
4110
|
+
function isRemarkAvailable() {
|
|
4111
|
+
try {
|
|
4112
|
+
__require.resolve("unified");
|
|
4113
|
+
__require.resolve("remark-parse");
|
|
4114
|
+
return true;
|
|
4115
|
+
} catch {
|
|
4116
|
+
return false;
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
async function loadRemark() {
|
|
4120
|
+
if (unified) return;
|
|
4121
|
+
try {
|
|
4122
|
+
const [unifiedMod, remarkParseMod, remarkGfmMod] = await Promise.all([
|
|
4123
|
+
import("unified"),
|
|
4124
|
+
import("remark-parse"),
|
|
4125
|
+
import("remark-gfm")
|
|
4126
|
+
]);
|
|
4127
|
+
unified = unifiedMod.unified;
|
|
4128
|
+
remarkParse = remarkParseMod.default;
|
|
4129
|
+
remarkGfm = remarkGfmMod.default;
|
|
4130
|
+
} catch {
|
|
4131
|
+
throw new Error(
|
|
4132
|
+
"remark is not installed. Install with: pnpm add unified remark-parse remark-gfm"
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
try {
|
|
4136
|
+
const remarkMathMod = await import("remark-math");
|
|
4137
|
+
remarkMath = remarkMathMod.default;
|
|
4138
|
+
} catch {
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
function preprocessMarkdown(markdown, mathBlock) {
|
|
4142
|
+
markdown = markdown.replace(/\\\((.+?)\\\)/g, (_match, content) => `$${content}$`);
|
|
4143
|
+
markdown = markdown.replace(/\\\[([\s\S]+?)\\\]/g, (_match, content) => `$$${content}$$`);
|
|
4144
|
+
if (mathBlock) {
|
|
4145
|
+
markdown = markdown.replace(
|
|
4146
|
+
/^\$([^$\n]+)\$\s*$/gm,
|
|
4147
|
+
(_match, content) => `$$
|
|
4148
|
+
${content}
|
|
4149
|
+
$$`
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
return markdown.replace(/^<br\s*\/?>$/gim, "\n<!--empty-line-->\n");
|
|
4153
|
+
}
|
|
4154
|
+
async function markdownToDelta(markdown, options = {}) {
|
|
4155
|
+
const {
|
|
4156
|
+
gfm = true,
|
|
4157
|
+
mathBlock = true,
|
|
4158
|
+
mermaidBlock = true,
|
|
4159
|
+
plantumlBlock = true,
|
|
4160
|
+
nodeHandlers = {}
|
|
4161
|
+
} = options;
|
|
4162
|
+
markdown = preprocessMarkdown(markdown, mathBlock);
|
|
4163
|
+
await loadRemark();
|
|
4164
|
+
if (!unified || !remarkParse) {
|
|
4165
|
+
throw new Error("Failed to load remark");
|
|
4166
|
+
}
|
|
4167
|
+
let processor = unified().use(remarkParse);
|
|
4168
|
+
if (gfm && remarkGfm) {
|
|
4169
|
+
processor = processor.use(remarkGfm);
|
|
4170
|
+
}
|
|
4171
|
+
if (remarkMath) {
|
|
4172
|
+
processor = processor.use(remarkMath);
|
|
4173
|
+
}
|
|
4174
|
+
const tree = processor.parse(markdown);
|
|
4175
|
+
return astToDelta(
|
|
4176
|
+
tree,
|
|
4177
|
+
nodeHandlers,
|
|
4178
|
+
mathBlock,
|
|
4179
|
+
mermaidBlock,
|
|
4180
|
+
plantumlBlock,
|
|
4181
|
+
options.blockHandlers
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
function markdownToDeltaSync(markdown, options = {}) {
|
|
4185
|
+
const {
|
|
4186
|
+
gfm = true,
|
|
4187
|
+
mathBlock = true,
|
|
4188
|
+
mermaidBlock = true,
|
|
4189
|
+
plantumlBlock = true,
|
|
4190
|
+
nodeHandlers = {}
|
|
4191
|
+
} = options;
|
|
4192
|
+
markdown = preprocessMarkdown(markdown, mathBlock);
|
|
4193
|
+
if (!unified || !remarkParse) {
|
|
4194
|
+
try {
|
|
4195
|
+
const unifiedMod = __require("unified");
|
|
4196
|
+
const remarkParseMod = __require("remark-parse");
|
|
4197
|
+
const remarkGfmMod = __require("remark-gfm");
|
|
4198
|
+
unified = unifiedMod.unified;
|
|
4199
|
+
remarkParse = remarkParseMod.default;
|
|
4200
|
+
remarkGfm = remarkGfmMod.default;
|
|
4201
|
+
} catch {
|
|
4202
|
+
throw new Error(
|
|
4203
|
+
"remark is not installed. Install with: pnpm add unified remark-parse remark-gfm"
|
|
4204
|
+
);
|
|
4205
|
+
}
|
|
4206
|
+
if (!remarkMath) {
|
|
4207
|
+
try {
|
|
4208
|
+
const remarkMathMod = __require("remark-math");
|
|
4209
|
+
remarkMath = remarkMathMod.default;
|
|
4210
|
+
} catch {
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
let processor = unified().use(remarkParse);
|
|
4215
|
+
if (gfm && remarkGfm) {
|
|
4216
|
+
processor = processor.use(remarkGfm);
|
|
4217
|
+
}
|
|
4218
|
+
if (remarkMath) {
|
|
4219
|
+
processor = processor.use(remarkMath);
|
|
4220
|
+
}
|
|
4221
|
+
const tree = processor.parse(markdown);
|
|
4222
|
+
return astToDelta(
|
|
4223
|
+
tree,
|
|
4224
|
+
nodeHandlers,
|
|
4225
|
+
mathBlock,
|
|
4226
|
+
mermaidBlock,
|
|
4227
|
+
plantumlBlock,
|
|
4228
|
+
options.blockHandlers
|
|
4229
|
+
);
|
|
4230
|
+
}
|
|
4231
|
+
function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock, blockHandlers) {
|
|
4232
|
+
const delta = new Delta10();
|
|
4233
|
+
let currentInlineAttrs = {};
|
|
4234
|
+
let pendingText = "";
|
|
4235
|
+
const spanAttrStack = [];
|
|
4236
|
+
const footnoteDefinitions = /* @__PURE__ */ new Map();
|
|
4237
|
+
const context = {
|
|
4238
|
+
delta,
|
|
4239
|
+
pushText(text, attrs) {
|
|
4240
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
4241
|
+
if (pendingText) {
|
|
4242
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4243
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4244
|
+
} else {
|
|
4245
|
+
delta.insert(pendingText);
|
|
4246
|
+
}
|
|
4247
|
+
pendingText = "";
|
|
4248
|
+
}
|
|
4249
|
+
delta.insert(text, { ...currentInlineAttrs, ...attrs });
|
|
4250
|
+
} else {
|
|
4251
|
+
pendingText += text;
|
|
4252
|
+
}
|
|
4253
|
+
},
|
|
4254
|
+
pushEmbed(embed, attrs) {
|
|
4255
|
+
if (pendingText) {
|
|
4256
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4257
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4258
|
+
} else {
|
|
4259
|
+
delta.insert(pendingText);
|
|
4260
|
+
}
|
|
4261
|
+
pendingText = "";
|
|
4262
|
+
}
|
|
4263
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
4264
|
+
delta.insert(embed, attrs);
|
|
4265
|
+
} else {
|
|
4266
|
+
delta.insert(embed);
|
|
4267
|
+
}
|
|
4268
|
+
},
|
|
4269
|
+
pushNewline(attrs) {
|
|
4270
|
+
if (pendingText) {
|
|
4271
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4272
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4273
|
+
} else {
|
|
4274
|
+
delta.insert(pendingText);
|
|
4275
|
+
}
|
|
4276
|
+
pendingText = "";
|
|
4277
|
+
}
|
|
4278
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
4279
|
+
delta.insert("\n", attrs);
|
|
4280
|
+
} else {
|
|
4281
|
+
delta.insert("\n");
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
};
|
|
4285
|
+
const HEADING_ID_RE = /\s*\{#([\w-]+)\}\s*$/;
|
|
4286
|
+
function extractHeadingId(heading) {
|
|
4287
|
+
const children = heading.children;
|
|
4288
|
+
if (!children || children.length === 0) return null;
|
|
4289
|
+
const lastChild = children[children.length - 1];
|
|
4290
|
+
if (!lastChild || lastChild.type !== "text" || !lastChild.value) return null;
|
|
4291
|
+
const match = lastChild.value.match(HEADING_ID_RE);
|
|
4292
|
+
if (!match) return null;
|
|
4293
|
+
lastChild.value = lastChild.value.replace(HEADING_ID_RE, "");
|
|
4294
|
+
return match[1];
|
|
4295
|
+
}
|
|
4296
|
+
const ALERT_TYPE_RE = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i;
|
|
4297
|
+
function extractAlertType(blockquote) {
|
|
4298
|
+
const bqChildren = blockquote.children;
|
|
4299
|
+
if (!bqChildren || bqChildren.length === 0) return null;
|
|
4300
|
+
const firstChild = bqChildren[0];
|
|
4301
|
+
if (!firstChild || firstChild.type !== "paragraph") return null;
|
|
4302
|
+
const paraChildren = firstChild.children;
|
|
4303
|
+
if (!paraChildren || paraChildren.length === 0) return null;
|
|
4304
|
+
const firstInline = paraChildren[0];
|
|
4305
|
+
if (!firstInline || firstInline.type !== "text" || !firstInline.value) return null;
|
|
4306
|
+
const firstLine = firstInline.value.split("\n")[0] ?? "";
|
|
4307
|
+
const match = ALERT_TYPE_RE.exec(firstLine.trim());
|
|
4308
|
+
if (!match || !match[1]) return null;
|
|
4309
|
+
return match[1].toLowerCase();
|
|
4310
|
+
}
|
|
4311
|
+
function processAlertBlockquote(blockquote, alertType) {
|
|
4312
|
+
const children = [...blockquote.children || []];
|
|
4313
|
+
const first = children[0];
|
|
4314
|
+
if (first && first.type === "paragraph" && first.children) {
|
|
4315
|
+
const firstPara = { ...first, children: [...first.children] };
|
|
4316
|
+
const firstInline = firstPara.children[0];
|
|
4317
|
+
if (firstInline && firstInline.type === "text" && firstInline.value) {
|
|
4318
|
+
const lines = firstInline.value.split("\n");
|
|
4319
|
+
lines.shift();
|
|
4320
|
+
const remaining = lines.join("\n");
|
|
4321
|
+
if (remaining.trim().length > 0) {
|
|
4322
|
+
firstPara.children[0] = { ...firstInline, value: remaining };
|
|
4323
|
+
} else {
|
|
4324
|
+
firstPara.children.shift();
|
|
4325
|
+
}
|
|
4326
|
+
if (firstPara.children.length === 0) {
|
|
4327
|
+
children.shift();
|
|
4328
|
+
} else {
|
|
4329
|
+
children[0] = firstPara;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
const syntheticRoot = {
|
|
4334
|
+
type: "root",
|
|
4335
|
+
children
|
|
4336
|
+
};
|
|
4337
|
+
const contentDelta = astToDelta(
|
|
4338
|
+
syntheticRoot,
|
|
4339
|
+
customHandlers,
|
|
4340
|
+
mathBlock,
|
|
4341
|
+
mermaidBlock,
|
|
4342
|
+
plantumlBlock,
|
|
4343
|
+
blockHandlers
|
|
4344
|
+
);
|
|
4345
|
+
context.pushEmbed({
|
|
4346
|
+
block: {
|
|
4347
|
+
type: "alert",
|
|
4348
|
+
alertType,
|
|
4349
|
+
content: { ops: contentDelta.ops }
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
4352
|
+
context.pushNewline();
|
|
4353
|
+
}
|
|
4354
|
+
function processNode(node, blockAttrs = {}) {
|
|
4355
|
+
const customHandler = customHandlers[node.type];
|
|
4356
|
+
if (customHandler) {
|
|
4357
|
+
customHandler(node, context);
|
|
4358
|
+
return;
|
|
4359
|
+
}
|
|
4360
|
+
switch (node.type) {
|
|
4361
|
+
case "root":
|
|
4362
|
+
processChildren(node, blockAttrs);
|
|
4363
|
+
break;
|
|
4364
|
+
case "paragraph":
|
|
4365
|
+
processChildren(node, blockAttrs);
|
|
4366
|
+
context.pushNewline(blockAttrs);
|
|
4367
|
+
break;
|
|
4368
|
+
case "heading": {
|
|
4369
|
+
const headerId = extractHeadingId(node);
|
|
4370
|
+
processChildren(node, {});
|
|
4371
|
+
const headerAttrs = { header: node.depth ?? 1 };
|
|
4372
|
+
if (headerId) {
|
|
4373
|
+
headerAttrs["header-id"] = headerId;
|
|
4374
|
+
}
|
|
4375
|
+
context.pushNewline(headerAttrs);
|
|
4376
|
+
break;
|
|
4377
|
+
}
|
|
4378
|
+
case "blockquote": {
|
|
4379
|
+
const alertType = extractAlertType(node);
|
|
4380
|
+
if (alertType) {
|
|
4381
|
+
processAlertBlockquote(node, alertType);
|
|
4382
|
+
} else {
|
|
4383
|
+
processChildren(node, { blockquote: true });
|
|
4384
|
+
}
|
|
4385
|
+
break;
|
|
4386
|
+
}
|
|
4387
|
+
case "list":
|
|
4388
|
+
processList(node);
|
|
4389
|
+
break;
|
|
4390
|
+
case "listItem":
|
|
4391
|
+
break;
|
|
4392
|
+
case "code":
|
|
4393
|
+
processCodeBlock(node);
|
|
4394
|
+
break;
|
|
4395
|
+
case "table":
|
|
4396
|
+
processTable(node);
|
|
4397
|
+
break;
|
|
4398
|
+
case "tableRow":
|
|
4399
|
+
case "tableCell":
|
|
4400
|
+
break;
|
|
4401
|
+
case "thematicBreak":
|
|
4402
|
+
context.pushEmbed({ divider: true });
|
|
4403
|
+
context.pushNewline();
|
|
4404
|
+
break;
|
|
4405
|
+
case "text":
|
|
4406
|
+
context.pushText(node.value ?? "");
|
|
4407
|
+
break;
|
|
4408
|
+
case "strong":
|
|
4409
|
+
processInline(node, { bold: true });
|
|
4410
|
+
break;
|
|
4411
|
+
case "emphasis":
|
|
4412
|
+
processInline(node, { italic: true });
|
|
4413
|
+
break;
|
|
4414
|
+
case "delete":
|
|
4415
|
+
processInline(node, { strike: true });
|
|
4416
|
+
break;
|
|
4417
|
+
case "inlineCode":
|
|
4418
|
+
context.pushText(node.value ?? "", { code: true });
|
|
4419
|
+
break;
|
|
4420
|
+
case "link":
|
|
4421
|
+
processLink(node);
|
|
4422
|
+
break;
|
|
4423
|
+
case "image":
|
|
4424
|
+
processImage(node);
|
|
4425
|
+
break;
|
|
4426
|
+
case "inlineMath":
|
|
4427
|
+
context.pushEmbed({ formula: node.value ?? "" });
|
|
4428
|
+
break;
|
|
4429
|
+
case "math":
|
|
4430
|
+
processMathBlock(node);
|
|
4431
|
+
break;
|
|
4432
|
+
case "footnoteReference":
|
|
4433
|
+
context.pushEmbed({ "footnote-ref": node.identifier ?? "" });
|
|
4434
|
+
break;
|
|
4435
|
+
case "footnoteDefinition":
|
|
4436
|
+
footnoteDefinitions.set(node.identifier ?? "", node);
|
|
4437
|
+
break;
|
|
4438
|
+
case "break":
|
|
4439
|
+
context.pushNewline();
|
|
4440
|
+
break;
|
|
4441
|
+
case "html": {
|
|
4442
|
+
const htmlContent = node.value ?? "";
|
|
4443
|
+
if (isBlockLevelHtml(htmlContent)) {
|
|
4444
|
+
processBlockHtml(htmlContent);
|
|
4445
|
+
} else {
|
|
4446
|
+
processInlineHtml(node);
|
|
4447
|
+
}
|
|
4448
|
+
break;
|
|
4449
|
+
}
|
|
4450
|
+
default:
|
|
4451
|
+
if (node.children) {
|
|
4452
|
+
processChildren(node, blockAttrs);
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
function processChildren(node, blockAttrs) {
|
|
4457
|
+
if (!node.children) return;
|
|
4458
|
+
for (const child of node.children) {
|
|
4459
|
+
processNode(child, blockAttrs);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
function processInline(node, attrs) {
|
|
4463
|
+
const prevAttrs = { ...currentInlineAttrs };
|
|
4464
|
+
if (pendingText) {
|
|
4465
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4466
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4467
|
+
} else {
|
|
4468
|
+
delta.insert(pendingText);
|
|
4469
|
+
}
|
|
4470
|
+
pendingText = "";
|
|
4471
|
+
}
|
|
4472
|
+
currentInlineAttrs = { ...currentInlineAttrs, ...attrs };
|
|
4473
|
+
if (node.children) {
|
|
4474
|
+
for (const child of node.children) {
|
|
4475
|
+
processNode(child);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
if (pendingText) {
|
|
4479
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4480
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4481
|
+
} else {
|
|
4482
|
+
delta.insert(pendingText);
|
|
4483
|
+
}
|
|
4484
|
+
pendingText = "";
|
|
4485
|
+
}
|
|
4486
|
+
currentInlineAttrs = prevAttrs;
|
|
4487
|
+
}
|
|
4488
|
+
function processLink(node) {
|
|
4489
|
+
processInline(node, { link: node.url ?? "" });
|
|
4490
|
+
}
|
|
4491
|
+
function processImage(node) {
|
|
4492
|
+
const url = node.url ?? "";
|
|
4493
|
+
const alt = node.alt ?? "";
|
|
4494
|
+
if (alt.toLowerCase() === "video") {
|
|
4495
|
+
context.pushEmbed({ video: url });
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
const attrs = {};
|
|
4499
|
+
if (node.alt) attrs.alt = node.alt;
|
|
4500
|
+
if (url.toLowerCase().endsWith(".drawio")) {
|
|
4501
|
+
context.pushEmbed({ drawio: url }, Object.keys(attrs).length > 0 ? attrs : void 0);
|
|
4502
|
+
} else {
|
|
4503
|
+
context.pushEmbed({ image: url }, Object.keys(attrs).length > 0 ? attrs : void 0);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
function processList(node, indent = 0) {
|
|
4507
|
+
if (!node.children) return;
|
|
4508
|
+
const ordered = node.ordered ?? false;
|
|
4509
|
+
for (const item of node.children) {
|
|
4510
|
+
if (item.type !== "listItem") continue;
|
|
4511
|
+
let listType;
|
|
4512
|
+
if (item.checked === true) {
|
|
4513
|
+
listType = "checked";
|
|
4514
|
+
} else if (item.checked === false) {
|
|
4515
|
+
listType = "unchecked";
|
|
4516
|
+
} else {
|
|
4517
|
+
listType = ordered ? "ordered" : "bullet";
|
|
4518
|
+
}
|
|
4519
|
+
const blockAttrs = { list: listType };
|
|
4520
|
+
if (indent > 0) {
|
|
4521
|
+
blockAttrs.indent = indent;
|
|
4522
|
+
}
|
|
4523
|
+
if (item.children) {
|
|
4524
|
+
for (const child of item.children) {
|
|
4525
|
+
if (child.type === "list") {
|
|
4526
|
+
processList(child, indent + 1);
|
|
4527
|
+
} else if (child.type === "paragraph") {
|
|
4528
|
+
processChildren(child, {});
|
|
4529
|
+
context.pushNewline(blockAttrs);
|
|
4530
|
+
} else {
|
|
4531
|
+
processNode(child, blockAttrs);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
function processCodeBlock(node) {
|
|
4538
|
+
const code = node.value ?? "";
|
|
4539
|
+
const lang = node.lang;
|
|
4540
|
+
if (lang === "mermaid" && mermaidBlock === false) {
|
|
4541
|
+
context.pushEmbed({ diagram: code });
|
|
4542
|
+
context.pushNewline();
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
4545
|
+
if (lang === "plantuml" && plantumlBlock === false) {
|
|
4546
|
+
context.pushEmbed({ diagram: code });
|
|
4547
|
+
context.pushNewline();
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
const lines = code.split("\n");
|
|
4551
|
+
const codeBlockAttr = {
|
|
4552
|
+
"code-block": lang ?? true
|
|
4553
|
+
};
|
|
4554
|
+
for (const line of lines) {
|
|
4555
|
+
context.pushText(line);
|
|
4556
|
+
context.pushNewline(codeBlockAttr);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
function processMathBlock(node) {
|
|
4560
|
+
const value = node.value ?? "";
|
|
4561
|
+
if (mathBlock === false) {
|
|
4562
|
+
context.pushEmbed({ formula: value });
|
|
4563
|
+
context.pushNewline();
|
|
4564
|
+
} else {
|
|
4565
|
+
const lines = value.split("\n");
|
|
4566
|
+
const mathBlockAttr = { "code-block": "math" };
|
|
4567
|
+
for (const line of lines) {
|
|
4568
|
+
context.pushText(line);
|
|
4569
|
+
context.pushNewline(mathBlockAttr);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
function processTable(node) {
|
|
4574
|
+
if (!node.children) return;
|
|
4575
|
+
const aligns = node.align || [];
|
|
4576
|
+
for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
|
|
4577
|
+
const rowNode = node.children[rowIdx];
|
|
4578
|
+
if (!rowNode || rowNode.type !== "tableRow") continue;
|
|
4579
|
+
const isHeader = rowIdx === 0;
|
|
4580
|
+
if (!rowNode.children) continue;
|
|
4581
|
+
for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
|
|
4582
|
+
const cellNode = rowNode.children[colIdx];
|
|
4583
|
+
if (!cellNode || cellNode.type !== "tableCell") continue;
|
|
4584
|
+
const cellBlockAttrs = {
|
|
4585
|
+
"table-row": rowIdx,
|
|
4586
|
+
"table-col": colIdx
|
|
4587
|
+
};
|
|
4588
|
+
if (isHeader) {
|
|
4589
|
+
cellBlockAttrs["table-header"] = true;
|
|
4590
|
+
}
|
|
4591
|
+
const colAlign = aligns[colIdx];
|
|
4592
|
+
if (colAlign) {
|
|
4593
|
+
cellBlockAttrs["table-col-align"] = colAlign;
|
|
4594
|
+
}
|
|
4595
|
+
if (cellNode.children) {
|
|
4596
|
+
for (const child of cellNode.children) {
|
|
4597
|
+
processNode(child);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
context.pushNewline(cellBlockAttrs);
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
function isBlockLevelHtml(html) {
|
|
4605
|
+
return /^\s*<(div|table|section|article|aside|nav|header|footer|figure|pre|hr|ol|ul|dl|details|iframe|video)\b/i.test(
|
|
4606
|
+
html
|
|
4607
|
+
);
|
|
4608
|
+
}
|
|
4609
|
+
function processBlockHtml(html) {
|
|
4610
|
+
flushInlineText();
|
|
4611
|
+
const blockDelta = htmlToDelta(html, blockHandlers ? { blockHandlers } : {});
|
|
4612
|
+
for (const op of blockDelta.ops) {
|
|
4613
|
+
delta.push(op);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
function flushInlineText() {
|
|
4617
|
+
if (pendingText) {
|
|
4618
|
+
if (Object.keys(currentInlineAttrs).length > 0) {
|
|
4619
|
+
delta.insert(pendingText, currentInlineAttrs);
|
|
4620
|
+
} else {
|
|
4621
|
+
delta.insert(pendingText);
|
|
4622
|
+
}
|
|
4623
|
+
pendingText = "";
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
function tagToInlineAttr(tag) {
|
|
4627
|
+
switch (tag) {
|
|
4628
|
+
case "u":
|
|
4629
|
+
case "ins":
|
|
4630
|
+
return ["underline", true];
|
|
4631
|
+
case "sub":
|
|
4632
|
+
return ["subscript", true];
|
|
4633
|
+
case "sup":
|
|
4634
|
+
return ["superscript", true];
|
|
4635
|
+
case "mark":
|
|
4636
|
+
return ["mark", true];
|
|
4637
|
+
case "kbd":
|
|
4638
|
+
return ["kbd", true];
|
|
4639
|
+
case "b":
|
|
4640
|
+
case "strong":
|
|
4641
|
+
return ["bold", true];
|
|
4642
|
+
case "i":
|
|
4643
|
+
case "em":
|
|
4644
|
+
return ["italic", true];
|
|
4645
|
+
case "s":
|
|
4646
|
+
case "del":
|
|
4647
|
+
case "strike":
|
|
4648
|
+
return ["strike", true];
|
|
4649
|
+
default:
|
|
4650
|
+
return null;
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
function processInlineHtml(node) {
|
|
4654
|
+
const html = (node.value ?? "").trim();
|
|
4655
|
+
const openTagMatch = html.match(/^<(u|ins|sub|sup|mark|kbd|b|strong|i|em|s|del|strike)>$/i);
|
|
4656
|
+
if (openTagMatch) {
|
|
4657
|
+
const tag = openTagMatch[1]?.toLowerCase() ?? "";
|
|
4658
|
+
flushInlineText();
|
|
4659
|
+
const attr = tagToInlineAttr(tag);
|
|
4660
|
+
if (attr) {
|
|
4661
|
+
currentInlineAttrs = { ...currentInlineAttrs, [attr[0]]: attr[1] };
|
|
4662
|
+
}
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
const closeTagMatch = html.match(/^<\/(u|ins|sub|sup|mark|kbd|b|strong|i|em|s|del|strike)>$/i);
|
|
4666
|
+
if (closeTagMatch) {
|
|
4667
|
+
const tag = closeTagMatch[1]?.toLowerCase() ?? "";
|
|
4668
|
+
flushInlineText();
|
|
4669
|
+
const attr = tagToInlineAttr(tag);
|
|
4670
|
+
if (attr) {
|
|
4671
|
+
const newAttrs = { ...currentInlineAttrs };
|
|
4672
|
+
delete newAttrs[attr[0]];
|
|
4673
|
+
currentInlineAttrs = newAttrs;
|
|
4674
|
+
}
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4677
|
+
const spanMatch = html.match(/^<span\s+style="([^"]*)">/i);
|
|
4678
|
+
if (spanMatch) {
|
|
4679
|
+
flushInlineText();
|
|
4680
|
+
const style = spanMatch[1] ?? "";
|
|
4681
|
+
const addedKeys = [];
|
|
4682
|
+
const colorMatch = style.match(/(?:^|;)\s*color\s*:\s*([^;]+)/i);
|
|
4683
|
+
if (colorMatch) {
|
|
4684
|
+
currentInlineAttrs = { ...currentInlineAttrs, color: colorMatch[1].trim() };
|
|
4685
|
+
addedKeys.push("color");
|
|
4686
|
+
}
|
|
4687
|
+
const bgMatch = style.match(/(?:^|;)\s*background(?:-color)?\s*:\s*([^;]+)/i);
|
|
4688
|
+
if (bgMatch) {
|
|
4689
|
+
currentInlineAttrs = { ...currentInlineAttrs, background: bgMatch[1].trim() };
|
|
4690
|
+
addedKeys.push("background");
|
|
4691
|
+
}
|
|
4692
|
+
spanAttrStack.push(addedKeys);
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
4695
|
+
if (/^<\/span>$/i.test(html)) {
|
|
4696
|
+
flushInlineText();
|
|
4697
|
+
const keys = spanAttrStack.pop();
|
|
4698
|
+
if (keys) {
|
|
4699
|
+
const newAttrs = { ...currentInlineAttrs };
|
|
4700
|
+
for (const key of keys) delete newAttrs[key];
|
|
4701
|
+
currentInlineAttrs = newAttrs;
|
|
4702
|
+
}
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
const imgMatch = html.match(/^<img\s+([^>]*)\/?>$/i);
|
|
4706
|
+
if (imgMatch) {
|
|
4707
|
+
const attrStr = imgMatch[1] ?? "";
|
|
4708
|
+
const srcMatch = attrStr.match(/src="([^"]*)"/);
|
|
4709
|
+
if (srcMatch) {
|
|
4710
|
+
flushInlineText();
|
|
4711
|
+
const attrs = {};
|
|
4712
|
+
const altMatch = attrStr.match(/alt="([^"]*)"/);
|
|
4713
|
+
const floatMatch = attrStr.match(/data-float="([^"]*)"/);
|
|
4714
|
+
const widthMatch = attrStr.match(/width="([^"]*)"/);
|
|
4715
|
+
const heightMatch = attrStr.match(/height="([^"]*)"/);
|
|
4716
|
+
if (altMatch?.[1]) attrs.alt = altMatch[1];
|
|
4717
|
+
if (floatMatch?.[1]) attrs.float = floatMatch[1];
|
|
4718
|
+
if (widthMatch?.[1]) attrs.width = parseInt(widthMatch[1], 10) || widthMatch[1];
|
|
4719
|
+
if (heightMatch?.[1]) attrs.height = parseInt(heightMatch[1], 10) || heightMatch[1];
|
|
4720
|
+
context.pushEmbed(
|
|
4721
|
+
{ image: srcMatch[1] },
|
|
4722
|
+
Object.keys(attrs).length > 0 ? attrs : void 0
|
|
4723
|
+
);
|
|
4724
|
+
context.pushNewline();
|
|
4725
|
+
}
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
if (html.trim() === "<!--empty-line-->") {
|
|
4729
|
+
context.pushNewline();
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
if (/^<br\s*\/?>$/i.test(html)) {
|
|
4733
|
+
context.pushNewline();
|
|
4734
|
+
return;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
processNode(tree);
|
|
4738
|
+
if (footnoteDefinitions.size > 0) {
|
|
4739
|
+
const notes = {};
|
|
4740
|
+
for (const [id, defNode] of footnoteDefinitions) {
|
|
4741
|
+
const syntheticRoot = {
|
|
4742
|
+
type: "root",
|
|
4743
|
+
children: defNode.children || []
|
|
4744
|
+
};
|
|
4745
|
+
const defDelta = astToDelta(
|
|
4746
|
+
syntheticRoot,
|
|
4747
|
+
customHandlers,
|
|
4748
|
+
mathBlock,
|
|
4749
|
+
mermaidBlock,
|
|
4750
|
+
plantumlBlock,
|
|
4751
|
+
blockHandlers
|
|
4752
|
+
);
|
|
4753
|
+
notes[id] = { ops: defDelta.ops };
|
|
4754
|
+
}
|
|
4755
|
+
context.pushEmbed({ block: { type: "footnotes", notes } });
|
|
4756
|
+
context.pushNewline();
|
|
4757
|
+
}
|
|
4758
|
+
if (delta.ops.length > 0) {
|
|
4759
|
+
const lastOp = delta.ops[delta.ops.length - 1];
|
|
4760
|
+
if (lastOp && "insert" in lastOp) {
|
|
4761
|
+
const lastInsert = lastOp.insert;
|
|
4762
|
+
if (typeof lastInsert !== "string" || !lastInsert.endsWith("\n")) {
|
|
4763
|
+
delta.insert("\n");
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
return delta;
|
|
4768
|
+
}
|
|
4769
|
+
export {
|
|
4770
|
+
ALERT_TYPES,
|
|
4771
|
+
BOX_FLOAT_VALUES,
|
|
4772
|
+
BOX_OVERFLOW_VALUES,
|
|
4773
|
+
BlockHandlerRegistry,
|
|
4774
|
+
BrowserDOMAdapter,
|
|
4775
|
+
NODE_TYPE,
|
|
4776
|
+
NodeDOMAdapter,
|
|
4777
|
+
Registry,
|
|
4778
|
+
alertBlockHandler,
|
|
4779
|
+
alignFormat,
|
|
4780
|
+
backgroundFormat,
|
|
4781
|
+
blockFormat,
|
|
4782
|
+
blockquoteFormat,
|
|
4783
|
+
boldFormat,
|
|
4784
|
+
boxBlockHandler,
|
|
4785
|
+
browserAdapter,
|
|
4786
|
+
cloneDelta,
|
|
4787
|
+
codeBlockFormat,
|
|
4788
|
+
codeFormat,
|
|
4789
|
+
colorFormat,
|
|
4790
|
+
columnsBlockHandler,
|
|
4791
|
+
createDefaultBlockHandlers,
|
|
4792
|
+
createDefaultRegistry,
|
|
4793
|
+
defaultBlockFormats,
|
|
4794
|
+
defaultEmbedFormats,
|
|
4795
|
+
defaultFormats,
|
|
4796
|
+
defaultInlineFormats,
|
|
4797
|
+
deltaToHtml,
|
|
4798
|
+
deltaToMarkdown,
|
|
4799
|
+
dividerFormat,
|
|
4800
|
+
escapeHtml,
|
|
4801
|
+
extractBoxOpAttributes,
|
|
4802
|
+
footnoteRefFormat,
|
|
4803
|
+
footnotesBlockHandler,
|
|
4804
|
+
formulaFormat,
|
|
4805
|
+
getAdapter,
|
|
4806
|
+
getNamedColors,
|
|
4807
|
+
headerFormat,
|
|
4808
|
+
headerIdFormat,
|
|
4809
|
+
htmlToDelta,
|
|
4810
|
+
imageFormat,
|
|
4811
|
+
indentFormat,
|
|
4812
|
+
isAdapterAvailable,
|
|
4813
|
+
isElement,
|
|
4814
|
+
isRemarkAvailable,
|
|
4815
|
+
isTextNode,
|
|
4816
|
+
isValidColor,
|
|
4817
|
+
isValidHexColor,
|
|
4818
|
+
italicFormat,
|
|
4819
|
+
kbdFormat,
|
|
4820
|
+
linkFormat,
|
|
4821
|
+
listFormat,
|
|
4822
|
+
markFormat,
|
|
4823
|
+
markdownToDelta,
|
|
4824
|
+
markdownToDeltaSync,
|
|
4825
|
+
nodeAdapter,
|
|
4826
|
+
normalizeDelta,
|
|
4827
|
+
sanitizeDelta,
|
|
4828
|
+
slugify,
|
|
4829
|
+
slugifyWithDedup,
|
|
4830
|
+
strikeFormat,
|
|
4831
|
+
subscriptFormat,
|
|
4832
|
+
superscriptFormat,
|
|
4833
|
+
tableBlockHandler,
|
|
4834
|
+
tableColAlignFormat,
|
|
4835
|
+
tableColFormat,
|
|
4836
|
+
tableHeaderFormat,
|
|
4837
|
+
tableRowFormat,
|
|
4838
|
+
toHexColor,
|
|
4839
|
+
underlineFormat,
|
|
4840
|
+
unescapeHtml,
|
|
4841
|
+
validateDelta,
|
|
4842
|
+
videoFormat
|
|
4843
|
+
};
|
|
4844
|
+
//# sourceMappingURL=index.js.map
|