@lofcz/platejs-markdown 52.3.5
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/README.md +12 -0
- package/dist/index.d.ts +425 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1935 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1935 @@
|
|
|
1
|
+
import { ElementApi, KEYS, TextApi, bindFirst, createTSlatePlugin, getPluginKey, getPluginType, isUrl } from "platejs";
|
|
2
|
+
import "mdast-util-mdx";
|
|
3
|
+
import kebabCase from "lodash/kebabCase.js";
|
|
4
|
+
import remarkStringify from "remark-stringify";
|
|
5
|
+
import { unified } from "unified";
|
|
6
|
+
import remarkParse from "remark-parse";
|
|
7
|
+
import { marked } from "marked";
|
|
8
|
+
import baseRemarkMdx from "remark-mdx";
|
|
9
|
+
import { visit } from "unist-util-visit";
|
|
10
|
+
|
|
11
|
+
//#region src/lib/types.ts
|
|
12
|
+
const PLATE_TO_MDAST = {
|
|
13
|
+
a: "link",
|
|
14
|
+
blockquote: "blockquote",
|
|
15
|
+
bold: "strong",
|
|
16
|
+
callout: "callout",
|
|
17
|
+
code: "inlineCode",
|
|
18
|
+
code_block: "code",
|
|
19
|
+
code_line: "code_line",
|
|
20
|
+
column: "column",
|
|
21
|
+
column_group: "column_group",
|
|
22
|
+
comment: "comment",
|
|
23
|
+
date: "date",
|
|
24
|
+
equation: "math",
|
|
25
|
+
heading: "heading",
|
|
26
|
+
hr: "thematicBreak",
|
|
27
|
+
img: "image",
|
|
28
|
+
inline_equation: "inlineMath",
|
|
29
|
+
italic: "emphasis",
|
|
30
|
+
li: "listItem",
|
|
31
|
+
list: "list",
|
|
32
|
+
mention: "mention",
|
|
33
|
+
p: "paragraph",
|
|
34
|
+
strikethrough: "delete",
|
|
35
|
+
subscript: "sub",
|
|
36
|
+
suggestion: "suggestion",
|
|
37
|
+
superscript: "sup",
|
|
38
|
+
table: "table",
|
|
39
|
+
td: "tableCell",
|
|
40
|
+
text: "text",
|
|
41
|
+
th: "tableCell",
|
|
42
|
+
toc: "toc",
|
|
43
|
+
toggle: "toggle",
|
|
44
|
+
tr: "tableRow",
|
|
45
|
+
underline: "u"
|
|
46
|
+
};
|
|
47
|
+
const MDAST_TO_PLATE = {
|
|
48
|
+
backgroundColor: "backgroundColor",
|
|
49
|
+
blockquote: "blockquote",
|
|
50
|
+
break: "break",
|
|
51
|
+
code: "code_block",
|
|
52
|
+
color: "color",
|
|
53
|
+
definition: "definition",
|
|
54
|
+
del: "strikethrough",
|
|
55
|
+
delete: "strikethrough",
|
|
56
|
+
emphasis: "italic",
|
|
57
|
+
fontFamily: "fontFamily",
|
|
58
|
+
fontSize: "fontSize",
|
|
59
|
+
fontWeight: "fontWeight",
|
|
60
|
+
footnoteDefinition: "footnoteDefinition",
|
|
61
|
+
footnoteReference: "footnoteReference",
|
|
62
|
+
heading: "heading",
|
|
63
|
+
html: "html",
|
|
64
|
+
image: "img",
|
|
65
|
+
imageReference: "imageReference",
|
|
66
|
+
inlineCode: "code",
|
|
67
|
+
inlineMath: "inline_equation",
|
|
68
|
+
link: "a",
|
|
69
|
+
linkReference: "linkReference",
|
|
70
|
+
list: "list",
|
|
71
|
+
listItem: "li",
|
|
72
|
+
mark: "highlight",
|
|
73
|
+
math: "equation",
|
|
74
|
+
mdxFlowExpression: "mdxFlowExpression",
|
|
75
|
+
mdxjsEsm: "mdxjsEsm",
|
|
76
|
+
mdxJsxFlowElement: "mdxJsxFlowElement",
|
|
77
|
+
mdxJsxTextElement: "mdxJsxTextElement",
|
|
78
|
+
mdxTextExpression: "mdxTextExpression",
|
|
79
|
+
paragraph: "p",
|
|
80
|
+
strong: "bold",
|
|
81
|
+
sub: "subscript",
|
|
82
|
+
sup: "superscript",
|
|
83
|
+
table: "table",
|
|
84
|
+
tableCell: "td",
|
|
85
|
+
tableRow: "tr",
|
|
86
|
+
text: "text",
|
|
87
|
+
thematicBreak: "hr",
|
|
88
|
+
u: "underline",
|
|
89
|
+
yaml: "yaml"
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Get plate node type from mdast node type if the mdast is mdast only return
|
|
93
|
+
* the mdast type itself.
|
|
94
|
+
*/
|
|
95
|
+
const mdastToPlate = (editor, mdastType) => {
|
|
96
|
+
const plateKey = MDAST_TO_PLATE[mdastType];
|
|
97
|
+
return getPluginKey(editor, plateKey) ?? plateKey ?? mdastType;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Get mdast node type from plate element type if the plateType is plate only
|
|
101
|
+
* return the plateType itself.
|
|
102
|
+
*/
|
|
103
|
+
const plateToMdast = (plateType) => PLATE_TO_MDAST[plateType] ?? plateType;
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/lib/serializer/utils/getCustomMark.ts
|
|
107
|
+
const getCustomMark = (options) => {
|
|
108
|
+
if (!options?.rules) return [];
|
|
109
|
+
return Object.entries(options.rules).filter(([_, parser]) => parser?.mark).map(([key]) => key);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/lib/rules/utils/parseAttributes.ts
|
|
114
|
+
function parseAttributes(attributes) {
|
|
115
|
+
const props = {};
|
|
116
|
+
if (attributes && attributes.length > 0) attributes.forEach((attr) => {
|
|
117
|
+
if (attr.name && attr.value !== void 0) {
|
|
118
|
+
let value = attr.value;
|
|
119
|
+
try {
|
|
120
|
+
value = JSON.parse(attr.value);
|
|
121
|
+
} catch (_error) {
|
|
122
|
+
value = attr.value;
|
|
123
|
+
}
|
|
124
|
+
props[attr.name] = value;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return props;
|
|
128
|
+
}
|
|
129
|
+
function propsToAttributes(props) {
|
|
130
|
+
return Object.entries(props).map(([name, value]) => ({
|
|
131
|
+
name,
|
|
132
|
+
type: "mdxJsxAttribute",
|
|
133
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/lib/rules/columnRules.ts
|
|
139
|
+
const columnRules = {
|
|
140
|
+
column: {
|
|
141
|
+
deserialize: (mdastNode, deco, options) => {
|
|
142
|
+
const props = parseAttributes(mdastNode.attributes);
|
|
143
|
+
return {
|
|
144
|
+
children: convertChildrenDeserialize(mdastNode.children, { ...deco }, options),
|
|
145
|
+
type: getPluginType(options.editor, KEYS.column),
|
|
146
|
+
...props
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
serialize: (node, options) => {
|
|
150
|
+
const { id, children, type, ...rest } = node;
|
|
151
|
+
return {
|
|
152
|
+
attributes: propsToAttributes(rest),
|
|
153
|
+
children: convertNodesSerialize(children, options),
|
|
154
|
+
name: type,
|
|
155
|
+
type: "mdxJsxFlowElement"
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
column_group: {
|
|
160
|
+
deserialize: (mdastNode, deco, options) => {
|
|
161
|
+
const props = parseAttributes(mdastNode.attributes);
|
|
162
|
+
return {
|
|
163
|
+
children: convertChildrenDeserialize(mdastNode.children, { ...deco }, options),
|
|
164
|
+
type: getPluginType(options.editor, KEYS.columnGroup),
|
|
165
|
+
...props
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
serialize: (node, options) => {
|
|
169
|
+
const { id, children, type, ...rest } = node;
|
|
170
|
+
return {
|
|
171
|
+
attributes: propsToAttributes(rest),
|
|
172
|
+
children: convertNodesSerialize(children, options),
|
|
173
|
+
name: type,
|
|
174
|
+
type: "mdxJsxFlowElement"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/lib/rules/fontRules.ts
|
|
182
|
+
function createFontRule(propName) {
|
|
183
|
+
const styleName = kebabCase(propName);
|
|
184
|
+
return {
|
|
185
|
+
mark: true,
|
|
186
|
+
serialize: (slateNode) => ({
|
|
187
|
+
attributes: [{
|
|
188
|
+
name: "style",
|
|
189
|
+
type: "mdxJsxAttribute",
|
|
190
|
+
value: `${styleName}: ${slateNode[propName]};`
|
|
191
|
+
}],
|
|
192
|
+
children: [{
|
|
193
|
+
type: "text",
|
|
194
|
+
value: slateNode.text
|
|
195
|
+
}],
|
|
196
|
+
name: "span",
|
|
197
|
+
type: "mdxJsxTextElement"
|
|
198
|
+
})
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const fontRules = {
|
|
202
|
+
backgroundColor: createFontRule("backgroundColor"),
|
|
203
|
+
color: createFontRule("color"),
|
|
204
|
+
fontFamily: createFontRule("fontFamily"),
|
|
205
|
+
fontSize: createFontRule("fontSize"),
|
|
206
|
+
fontWeight: createFontRule("fontWeight"),
|
|
207
|
+
span: {
|
|
208
|
+
mark: true,
|
|
209
|
+
deserialize: (mdastNode, deco, options) => {
|
|
210
|
+
const fontFamily = getStyleValue(mdastNode, "font-family");
|
|
211
|
+
const fontSize = getStyleValue(mdastNode, "font-size");
|
|
212
|
+
const fontWeight = getStyleValue(mdastNode, "font-weight");
|
|
213
|
+
const color = getStyleValue(mdastNode, "color");
|
|
214
|
+
const backgroundColor = getStyleValue(mdastNode, "background-color");
|
|
215
|
+
return convertChildrenDeserialize(mdastNode.children, {
|
|
216
|
+
...deco,
|
|
217
|
+
backgroundColor,
|
|
218
|
+
color,
|
|
219
|
+
fontFamily,
|
|
220
|
+
fontSize,
|
|
221
|
+
fontWeight
|
|
222
|
+
}, options);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/lib/rules/mediaRules.ts
|
|
229
|
+
function createMediaRule() {
|
|
230
|
+
return {
|
|
231
|
+
deserialize: (node) => {
|
|
232
|
+
const { src, ...props } = parseAttributes(node.attributes);
|
|
233
|
+
return {
|
|
234
|
+
children: [{ text: "" }],
|
|
235
|
+
type: node.name,
|
|
236
|
+
url: src,
|
|
237
|
+
...props
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
serialize: (node, options) => {
|
|
241
|
+
const { id, children, type, url, ...rest } = node;
|
|
242
|
+
return {
|
|
243
|
+
attributes: propsToAttributes({
|
|
244
|
+
...rest,
|
|
245
|
+
src: url
|
|
246
|
+
}),
|
|
247
|
+
children: convertNodesSerialize(children, options),
|
|
248
|
+
name: type,
|
|
249
|
+
type: "mdxJsxFlowElement"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const mediaRules = {
|
|
255
|
+
audio: createMediaRule(),
|
|
256
|
+
file: createMediaRule(),
|
|
257
|
+
video: createMediaRule()
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/lib/serializer/utils/getMergedOptionsSerialize.ts
|
|
262
|
+
/**
|
|
263
|
+
* Merges Markdown configurations, following the principle that options take
|
|
264
|
+
* precedence
|
|
265
|
+
*
|
|
266
|
+
* @param editor Editor instance used to get plugin default configurations
|
|
267
|
+
* @param options User-provided options (higher priority)
|
|
268
|
+
* @returns The final merged configuration
|
|
269
|
+
*/
|
|
270
|
+
const getMergedOptionsSerialize = (editor, options) => {
|
|
271
|
+
const { allowedNodes: PluginAllowedNodes, allowNode: PluginAllowNode, disallowedNodes: PluginDisallowedNodes, plainMarks: PluginPlainMarks, remarkPlugins: PluginRemarkPlugins, remarkStringifyOptions: PluginRemarkStringifyOptions, rules: PluginRules } = editor.getOptions(MarkdownPlugin);
|
|
272
|
+
const mergedRules = {
|
|
273
|
+
...buildRules(editor),
|
|
274
|
+
...options?.rules ?? PluginRules
|
|
275
|
+
};
|
|
276
|
+
return {
|
|
277
|
+
allowedNodes: options?.allowedNodes ?? PluginAllowedNodes,
|
|
278
|
+
allowNode: options?.allowNode ?? PluginAllowNode,
|
|
279
|
+
disallowedNodes: options?.disallowedNodes ?? PluginDisallowedNodes,
|
|
280
|
+
editor,
|
|
281
|
+
plainMarks: options?.plainMarks ?? PluginPlainMarks,
|
|
282
|
+
preserveEmptyParagraphs: options?.preserveEmptyParagraphs,
|
|
283
|
+
remarkPlugins: options?.remarkPlugins ?? PluginRemarkPlugins ?? [],
|
|
284
|
+
remarkStringifyOptions: options?.remarkStringifyOptions ?? PluginRemarkStringifyOptions,
|
|
285
|
+
rules: mergedRules,
|
|
286
|
+
spread: options?.spread,
|
|
287
|
+
value: options?.value ?? editor.children,
|
|
288
|
+
withBlockId: options?.withBlockId ?? false
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/lib/serializer/utils/getSerializerByKey.ts
|
|
294
|
+
const getSerializerByKey = (key, options) => {
|
|
295
|
+
const nodes = options.rules;
|
|
296
|
+
const rules = buildRules(options.editor);
|
|
297
|
+
return nodes?.[key]?.serialize === void 0 ? rules[key]?.serialize : nodes?.[key]?.serialize;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/lib/serializer/utils/unreachable.ts
|
|
302
|
+
const unreachable = (value) => {
|
|
303
|
+
console.warn(`Unreachable code: ${JSON.stringify(value)}`);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/lib/serializer/convertTextsSerialize.ts
|
|
308
|
+
const basicMarkdownMarks = [
|
|
309
|
+
"italic",
|
|
310
|
+
"bold",
|
|
311
|
+
"strikethrough",
|
|
312
|
+
"code"
|
|
313
|
+
];
|
|
314
|
+
const convertTextsSerialize = (slateTexts, options, _key) => {
|
|
315
|
+
const customLeaf = getCustomMark(options);
|
|
316
|
+
const mdastTexts = [];
|
|
317
|
+
const starts = [];
|
|
318
|
+
let ends = [];
|
|
319
|
+
let textTemp = "";
|
|
320
|
+
for (let j = 0; j < slateTexts.length; j++) {
|
|
321
|
+
const cur = slateTexts[j];
|
|
322
|
+
textTemp += cur.text;
|
|
323
|
+
const prevStarts = starts.slice();
|
|
324
|
+
const prevEnds = ends.slice();
|
|
325
|
+
const prev = slateTexts[j - 1];
|
|
326
|
+
const next = slateTexts[j + 1];
|
|
327
|
+
ends = [];
|
|
328
|
+
[...basicMarkdownMarks, ...customLeaf.filter((k) => !basicMarkdownMarks.includes(k))].forEach((key) => {
|
|
329
|
+
const nodeType = getPluginType(options.editor, key);
|
|
330
|
+
if (cur[nodeType]) {
|
|
331
|
+
if (options.plainMarks?.includes(key)) return;
|
|
332
|
+
if (!prev?.[nodeType]) starts.push(key);
|
|
333
|
+
if (!next?.[nodeType]) ends.push(key);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const endsToRemove = starts.reduce((acc, k, kIndex) => {
|
|
337
|
+
if (ends.includes(k)) acc.push({
|
|
338
|
+
key: k,
|
|
339
|
+
index: kIndex
|
|
340
|
+
});
|
|
341
|
+
return acc;
|
|
342
|
+
}, []);
|
|
343
|
+
if (starts.length > 0) {
|
|
344
|
+
let bef = "";
|
|
345
|
+
let aft = "";
|
|
346
|
+
if (endsToRemove.length === 1 && (prevStarts.toString() !== starts.toString() || prevEnds.includes("italic") && ends.includes("bold")) && starts.length - endsToRemove.length === 0) {
|
|
347
|
+
while (textTemp.startsWith(" ")) {
|
|
348
|
+
bef += " ";
|
|
349
|
+
textTemp = textTemp.slice(1);
|
|
350
|
+
}
|
|
351
|
+
while (textTemp.endsWith(" ")) {
|
|
352
|
+
aft += " ";
|
|
353
|
+
textTemp = textTemp.slice(0, -1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let res = {
|
|
357
|
+
type: "text",
|
|
358
|
+
value: textTemp
|
|
359
|
+
};
|
|
360
|
+
textTemp = "";
|
|
361
|
+
starts.slice().reverse().forEach((k) => {
|
|
362
|
+
const nodeParser = getSerializerByKey(k, options);
|
|
363
|
+
if (nodeParser) res = {
|
|
364
|
+
...nodeParser(cur, options),
|
|
365
|
+
children: [res]
|
|
366
|
+
};
|
|
367
|
+
switch (k) {
|
|
368
|
+
case "bold":
|
|
369
|
+
res = {
|
|
370
|
+
children: [res],
|
|
371
|
+
type: "strong"
|
|
372
|
+
};
|
|
373
|
+
break;
|
|
374
|
+
case "code": {
|
|
375
|
+
let currentRes = res;
|
|
376
|
+
while (currentRes.type !== "text" && currentRes.type !== "inlineCode") currentRes = currentRes.children[0];
|
|
377
|
+
currentRes.type = "inlineCode";
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "italic":
|
|
381
|
+
res = {
|
|
382
|
+
children: [res],
|
|
383
|
+
type: "emphasis"
|
|
384
|
+
};
|
|
385
|
+
break;
|
|
386
|
+
case "strikethrough":
|
|
387
|
+
res = {
|
|
388
|
+
children: [res],
|
|
389
|
+
type: "delete"
|
|
390
|
+
};
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
const arr = [];
|
|
395
|
+
if (bef.length > 0) arr.push({
|
|
396
|
+
type: "text",
|
|
397
|
+
value: bef
|
|
398
|
+
});
|
|
399
|
+
arr.push(res);
|
|
400
|
+
if (aft.length > 0) arr.push({
|
|
401
|
+
type: "text",
|
|
402
|
+
value: aft
|
|
403
|
+
});
|
|
404
|
+
mdastTexts.push(...arr);
|
|
405
|
+
}
|
|
406
|
+
if (endsToRemove.length > 0) endsToRemove.reverse().forEach((e) => {
|
|
407
|
+
starts.splice(e.index, 1);
|
|
408
|
+
});
|
|
409
|
+
else {
|
|
410
|
+
mdastTexts.push({
|
|
411
|
+
type: "text",
|
|
412
|
+
value: textTemp
|
|
413
|
+
});
|
|
414
|
+
textTemp = "";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (textTemp) {
|
|
418
|
+
mdastTexts.push({
|
|
419
|
+
type: "text",
|
|
420
|
+
value: textTemp
|
|
421
|
+
});
|
|
422
|
+
textTemp = "";
|
|
423
|
+
}
|
|
424
|
+
return mergeTexts(mdastTexts).map((node) => {
|
|
425
|
+
if (!hasContent(node)) return {
|
|
426
|
+
type: "text",
|
|
427
|
+
value: ""
|
|
428
|
+
};
|
|
429
|
+
return node;
|
|
430
|
+
});
|
|
431
|
+
};
|
|
432
|
+
const hasContent = (node) => {
|
|
433
|
+
if (node.type === "inlineCode") return node.value !== "";
|
|
434
|
+
if (node.type === "text") return node.value !== "";
|
|
435
|
+
if (node.children?.length > 0) for (const child of node.children) {
|
|
436
|
+
if (child.type !== "emphasis" && child.type !== "strong" && child.type !== "inlineCode" && child.type !== "delete" && child.type !== "text") return true;
|
|
437
|
+
if (hasContent(child)) return true;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
};
|
|
441
|
+
const mergeTexts = (nodes) => {
|
|
442
|
+
const res = [];
|
|
443
|
+
for (const cur of nodes) {
|
|
444
|
+
const last = res.at(-1);
|
|
445
|
+
if (last && last.type === cur.type) if (last.type === "text") last.value += cur.value;
|
|
446
|
+
else if (last.type === "inlineCode") last.value += cur.value;
|
|
447
|
+
else last.children = mergeTexts(last.children.concat(cur.children));
|
|
448
|
+
else {
|
|
449
|
+
if (cur.type === "text" && cur.value === "") continue;
|
|
450
|
+
res.push(cur);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return res;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/lib/serializer/wrapWithBlockId.ts
|
|
458
|
+
/**
|
|
459
|
+
* Wraps an mdast node with a block element containing an ID attribute. Used for
|
|
460
|
+
* preserving block IDs when serializing to markdown.
|
|
461
|
+
*
|
|
462
|
+
* @param mdastNode - The mdast node to wrap
|
|
463
|
+
* @param nodeId - The ID to attach to the block element
|
|
464
|
+
* @returns The wrapped mdast node with block element and ID attribute
|
|
465
|
+
*/
|
|
466
|
+
const wrapWithBlockId = (mdastNode, nodeId) => ({
|
|
467
|
+
attributes: [{
|
|
468
|
+
name: "id",
|
|
469
|
+
type: "mdxJsxAttribute",
|
|
470
|
+
value: String(nodeId)
|
|
471
|
+
}],
|
|
472
|
+
children: [mdastNode],
|
|
473
|
+
data: { _mdxExplicitJsx: true },
|
|
474
|
+
name: "block",
|
|
475
|
+
type: "mdxJsxFlowElement"
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/lib/serializer/listToMdastTree.ts
|
|
480
|
+
function listToMdastTree(nodes, options, isBlock = false) {
|
|
481
|
+
if (nodes.length === 0) throw new Error("Cannot create a list from empty nodes");
|
|
482
|
+
if (options.withBlockId && isBlock && nodes.some((node) => node.id)) return processListWithBlockIds(nodes, options);
|
|
483
|
+
const root = {
|
|
484
|
+
children: [],
|
|
485
|
+
ordered: nodes[0].listStyleType === "decimal",
|
|
486
|
+
spread: options.spread ?? false,
|
|
487
|
+
start: nodes[0].listStart,
|
|
488
|
+
type: "list"
|
|
489
|
+
};
|
|
490
|
+
const indentStack = [{
|
|
491
|
+
indent: nodes[0].indent,
|
|
492
|
+
list: root,
|
|
493
|
+
parent: null,
|
|
494
|
+
styleType: nodes[0].listStyleType
|
|
495
|
+
}];
|
|
496
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
497
|
+
const node = nodes[i];
|
|
498
|
+
const currentIndent = node.indent;
|
|
499
|
+
while (indentStack.length > 1 && indentStack.at(-1).indent > currentIndent) indentStack.pop();
|
|
500
|
+
let stackTop = indentStack.at(-1);
|
|
501
|
+
if (!stackTop) throw new Error("Stack should never be empty");
|
|
502
|
+
if (stackTop.indent === currentIndent && stackTop.styleType !== node.listStyleType && !!stackTop.parent) {
|
|
503
|
+
const siblingList = {
|
|
504
|
+
children: [],
|
|
505
|
+
ordered: node.listStyleType === "decimal",
|
|
506
|
+
spread: options.spread ?? false,
|
|
507
|
+
start: node.listStart,
|
|
508
|
+
type: "list"
|
|
509
|
+
};
|
|
510
|
+
stackTop.parent.children.push(siblingList);
|
|
511
|
+
indentStack[indentStack.length - 1] = {
|
|
512
|
+
indent: currentIndent,
|
|
513
|
+
list: siblingList,
|
|
514
|
+
parent: stackTop.parent,
|
|
515
|
+
styleType: node.listStyleType
|
|
516
|
+
};
|
|
517
|
+
stackTop = indentStack.at(-1);
|
|
518
|
+
}
|
|
519
|
+
const listItem = {
|
|
520
|
+
checked: null,
|
|
521
|
+
children: [{
|
|
522
|
+
children: convertNodesSerialize(node.children, options),
|
|
523
|
+
type: "paragraph"
|
|
524
|
+
}],
|
|
525
|
+
type: "listItem"
|
|
526
|
+
};
|
|
527
|
+
if (options.spread) listItem.spread = true;
|
|
528
|
+
if (node.listStyleType === "todo" && node.checked !== void 0) listItem.checked = node.checked;
|
|
529
|
+
stackTop.list.children.push(listItem);
|
|
530
|
+
const nextNode = nodes[i + 1];
|
|
531
|
+
if (nextNode && nextNode.indent > currentIndent) {
|
|
532
|
+
const nestedList = {
|
|
533
|
+
children: [],
|
|
534
|
+
ordered: nextNode.listStyleType === "decimal",
|
|
535
|
+
spread: options.spread ?? false,
|
|
536
|
+
start: nextNode.listStart,
|
|
537
|
+
type: "list"
|
|
538
|
+
};
|
|
539
|
+
listItem.children.push(nestedList);
|
|
540
|
+
indentStack.push({
|
|
541
|
+
indent: nextNode.indent,
|
|
542
|
+
list: nestedList,
|
|
543
|
+
parent: listItem,
|
|
544
|
+
styleType: nextNode.listStyleType
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return root;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Process list nodes with block IDs by wrapping each item separately This
|
|
552
|
+
* preserves list numbering while allowing individual block wrapping
|
|
553
|
+
*/
|
|
554
|
+
function processListWithBlockIds(nodes, options) {
|
|
555
|
+
const fragments = [];
|
|
556
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
557
|
+
const node = nodes[i];
|
|
558
|
+
const singleList = {
|
|
559
|
+
children: [],
|
|
560
|
+
ordered: node.listStyleType === "decimal",
|
|
561
|
+
spread: options.spread ?? false,
|
|
562
|
+
start: node.listStyleType === "decimal" ? i + 1 : void 0,
|
|
563
|
+
type: "list"
|
|
564
|
+
};
|
|
565
|
+
const listItem = {
|
|
566
|
+
checked: null,
|
|
567
|
+
children: [{
|
|
568
|
+
children: convertNodesSerialize(node.children, options),
|
|
569
|
+
type: "paragraph"
|
|
570
|
+
}],
|
|
571
|
+
type: "listItem"
|
|
572
|
+
};
|
|
573
|
+
if (options.spread) listItem.spread = true;
|
|
574
|
+
if (node.listStyleType === "todo" && node.checked !== void 0) listItem.checked = node.checked;
|
|
575
|
+
singleList.children.push(listItem);
|
|
576
|
+
if (node.id) fragments.push(wrapWithBlockId(singleList, String(node.id)));
|
|
577
|
+
else fragments.push(singleList);
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
children: fragments,
|
|
581
|
+
type: "fragment"
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/lib/serializer/convertNodesSerialize.ts
|
|
587
|
+
const convertNodesSerialize = (nodes, options, isBlock = false) => {
|
|
588
|
+
const mdastNodes = [];
|
|
589
|
+
let textQueue = [];
|
|
590
|
+
const listBlock = [];
|
|
591
|
+
for (let i = 0; i <= nodes.length; i++) {
|
|
592
|
+
const n = nodes[i];
|
|
593
|
+
if (n && TextApi.isText(n)) {
|
|
594
|
+
if (shouldIncludeText(n, options)) textQueue.push(n);
|
|
595
|
+
} else {
|
|
596
|
+
if (textQueue.length > 0) mdastNodes.push(...convertTextsSerialize(textQueue, options));
|
|
597
|
+
textQueue = [];
|
|
598
|
+
if (!n) continue;
|
|
599
|
+
if (!shouldIncludeNode$1(n, options)) continue;
|
|
600
|
+
const pType = getPluginType(options.editor, KEYS.p) ?? KEYS.p;
|
|
601
|
+
if (n?.type === pType && "listStyleType" in n) {
|
|
602
|
+
listBlock.push(n);
|
|
603
|
+
const next = nodes[i + 1];
|
|
604
|
+
const isNextIndent = next && next.type === pType && "listStyleType" in next;
|
|
605
|
+
const firstList = listBlock.at(0);
|
|
606
|
+
const hasDifferentListStyle = isNextIndent && firstList && next.listStyleType !== firstList.listStyleType && next.indent === firstList.indent;
|
|
607
|
+
if (!isNextIndent || hasDifferentListStyle) {
|
|
608
|
+
const result = listToMdastTree(listBlock, options, isBlock);
|
|
609
|
+
if (result.type === "fragment") mdastNodes.push(...result.children);
|
|
610
|
+
else mdastNodes.push(result);
|
|
611
|
+
listBlock.length = 0;
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
const node = buildMdastNode(n, options, isBlock);
|
|
615
|
+
if (node) mdastNodes.push(node);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return mdastNodes;
|
|
620
|
+
};
|
|
621
|
+
const buildMdastNode = (node, options, isBlock = false) => {
|
|
622
|
+
const editor = options.editor;
|
|
623
|
+
let key = getPluginKey(editor, node.type) ?? node.type;
|
|
624
|
+
if (KEYS.heading.includes(key)) key = "heading";
|
|
625
|
+
if (key === KEYS.olClassic || key === KEYS.ulClassic) key = "list";
|
|
626
|
+
const nodeParser = getSerializerByKey(key, options);
|
|
627
|
+
if (nodeParser) {
|
|
628
|
+
const mdastNode = nodeParser(node, options);
|
|
629
|
+
if (options.withBlockId && node.id && isBlock) return wrapWithBlockId(mdastNode, node.id);
|
|
630
|
+
return mdastNode;
|
|
631
|
+
}
|
|
632
|
+
unreachable(node);
|
|
633
|
+
};
|
|
634
|
+
const shouldIncludeText = (text, options) => {
|
|
635
|
+
const { allowedNodes, allowNode, disallowedNodes } = options;
|
|
636
|
+
if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
|
|
637
|
+
for (const [key, value] of Object.entries(text)) {
|
|
638
|
+
if (key === "text") continue;
|
|
639
|
+
if (allowedNodes) {
|
|
640
|
+
if (!allowedNodes.includes(key) && value) return false;
|
|
641
|
+
} else if (disallowedNodes?.includes(key) && value) return false;
|
|
642
|
+
}
|
|
643
|
+
if (allowNode?.serialize) return allowNode.serialize(text);
|
|
644
|
+
return true;
|
|
645
|
+
};
|
|
646
|
+
const shouldIncludeNode$1 = (node, options) => {
|
|
647
|
+
const { allowedNodes, allowNode, disallowedNodes } = options;
|
|
648
|
+
if (!node.type) return true;
|
|
649
|
+
if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
|
|
650
|
+
if (allowedNodes) {
|
|
651
|
+
if (!allowedNodes.includes(node.type)) return false;
|
|
652
|
+
} else if (disallowedNodes?.includes(node.type)) return false;
|
|
653
|
+
if (allowNode?.serialize) return allowNode.serialize(node);
|
|
654
|
+
return true;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/lib/serializer/serializeInlineMd.ts
|
|
659
|
+
const serializeInlineMd = (editor, options) => {
|
|
660
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
661
|
+
const toRemarkProcessor = unified().use(mergedOptions.remarkPlugins ?? []).use(remarkStringify, {
|
|
662
|
+
emphasis: "_",
|
|
663
|
+
...mergedOptions?.remarkStringifyOptions
|
|
664
|
+
});
|
|
665
|
+
if (options?.value?.length === 0) return "";
|
|
666
|
+
const convertedTexts = convertTextsSerialize(mergedOptions.value, { editor });
|
|
667
|
+
return toRemarkProcessor.stringify({
|
|
668
|
+
children: convertedTexts,
|
|
669
|
+
type: "root"
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/lib/serializer/serializeMd.ts
|
|
675
|
+
/** Serialize the editor value to Markdown. */
|
|
676
|
+
const serializeMd = (editor, options) => {
|
|
677
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
678
|
+
const { remarkPlugins, value } = mergedOptions;
|
|
679
|
+
const toRemarkProcessor = unified().use(remarkPlugins ?? []).use(remarkStringify, {
|
|
680
|
+
emphasis: "_",
|
|
681
|
+
...mergedOptions?.remarkStringifyOptions
|
|
682
|
+
});
|
|
683
|
+
const mdast = slateToMdast({
|
|
684
|
+
children: value,
|
|
685
|
+
options: mergedOptions
|
|
686
|
+
});
|
|
687
|
+
return toRemarkProcessor.stringify(mdast);
|
|
688
|
+
};
|
|
689
|
+
const slateToMdast = ({ children, options }) => {
|
|
690
|
+
return {
|
|
691
|
+
children: convertNodesSerialize(children, options, true),
|
|
692
|
+
type: "root"
|
|
693
|
+
};
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/lib/rules/defaultRules.ts
|
|
698
|
+
const LEADING_NEWLINE_REGEX = /^\n/;
|
|
699
|
+
function isBoolean(value) {
|
|
700
|
+
return value === true || value === false || !!value && typeof value === "object" && Object.prototype.toString.call(value) === "[object Boolean]";
|
|
701
|
+
}
|
|
702
|
+
const defaultRules = {
|
|
703
|
+
a: {
|
|
704
|
+
deserialize: (mdastNode, deco, options) => ({
|
|
705
|
+
children: convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
706
|
+
type: getPluginType(options.editor, KEYS.a),
|
|
707
|
+
url: mdastNode.url
|
|
708
|
+
}),
|
|
709
|
+
serialize: (node, options) => ({
|
|
710
|
+
children: convertNodesSerialize(node.children, options),
|
|
711
|
+
type: "link",
|
|
712
|
+
url: node.url
|
|
713
|
+
})
|
|
714
|
+
},
|
|
715
|
+
blockquote: {
|
|
716
|
+
deserialize: (mdastNode, deco, options) => {
|
|
717
|
+
return {
|
|
718
|
+
children: (mdastNode.children.length > 0 ? mdastNode.children.flatMap((paragraph, index, children) => {
|
|
719
|
+
if (paragraph.type === "paragraph") {
|
|
720
|
+
if (children.length > 1 && children.length - 1 !== index) {
|
|
721
|
+
const paragraphChildren = convertChildrenDeserialize(paragraph.children, deco, options);
|
|
722
|
+
paragraphChildren.push({ text: "\n" }, { text: "\n" });
|
|
723
|
+
return paragraphChildren;
|
|
724
|
+
}
|
|
725
|
+
return convertChildrenDeserialize(paragraph.children, deco, options);
|
|
726
|
+
}
|
|
727
|
+
if ("children" in paragraph) return convertChildrenDeserialize(paragraph.children, deco, options);
|
|
728
|
+
return [{ text: "" }];
|
|
729
|
+
}) : [{ text: "" }]).flatMap((child) => child.type === "blockquote" ? child.children : [child]),
|
|
730
|
+
type: getPluginType(options.editor, KEYS.blockquote)
|
|
731
|
+
};
|
|
732
|
+
},
|
|
733
|
+
serialize: (node, options) => {
|
|
734
|
+
const nodes = [];
|
|
735
|
+
for (const child of node.children) if (child.text === "\n") nodes.push({ type: "break" });
|
|
736
|
+
else nodes.push(child);
|
|
737
|
+
const paragraphChildren = convertNodesSerialize(nodes, options);
|
|
738
|
+
if (paragraphChildren.length > 0 && paragraphChildren.at(-1).type === "break") {
|
|
739
|
+
paragraphChildren.at(-1).type = "html";
|
|
740
|
+
paragraphChildren.at(-1).value = "\n<br />";
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
children: [{
|
|
744
|
+
children: paragraphChildren,
|
|
745
|
+
type: "paragraph"
|
|
746
|
+
}],
|
|
747
|
+
type: "blockquote"
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
bold: {
|
|
752
|
+
mark: true,
|
|
753
|
+
deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
|
|
754
|
+
},
|
|
755
|
+
br: { deserialize() {
|
|
756
|
+
return [{ text: "\n" }];
|
|
757
|
+
} },
|
|
758
|
+
break: {
|
|
759
|
+
deserialize: (_mdastNode, _deco) => ({ text: "\n" }),
|
|
760
|
+
serialize: () => ({ type: "break" })
|
|
761
|
+
},
|
|
762
|
+
callout: {
|
|
763
|
+
deserialize: (mdastNode, deco, options) => {
|
|
764
|
+
const props = parseAttributes(mdastNode.attributes);
|
|
765
|
+
return {
|
|
766
|
+
children: convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
767
|
+
type: getPluginType(options.editor, KEYS.callout),
|
|
768
|
+
...props
|
|
769
|
+
};
|
|
770
|
+
},
|
|
771
|
+
serialize(slateNode, options) {
|
|
772
|
+
const { children, type, ...rest } = slateNode;
|
|
773
|
+
return {
|
|
774
|
+
attributes: propsToAttributes(rest),
|
|
775
|
+
children: convertNodesSerialize(children, options),
|
|
776
|
+
name: "callout",
|
|
777
|
+
type: "mdxJsxFlowElement"
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
code: {
|
|
782
|
+
mark: true,
|
|
783
|
+
deserialize: (mdastNode, deco, options) => ({
|
|
784
|
+
...deco,
|
|
785
|
+
[getPluginType(options.editor, KEYS.code)]: true,
|
|
786
|
+
text: mdastNode.value
|
|
787
|
+
})
|
|
788
|
+
},
|
|
789
|
+
code_block: {
|
|
790
|
+
deserialize: (mdastNode, _deco, options) => ({
|
|
791
|
+
children: (mdastNode.value || "").split("\n").map((line) => ({
|
|
792
|
+
children: [{ text: line }],
|
|
793
|
+
type: getPluginType(options.editor, KEYS.codeLine)
|
|
794
|
+
})),
|
|
795
|
+
lang: mdastNode.lang ?? void 0,
|
|
796
|
+
type: getPluginType(options.editor, KEYS.codeBlock)
|
|
797
|
+
}),
|
|
798
|
+
serialize: (node) => ({
|
|
799
|
+
lang: node.lang,
|
|
800
|
+
type: "code",
|
|
801
|
+
value: node.children.map((child) => child?.children === void 0 ? child.text : child.children.map((c) => c.text).join("")).join("\n")
|
|
802
|
+
})
|
|
803
|
+
},
|
|
804
|
+
comment: {
|
|
805
|
+
mark: true,
|
|
806
|
+
deserialize: (mdastNode, deco, options) => {
|
|
807
|
+
return convertChildrenDeserialize(mdastNode.children, {
|
|
808
|
+
[getPluginType(options.editor, KEYS.comment)]: true,
|
|
809
|
+
...deco
|
|
810
|
+
}, options);
|
|
811
|
+
},
|
|
812
|
+
serialize(slateNode) {
|
|
813
|
+
return {
|
|
814
|
+
attributes: [],
|
|
815
|
+
children: [{
|
|
816
|
+
type: "text",
|
|
817
|
+
value: slateNode.text
|
|
818
|
+
}],
|
|
819
|
+
name: "comment",
|
|
820
|
+
type: "mdxJsxTextElement"
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
date: {
|
|
825
|
+
deserialize(mdastNode, _deco, options) {
|
|
826
|
+
return {
|
|
827
|
+
children: [{ text: "" }],
|
|
828
|
+
date: (mdastNode.children?.[0])?.value || "",
|
|
829
|
+
type: getPluginType(options.editor, KEYS.date)
|
|
830
|
+
};
|
|
831
|
+
},
|
|
832
|
+
serialize({ date }) {
|
|
833
|
+
return {
|
|
834
|
+
attributes: [],
|
|
835
|
+
children: [{
|
|
836
|
+
type: "text",
|
|
837
|
+
value: date ?? ""
|
|
838
|
+
}],
|
|
839
|
+
name: "date",
|
|
840
|
+
type: "mdxJsxTextElement"
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
del: {
|
|
845
|
+
mark: true,
|
|
846
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
847
|
+
[getPluginType(options.editor, KEYS.strikethrough)]: true,
|
|
848
|
+
...deco
|
|
849
|
+
}, options)
|
|
850
|
+
},
|
|
851
|
+
equation: {
|
|
852
|
+
deserialize: (mdastNode, _deco, options) => ({
|
|
853
|
+
children: [{ text: "" }],
|
|
854
|
+
texExpression: mdastNode.value,
|
|
855
|
+
type: getPluginType(options.editor, KEYS.equation)
|
|
856
|
+
}),
|
|
857
|
+
serialize: (node) => ({
|
|
858
|
+
type: "math",
|
|
859
|
+
value: node.texExpression
|
|
860
|
+
})
|
|
861
|
+
},
|
|
862
|
+
footnoteDefinition: { deserialize: (mdastNode, deco, options) => {
|
|
863
|
+
return {
|
|
864
|
+
children: convertChildrenDeserialize(mdastNode.children, deco, options).flatMap((child) => child.type === "p" ? child.children : [child]),
|
|
865
|
+
type: getPluginType(options.editor, KEYS.p)
|
|
866
|
+
};
|
|
867
|
+
} },
|
|
868
|
+
footnoteReference: {},
|
|
869
|
+
heading: {
|
|
870
|
+
deserialize: (mdastNode, deco, options) => {
|
|
871
|
+
const defaultType = {
|
|
872
|
+
1: "h1",
|
|
873
|
+
2: "h2",
|
|
874
|
+
3: "h3",
|
|
875
|
+
4: "h4",
|
|
876
|
+
5: "h5",
|
|
877
|
+
6: "h6"
|
|
878
|
+
}[mdastNode.depth];
|
|
879
|
+
const type = getPluginType(options.editor, defaultType);
|
|
880
|
+
return {
|
|
881
|
+
children: convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
882
|
+
type
|
|
883
|
+
};
|
|
884
|
+
},
|
|
885
|
+
serialize: (node, options) => {
|
|
886
|
+
const key = getPluginKey(options.editor, node.type) ?? node.type;
|
|
887
|
+
return {
|
|
888
|
+
children: convertNodesSerialize(node.children, options),
|
|
889
|
+
depth: {
|
|
890
|
+
h1: 1,
|
|
891
|
+
h2: 2,
|
|
892
|
+
h3: 3,
|
|
893
|
+
h4: 4,
|
|
894
|
+
h5: 5,
|
|
895
|
+
h6: 6
|
|
896
|
+
}[key],
|
|
897
|
+
type: "heading"
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
highlight: {
|
|
902
|
+
mark: true,
|
|
903
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
904
|
+
[getPluginType(options.editor, KEYS.highlight)]: true,
|
|
905
|
+
...deco
|
|
906
|
+
}, options),
|
|
907
|
+
serialize(slateNode) {
|
|
908
|
+
return {
|
|
909
|
+
attributes: [],
|
|
910
|
+
children: [{
|
|
911
|
+
type: "text",
|
|
912
|
+
value: slateNode.text
|
|
913
|
+
}],
|
|
914
|
+
name: "mark",
|
|
915
|
+
type: "mdxJsxTextElement"
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
hr: {
|
|
920
|
+
deserialize: (_, __, options) => ({
|
|
921
|
+
children: [{ text: "" }],
|
|
922
|
+
type: getPluginType(options.editor, KEYS.hr)
|
|
923
|
+
}),
|
|
924
|
+
serialize: () => ({ type: "thematicBreak" })
|
|
925
|
+
},
|
|
926
|
+
html: { deserialize: (mdastNode, _deco, _options) => ({ text: (mdastNode.value || "").replaceAll("<br />", "\n") }) },
|
|
927
|
+
img: {
|
|
928
|
+
deserialize: (mdastNode, _deco, options) => {
|
|
929
|
+
const { alt, attributes, title, url } = mdastNode;
|
|
930
|
+
const { alt: altAttr, src, ...rest } = attributes ? parseAttributes(attributes) : {};
|
|
931
|
+
return {
|
|
932
|
+
caption: [{ text: altAttr || alt || "" }],
|
|
933
|
+
children: [{ text: "" }],
|
|
934
|
+
...title && { title },
|
|
935
|
+
type: getPluginType(options.editor, KEYS.img),
|
|
936
|
+
url: src || url,
|
|
937
|
+
...rest
|
|
938
|
+
};
|
|
939
|
+
},
|
|
940
|
+
serialize: ({ caption, url }) => {
|
|
941
|
+
return {
|
|
942
|
+
children: [{
|
|
943
|
+
alt: caption ? caption.map((c) => c.text).join("") : void 0,
|
|
944
|
+
title: caption ? caption.map((c) => c.text).join("") : void 0,
|
|
945
|
+
type: "image",
|
|
946
|
+
url
|
|
947
|
+
}],
|
|
948
|
+
type: "paragraph"
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
inline_equation: {
|
|
953
|
+
deserialize(mdastNode, _, options) {
|
|
954
|
+
return {
|
|
955
|
+
children: [{ text: "" }],
|
|
956
|
+
texExpression: mdastNode.value,
|
|
957
|
+
type: getPluginType(options.editor, KEYS.inlineEquation)
|
|
958
|
+
};
|
|
959
|
+
},
|
|
960
|
+
serialize: (node) => ({
|
|
961
|
+
type: "inlineMath",
|
|
962
|
+
value: node.texExpression
|
|
963
|
+
})
|
|
964
|
+
},
|
|
965
|
+
italic: {
|
|
966
|
+
mark: true,
|
|
967
|
+
deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
|
|
968
|
+
},
|
|
969
|
+
kbd: {
|
|
970
|
+
mark: true,
|
|
971
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
972
|
+
[getPluginType(options.editor, KEYS.kbd)]: true,
|
|
973
|
+
...deco
|
|
974
|
+
}, options),
|
|
975
|
+
serialize(slateNode) {
|
|
976
|
+
return {
|
|
977
|
+
attributes: [],
|
|
978
|
+
children: [{
|
|
979
|
+
type: "text",
|
|
980
|
+
value: slateNode.text
|
|
981
|
+
}],
|
|
982
|
+
name: "kbd",
|
|
983
|
+
type: "mdxJsxTextElement"
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
list: {
|
|
988
|
+
deserialize: (mdastNode, deco, options) => {
|
|
989
|
+
if (!!!options.editor?.plugins.list) return {
|
|
990
|
+
children: mdastNode.children.map((child) => {
|
|
991
|
+
if (child.type === "listItem") return {
|
|
992
|
+
children: child.children.map((itemChild) => {
|
|
993
|
+
if (itemChild.type === "paragraph") return {
|
|
994
|
+
children: convertChildrenDeserialize(itemChild.children, deco, options),
|
|
995
|
+
type: getPluginType(options.editor, KEYS.lic)
|
|
996
|
+
};
|
|
997
|
+
return convertChildrenDeserialize([itemChild], deco, options)[0];
|
|
998
|
+
}),
|
|
999
|
+
type: getPluginType(options.editor, KEYS.li)
|
|
1000
|
+
};
|
|
1001
|
+
return convertChildrenDeserialize([child], deco, options)[0];
|
|
1002
|
+
}),
|
|
1003
|
+
type: getPluginType(options.editor, mdastNode.ordered ? KEYS.olClassic : KEYS.ulClassic)
|
|
1004
|
+
};
|
|
1005
|
+
const parseListItems = (listNode, indent = 1, startIndex = 1) => {
|
|
1006
|
+
const items = [];
|
|
1007
|
+
const isOrdered = !!listNode.ordered;
|
|
1008
|
+
let listStyleType = isOrdered ? getPluginType(options.editor, KEYS.ol) : getPluginType(options.editor, KEYS.ul);
|
|
1009
|
+
listNode.children?.forEach((listItem, index) => {
|
|
1010
|
+
if (listItem.type !== "listItem") return;
|
|
1011
|
+
const isTodoList = isBoolean(listItem.checked);
|
|
1012
|
+
if (isTodoList) listStyleType = getPluginType(options.editor, KEYS.listTodo);
|
|
1013
|
+
const [paragraph, ...subLists] = listItem.children || [];
|
|
1014
|
+
const result = paragraph ? buildSlateNode(paragraph, deco, options) : {
|
|
1015
|
+
children: [{ text: "" }],
|
|
1016
|
+
type: getPluginType(options.editor, KEYS.p)
|
|
1017
|
+
};
|
|
1018
|
+
(Array.isArray(result) ? result : [result]).forEach((node) => {
|
|
1019
|
+
const itemContent = {
|
|
1020
|
+
...node,
|
|
1021
|
+
indent,
|
|
1022
|
+
type: node.type === getPluginType(options.editor, KEYS.img) ? node.type : getPluginType(options.editor, KEYS.p)
|
|
1023
|
+
};
|
|
1024
|
+
itemContent.listStyleType = listStyleType;
|
|
1025
|
+
if (isTodoList) itemContent.checked = listItem.checked;
|
|
1026
|
+
if (isOrdered) itemContent.listStart = startIndex + index;
|
|
1027
|
+
items.push(itemContent);
|
|
1028
|
+
});
|
|
1029
|
+
subLists.forEach((subNode) => {
|
|
1030
|
+
if (subNode.type === "list") {
|
|
1031
|
+
const subListStart = subNode.start || 1;
|
|
1032
|
+
const nestedItems = parseListItems(subNode, indent + 1, subListStart);
|
|
1033
|
+
items.push(...nestedItems);
|
|
1034
|
+
} else {
|
|
1035
|
+
const result$1 = buildSlateNode(subNode, deco, options);
|
|
1036
|
+
if (Array.isArray(result$1)) items.push(...result$1.map((item) => ({
|
|
1037
|
+
...item,
|
|
1038
|
+
indent: indent + 1
|
|
1039
|
+
})));
|
|
1040
|
+
else items.push({
|
|
1041
|
+
...result$1,
|
|
1042
|
+
indent: indent + 1
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
return items;
|
|
1048
|
+
};
|
|
1049
|
+
return parseListItems(mdastNode, 1, mdastNode.start || 1);
|
|
1050
|
+
},
|
|
1051
|
+
serialize: (node, options) => {
|
|
1052
|
+
const editor = options.editor;
|
|
1053
|
+
const isOrdered = getPluginKey(editor, node.type) === KEYS.olClassic;
|
|
1054
|
+
const serializeListItems = (children) => {
|
|
1055
|
+
const items = [];
|
|
1056
|
+
let currentItem = null;
|
|
1057
|
+
for (const child of children) if (getPluginKey(editor, child.type) === "li") {
|
|
1058
|
+
if (currentItem) items.push(currentItem);
|
|
1059
|
+
currentItem = {
|
|
1060
|
+
children: [],
|
|
1061
|
+
spread: false,
|
|
1062
|
+
type: "listItem"
|
|
1063
|
+
};
|
|
1064
|
+
for (const liChild of child.children) if (getPluginKey(editor, liChild.type) === "lic") currentItem.children.push({
|
|
1065
|
+
children: convertNodesSerialize(liChild.children, options),
|
|
1066
|
+
type: "paragraph"
|
|
1067
|
+
});
|
|
1068
|
+
else if (getPluginKey(editor, liChild.type) === "ol" || getPluginKey(editor, liChild.type) === "ul") currentItem.children.push({
|
|
1069
|
+
children: serializeListItems(liChild.children),
|
|
1070
|
+
ordered: getPluginKey(editor, liChild.type) === "ol",
|
|
1071
|
+
spread: false,
|
|
1072
|
+
type: "list"
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
if (currentItem) items.push(currentItem);
|
|
1076
|
+
return items;
|
|
1077
|
+
};
|
|
1078
|
+
return {
|
|
1079
|
+
children: serializeListItems(node.children),
|
|
1080
|
+
ordered: isOrdered,
|
|
1081
|
+
spread: false,
|
|
1082
|
+
type: "list"
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
listItem: {
|
|
1087
|
+
deserialize: (mdastNode, deco, options) => {
|
|
1088
|
+
return {
|
|
1089
|
+
children: mdastNode.children.map((child) => {
|
|
1090
|
+
if (child.type === "paragraph") return {
|
|
1091
|
+
children: convertChildrenDeserialize(child.children, deco, options),
|
|
1092
|
+
type: getPluginType(options.editor, KEYS.lic)
|
|
1093
|
+
};
|
|
1094
|
+
return convertChildrenDeserialize([child], deco, options)[0];
|
|
1095
|
+
}),
|
|
1096
|
+
type: getPluginType(options.editor, KEYS.li)
|
|
1097
|
+
};
|
|
1098
|
+
},
|
|
1099
|
+
serialize: (node, options) => ({
|
|
1100
|
+
children: convertNodesSerialize(node.children, options),
|
|
1101
|
+
type: "listItem"
|
|
1102
|
+
})
|
|
1103
|
+
},
|
|
1104
|
+
mention: {
|
|
1105
|
+
deserialize: (node, _deco, options) => ({
|
|
1106
|
+
children: [{ text: "" }],
|
|
1107
|
+
type: getPluginType(options.editor, KEYS.mention),
|
|
1108
|
+
value: node.displayText || node.username,
|
|
1109
|
+
...node.displayText && { key: node.username }
|
|
1110
|
+
}),
|
|
1111
|
+
serialize: (node) => {
|
|
1112
|
+
const mentionId = node.key || node.value;
|
|
1113
|
+
const displayText = node.value;
|
|
1114
|
+
const encodedId = encodeURIComponent(String(mentionId)).replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
1115
|
+
return {
|
|
1116
|
+
children: [{
|
|
1117
|
+
type: "text",
|
|
1118
|
+
value: displayText
|
|
1119
|
+
}],
|
|
1120
|
+
type: "link",
|
|
1121
|
+
url: `mention:${encodedId}`
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
p: {
|
|
1126
|
+
deserialize: (node, deco, options) => {
|
|
1127
|
+
const isKeepLineBreak = options.splitLineBreaks;
|
|
1128
|
+
const children = convertChildrenDeserialize(node.children, deco, options);
|
|
1129
|
+
const splitBlockTypes = new Set(["img"]);
|
|
1130
|
+
const elements = [];
|
|
1131
|
+
let inlineNodes = [];
|
|
1132
|
+
const flushInlineNodes = () => {
|
|
1133
|
+
if (inlineNodes.length > 0) {
|
|
1134
|
+
elements.push({
|
|
1135
|
+
children: inlineNodes,
|
|
1136
|
+
type: getPluginType(options.editor, KEYS.p)
|
|
1137
|
+
});
|
|
1138
|
+
inlineNodes = [];
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
children.forEach((c) => {
|
|
1142
|
+
if (c.text === "") c.text = "";
|
|
1143
|
+
});
|
|
1144
|
+
children.forEach((child, index, children$1) => {
|
|
1145
|
+
const { type } = child;
|
|
1146
|
+
if (type && splitBlockTypes.has(type)) {
|
|
1147
|
+
flushInlineNodes();
|
|
1148
|
+
elements.push(child);
|
|
1149
|
+
} else if (isKeepLineBreak && "text" in child && typeof child.text === "string") {
|
|
1150
|
+
const textParts = child.text.split("\n");
|
|
1151
|
+
if (child.text === "\n" && inlineNodes.length === 0) {
|
|
1152
|
+
inlineNodes.push({
|
|
1153
|
+
...child,
|
|
1154
|
+
text: ""
|
|
1155
|
+
});
|
|
1156
|
+
flushInlineNodes();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
textParts.forEach((part, index$1, array) => {
|
|
1160
|
+
const isNotFirstPart = index$1 > 0;
|
|
1161
|
+
const isNotLastPart = index$1 < array.length - 1;
|
|
1162
|
+
if (isNotFirstPart) flushInlineNodes();
|
|
1163
|
+
if (part) inlineNodes.push({
|
|
1164
|
+
...child,
|
|
1165
|
+
text: part
|
|
1166
|
+
});
|
|
1167
|
+
if (isNotLastPart) flushInlineNodes();
|
|
1168
|
+
});
|
|
1169
|
+
} else if (child.text === "\n" && children$1.length > 1 && index === children$1.length - 1) {} else inlineNodes.push(child);
|
|
1170
|
+
});
|
|
1171
|
+
flushInlineNodes();
|
|
1172
|
+
return elements.length === 1 ? elements[0] : elements;
|
|
1173
|
+
},
|
|
1174
|
+
serialize: (node, options) => {
|
|
1175
|
+
let enrichedChildren = node.children;
|
|
1176
|
+
enrichedChildren = enrichedChildren.map((child) => {
|
|
1177
|
+
if (child.text === "\n") return { type: "break" };
|
|
1178
|
+
if (child.text === "" && options.preserveEmptyParagraphs !== false) return {
|
|
1179
|
+
...child,
|
|
1180
|
+
text: ""
|
|
1181
|
+
};
|
|
1182
|
+
return child;
|
|
1183
|
+
});
|
|
1184
|
+
const convertedNodes = convertNodesSerialize(enrichedChildren, options);
|
|
1185
|
+
if (convertedNodes.length > 0 && enrichedChildren.at(-1).type === "break") {
|
|
1186
|
+
convertedNodes.at(-1).type = "html";
|
|
1187
|
+
convertedNodes.at(-1).value = "\n<br />";
|
|
1188
|
+
}
|
|
1189
|
+
return {
|
|
1190
|
+
children: convertedNodes,
|
|
1191
|
+
type: "paragraph"
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
strikethrough: {
|
|
1196
|
+
mark: true,
|
|
1197
|
+
deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
|
|
1198
|
+
},
|
|
1199
|
+
subscript: {
|
|
1200
|
+
mark: true,
|
|
1201
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
1202
|
+
[getPluginType(options.editor, KEYS.sub)]: true,
|
|
1203
|
+
...deco
|
|
1204
|
+
}, options),
|
|
1205
|
+
serialize(slateNode, _options) {
|
|
1206
|
+
return {
|
|
1207
|
+
attributes: [],
|
|
1208
|
+
children: [{
|
|
1209
|
+
type: "text",
|
|
1210
|
+
value: slateNode.text
|
|
1211
|
+
}],
|
|
1212
|
+
name: "sub",
|
|
1213
|
+
type: "mdxJsxTextElement"
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
suggestion: {
|
|
1218
|
+
mark: true,
|
|
1219
|
+
deserialize: (mdastNode, deco, options) => {
|
|
1220
|
+
return convertChildrenDeserialize(mdastNode.children, {
|
|
1221
|
+
[getPluginType(options.editor, KEYS.suggestion)]: true,
|
|
1222
|
+
...deco
|
|
1223
|
+
}, options);
|
|
1224
|
+
},
|
|
1225
|
+
serialize(slateNode) {
|
|
1226
|
+
return {
|
|
1227
|
+
attributes: [],
|
|
1228
|
+
children: [{
|
|
1229
|
+
type: "text",
|
|
1230
|
+
value: slateNode.text
|
|
1231
|
+
}],
|
|
1232
|
+
name: "suggestion",
|
|
1233
|
+
type: "mdxJsxTextElement"
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
superscript: {
|
|
1238
|
+
mark: true,
|
|
1239
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
1240
|
+
[getPluginType(options.editor, KEYS.sup)]: true,
|
|
1241
|
+
...deco
|
|
1242
|
+
}, options),
|
|
1243
|
+
serialize(slateNode, _options) {
|
|
1244
|
+
return {
|
|
1245
|
+
attributes: [],
|
|
1246
|
+
children: [{
|
|
1247
|
+
type: "text",
|
|
1248
|
+
value: slateNode.text
|
|
1249
|
+
}],
|
|
1250
|
+
name: "sup",
|
|
1251
|
+
type: "mdxJsxTextElement"
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
table: {
|
|
1256
|
+
deserialize: (node, deco, options) => {
|
|
1257
|
+
const paragraphType = getPluginType(options.editor, KEYS.p);
|
|
1258
|
+
return {
|
|
1259
|
+
children: node.children?.map((row, rowIndex) => ({
|
|
1260
|
+
children: row.children?.map((cell) => {
|
|
1261
|
+
const cellType = rowIndex === 0 ? "th" : "td";
|
|
1262
|
+
const cellChildren = convertChildrenDeserialize(cell.children, deco, options);
|
|
1263
|
+
const groupedChildren = [];
|
|
1264
|
+
let currentParagraphChildren = [];
|
|
1265
|
+
for (const child of cellChildren) if (!child.type || child.type === KEYS.inlineEquation) currentParagraphChildren.push(child);
|
|
1266
|
+
else {
|
|
1267
|
+
if (currentParagraphChildren.length > 0) {
|
|
1268
|
+
groupedChildren.push({
|
|
1269
|
+
children: currentParagraphChildren,
|
|
1270
|
+
type: paragraphType
|
|
1271
|
+
});
|
|
1272
|
+
currentParagraphChildren = [];
|
|
1273
|
+
}
|
|
1274
|
+
groupedChildren.push(child);
|
|
1275
|
+
}
|
|
1276
|
+
if (currentParagraphChildren.length > 0) groupedChildren.push({
|
|
1277
|
+
children: currentParagraphChildren,
|
|
1278
|
+
type: paragraphType
|
|
1279
|
+
});
|
|
1280
|
+
return {
|
|
1281
|
+
children: groupedChildren.length > 0 ? groupedChildren : [{
|
|
1282
|
+
children: [{ text: "" }],
|
|
1283
|
+
type: paragraphType
|
|
1284
|
+
}],
|
|
1285
|
+
type: getPluginType(options.editor, cellType)
|
|
1286
|
+
};
|
|
1287
|
+
}) || [],
|
|
1288
|
+
type: getPluginType(options.editor, KEYS.tr)
|
|
1289
|
+
})) || [],
|
|
1290
|
+
type: getPluginType(options.editor, KEYS.table)
|
|
1291
|
+
};
|
|
1292
|
+
},
|
|
1293
|
+
serialize: (node, options) => ({
|
|
1294
|
+
children: convertNodesSerialize(node.children, options),
|
|
1295
|
+
type: "table"
|
|
1296
|
+
})
|
|
1297
|
+
},
|
|
1298
|
+
td: { serialize: (node, options) => {
|
|
1299
|
+
const children = convertNodesSerialize(node.children, options);
|
|
1300
|
+
if (children.length > 1) {
|
|
1301
|
+
const result = [];
|
|
1302
|
+
for (let i = 0; i < children.length; i++) {
|
|
1303
|
+
result.push(children[i]);
|
|
1304
|
+
if (i < children.length - 1) result.push({
|
|
1305
|
+
type: "html",
|
|
1306
|
+
value: "<br/>"
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
return {
|
|
1310
|
+
children: result,
|
|
1311
|
+
type: "tableCell"
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
children,
|
|
1316
|
+
type: "tableCell"
|
|
1317
|
+
};
|
|
1318
|
+
} },
|
|
1319
|
+
text: { deserialize: (mdastNode, deco) => ({
|
|
1320
|
+
...deco,
|
|
1321
|
+
text: mdastNode.value.replace(LEADING_NEWLINE_REGEX, "")
|
|
1322
|
+
}) },
|
|
1323
|
+
th: { serialize: (node, options) => {
|
|
1324
|
+
const children = convertNodesSerialize(node.children, options);
|
|
1325
|
+
if (children.length > 1) {
|
|
1326
|
+
const result = [];
|
|
1327
|
+
for (let i = 0; i < children.length; i++) {
|
|
1328
|
+
result.push(children[i]);
|
|
1329
|
+
if (i < children.length - 1) result.push({
|
|
1330
|
+
type: "html",
|
|
1331
|
+
value: "<br/>"
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
children: result,
|
|
1336
|
+
type: "tableCell"
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
children,
|
|
1341
|
+
type: "tableCell"
|
|
1342
|
+
};
|
|
1343
|
+
} },
|
|
1344
|
+
toc: {
|
|
1345
|
+
deserialize: (mdastNode, deco, options) => ({
|
|
1346
|
+
children: convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
1347
|
+
type: getPluginType(options.editor, KEYS.toc)
|
|
1348
|
+
}),
|
|
1349
|
+
serialize: (node, options) => ({
|
|
1350
|
+
attributes: [],
|
|
1351
|
+
children: convertNodesSerialize(node.children, options),
|
|
1352
|
+
name: "toc",
|
|
1353
|
+
type: "mdxJsxFlowElement"
|
|
1354
|
+
})
|
|
1355
|
+
},
|
|
1356
|
+
tr: { serialize: (node, options) => ({
|
|
1357
|
+
children: convertNodesSerialize(node.children, options),
|
|
1358
|
+
type: "tableRow"
|
|
1359
|
+
}) },
|
|
1360
|
+
underline: {
|
|
1361
|
+
mark: true,
|
|
1362
|
+
deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
|
|
1363
|
+
[getPluginType(options.editor, KEYS.underline)]: true,
|
|
1364
|
+
...deco
|
|
1365
|
+
}, options),
|
|
1366
|
+
serialize(slateNode, _options) {
|
|
1367
|
+
return {
|
|
1368
|
+
attributes: [],
|
|
1369
|
+
children: [{
|
|
1370
|
+
type: "text",
|
|
1371
|
+
value: slateNode.text
|
|
1372
|
+
}],
|
|
1373
|
+
name: "u",
|
|
1374
|
+
type: "mdxJsxTextElement"
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
...fontRules,
|
|
1379
|
+
...mediaRules,
|
|
1380
|
+
...columnRules
|
|
1381
|
+
};
|
|
1382
|
+
const buildRules = (editor) => {
|
|
1383
|
+
const keys = Object.keys(defaultRules);
|
|
1384
|
+
const newRules = {};
|
|
1385
|
+
keys.forEach((key) => {
|
|
1386
|
+
const pluginKey = getPluginKey(editor, key);
|
|
1387
|
+
newRules[pluginKey ?? key] = defaultRules[key];
|
|
1388
|
+
});
|
|
1389
|
+
return newRules;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region src/lib/deserializer/utils/getDeserializerByKey.ts
|
|
1394
|
+
const getDeserializerByKey = (key, options) => {
|
|
1395
|
+
const rules = options.rules;
|
|
1396
|
+
return rules?.[key]?.deserialize === void 0 ? buildRules(options.editor)[key]?.deserialize : rules?.[key]?.deserialize;
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/lib/deserializer/utils/customMdxDeserialize.ts
|
|
1401
|
+
const customMdxDeserialize = (mdastNode, deco, options) => {
|
|
1402
|
+
const customJsxElementKey = mdastNode.name;
|
|
1403
|
+
const key = getPluginKey(options.editor, customJsxElementKey) ?? mdastNode.name;
|
|
1404
|
+
if (key) {
|
|
1405
|
+
const nodeParserDeserialize = getDeserializerByKey(mdastToPlate(options.editor, key), options);
|
|
1406
|
+
if (nodeParserDeserialize) return nodeParserDeserialize(mdastNode, deco, options);
|
|
1407
|
+
} else console.warn("This MDX node does not have a parser for deserialization", mdastNode);
|
|
1408
|
+
if (mdastNode.type === "mdxJsxTextElement") {
|
|
1409
|
+
const tagName = mdastNode.name;
|
|
1410
|
+
let textContent = "";
|
|
1411
|
+
if (mdastNode.children) textContent = mdastNode.children.map((child) => {
|
|
1412
|
+
if ("value" in child) return child.value;
|
|
1413
|
+
return "";
|
|
1414
|
+
}).join("");
|
|
1415
|
+
return [{ text: `<${tagName}>${textContent}</${tagName}>` }];
|
|
1416
|
+
}
|
|
1417
|
+
if (mdastNode.type === "mdxJsxFlowElement") {
|
|
1418
|
+
const tagName = mdastNode.name;
|
|
1419
|
+
return [{
|
|
1420
|
+
children: [
|
|
1421
|
+
{ text: `<${tagName}>\n` },
|
|
1422
|
+
...convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
1423
|
+
{ text: `\n</${tagName}>` }
|
|
1424
|
+
],
|
|
1425
|
+
type: getPluginType(options.editor, KEYS.p)
|
|
1426
|
+
}];
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
//#endregion
|
|
1431
|
+
//#region src/lib/deserializer/utils/stripMarkdown.ts
|
|
1432
|
+
const stripMarkdownBlocks = (text) => {
|
|
1433
|
+
let result = text;
|
|
1434
|
+
result = result.replaceAll(/^#{1,6}\s+/gm, "");
|
|
1435
|
+
result = result.replaceAll(/^\s*>\s?/gm, "");
|
|
1436
|
+
result = result.replaceAll(/^([*_-]){3,}\s*$/gm, "");
|
|
1437
|
+
result = result.replaceAll(/^(\s*)([*+-]|\d+\.)\s/gm, "$1");
|
|
1438
|
+
result = result.replaceAll(/^```[\S\s]*?^```/gm, "");
|
|
1439
|
+
result = result.replaceAll("<br>", "\n");
|
|
1440
|
+
return result;
|
|
1441
|
+
};
|
|
1442
|
+
const stripMarkdownInline = (text) => {
|
|
1443
|
+
let result = text;
|
|
1444
|
+
result = result.replaceAll(/(\*\*|__)(.*?)\1/g, "$2");
|
|
1445
|
+
result = result.replaceAll(/(\*|_)(.*?)\1/g, "$2");
|
|
1446
|
+
result = result.replaceAll(/\[([^\]]+)]\(([^)]+)\)/g, "$1");
|
|
1447
|
+
result = result.replaceAll(/`(.+?)`/g, "$1");
|
|
1448
|
+
result = result.replaceAll(" ", " ");
|
|
1449
|
+
result = result.replaceAll("<", "<");
|
|
1450
|
+
result = result.replaceAll(">", ">");
|
|
1451
|
+
result = result.replaceAll("&", "&");
|
|
1452
|
+
return result;
|
|
1453
|
+
};
|
|
1454
|
+
const stripMarkdown = (text) => {
|
|
1455
|
+
let result = stripMarkdownBlocks(text);
|
|
1456
|
+
result = stripMarkdownInline(result);
|
|
1457
|
+
return result;
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
//#endregion
|
|
1461
|
+
//#region src/lib/deserializer/utils/deserializeInlineMd.ts
|
|
1462
|
+
const LEADING_SPACES_REGEX = /^\s*/;
|
|
1463
|
+
const TRAILING_SPACES_REGEX = /\s*$/;
|
|
1464
|
+
const deserializeInlineMd = (editor, text, options) => {
|
|
1465
|
+
const leadingSpaces = LEADING_SPACES_REGEX.exec(text)?.[0] || "";
|
|
1466
|
+
const trailingSpaces = TRAILING_SPACES_REGEX.exec(text)?.[0] || "";
|
|
1467
|
+
const strippedText = stripMarkdownBlocks(text.trim());
|
|
1468
|
+
const fragment = [];
|
|
1469
|
+
if (leadingSpaces) fragment.push({ text: leadingSpaces });
|
|
1470
|
+
if (strippedText) {
|
|
1471
|
+
const result = editor.getApi(MarkdownPlugin).markdown.deserialize(strippedText, options)[0];
|
|
1472
|
+
if (result) {
|
|
1473
|
+
const nodes = ElementApi.isElement(result) ? result.children : [result];
|
|
1474
|
+
fragment.push(...nodes);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (trailingSpaces) fragment.push({ text: trailingSpaces });
|
|
1478
|
+
return fragment;
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region src/lib/utils/getRemarkPluginsWithoutMdx.ts
|
|
1483
|
+
const REMARK_MDX_TAG = "remarkMdx";
|
|
1484
|
+
const tagRemarkPlugin = (pluginFn, tag) => {
|
|
1485
|
+
const wrapped = function(...args) {
|
|
1486
|
+
return pluginFn.apply(this, args);
|
|
1487
|
+
};
|
|
1488
|
+
wrapped.__pluginTag = tag;
|
|
1489
|
+
return wrapped;
|
|
1490
|
+
};
|
|
1491
|
+
const getRemarkPluginsWithoutMdx = (plugins) => plugins.filter((plugin) => plugin.__pluginTag !== REMARK_MDX_TAG);
|
|
1492
|
+
|
|
1493
|
+
//#endregion
|
|
1494
|
+
//#region src/lib/deserializer/utils/getMergedOptionsDeserialize.ts
|
|
1495
|
+
/**
|
|
1496
|
+
* Merges Markdown configurations, following the principle that options take
|
|
1497
|
+
* precedence
|
|
1498
|
+
*
|
|
1499
|
+
* @param editor Editor instance used to get plugin default configurations
|
|
1500
|
+
* @param options User-provided options (higher priority)
|
|
1501
|
+
* @returns The final merged configuration
|
|
1502
|
+
*/
|
|
1503
|
+
const getMergedOptionsDeserialize = (editor, options) => {
|
|
1504
|
+
const { allowedNodes: PluginAllowedNodes, allowNode: PluginAllowNode, disallowedNodes: PluginDisallowedNodes, remarkPlugins: PluginRemarkPlugins, rules: PluginRules } = editor.getOptions(MarkdownPlugin);
|
|
1505
|
+
const mergedRules = {
|
|
1506
|
+
...buildRules(editor),
|
|
1507
|
+
...options?.rules ?? PluginRules
|
|
1508
|
+
};
|
|
1509
|
+
const remarkPlugins = options?.remarkPlugins ?? PluginRemarkPlugins ?? [];
|
|
1510
|
+
return {
|
|
1511
|
+
allowedNodes: options?.allowedNodes ?? PluginAllowedNodes,
|
|
1512
|
+
allowNode: options?.allowNode ?? PluginAllowNode,
|
|
1513
|
+
disallowedNodes: options?.disallowedNodes ?? PluginDisallowedNodes,
|
|
1514
|
+
editor,
|
|
1515
|
+
memoize: options?.memoize,
|
|
1516
|
+
parser: options?.parser,
|
|
1517
|
+
remarkPlugins: options?.withoutMdx ? getRemarkPluginsWithoutMdx(remarkPlugins) : remarkPlugins,
|
|
1518
|
+
rules: mergedRules,
|
|
1519
|
+
splitLineBreaks: options?.splitLineBreaks
|
|
1520
|
+
};
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
//#endregion
|
|
1524
|
+
//#region src/lib/deserializer/utils/getStyleValue.ts
|
|
1525
|
+
const getStyleValue = (mdastNode, styleName) => {
|
|
1526
|
+
const styleAttribute = mdastNode.attributes.find((attr) => "name" in attr && attr.name === "style");
|
|
1527
|
+
if (!styleAttribute?.value) return;
|
|
1528
|
+
const styles = styleAttribute.value.split(";");
|
|
1529
|
+
for (const style of styles) {
|
|
1530
|
+
const [name, value] = style.split(":").map((s) => s.trim());
|
|
1531
|
+
if (name === styleName) return value;
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
//#endregion
|
|
1536
|
+
//#region src/lib/deserializer/utils/htmlToJsx.ts
|
|
1537
|
+
const VOID_ELEMENTS = new Set([
|
|
1538
|
+
"area",
|
|
1539
|
+
"base",
|
|
1540
|
+
"br",
|
|
1541
|
+
"col",
|
|
1542
|
+
"embed",
|
|
1543
|
+
"hr",
|
|
1544
|
+
"img",
|
|
1545
|
+
"input",
|
|
1546
|
+
"link",
|
|
1547
|
+
"meta",
|
|
1548
|
+
"param",
|
|
1549
|
+
"source",
|
|
1550
|
+
"track",
|
|
1551
|
+
"wbr"
|
|
1552
|
+
]);
|
|
1553
|
+
const BOOL_ATTRS = new Map([
|
|
1554
|
+
["checked", "checked"],
|
|
1555
|
+
["disabled", "disabled"],
|
|
1556
|
+
["readonly", "readOnly"],
|
|
1557
|
+
["required", "required"],
|
|
1558
|
+
["multiple", "multiple"],
|
|
1559
|
+
["hidden", "hidden"]
|
|
1560
|
+
]);
|
|
1561
|
+
const BOOL_ATTR_REGEXES = Array.from(BOOL_ATTRS.entries()).map(([htmlAttr, jsxAttr]) => ({
|
|
1562
|
+
jsxAttr,
|
|
1563
|
+
reg: new RegExp(`(\\s|^)${htmlAttr}(\\s|/?>|$)`, "gi")
|
|
1564
|
+
}));
|
|
1565
|
+
const ATTR_RENAMES = [[/(\s)class=/g, "$1className="], [/(\s)for=/g, "$1htmlFor="]];
|
|
1566
|
+
const htmlToJsx = (html) => {
|
|
1567
|
+
if (!html || typeof html !== "string") return html;
|
|
1568
|
+
return html.replace(/<!--([\s\S]*?)-->/g, "{/*$1*/}").replace(/<([a-zA-Z0-9]+)\b([^>]*?)(\/?)>/gi, (_match, tagName, attrs, selfClosing) => {
|
|
1569
|
+
let a = attrs;
|
|
1570
|
+
ATTR_RENAMES.forEach(([pattern, replacement]) => {
|
|
1571
|
+
a = a.replace(pattern, replacement);
|
|
1572
|
+
});
|
|
1573
|
+
a = a.replace(/(^|\s)([a-zA-Z0-9_-]+)=([^{ \t\n\r"'>]+?)(?=\s|\/?>|$)/g, "$1$2=\"$3\"");
|
|
1574
|
+
for (const { reg, jsxAttr } of BOOL_ATTR_REGEXES) a = a.replace(reg, `$1${jsxAttr}="true"$2`);
|
|
1575
|
+
const closing = VOID_ELEMENTS.has(tagName.toLowerCase()) ? " /" : selfClosing;
|
|
1576
|
+
return `<${tagName}${a.trimEnd()}${closing}>`;
|
|
1577
|
+
});
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
//#endregion
|
|
1581
|
+
//#region src/lib/deserializer/mdastToSlate.ts
|
|
1582
|
+
const mdastToSlate = (node, options) => buildSlateRoot(node, options);
|
|
1583
|
+
const buildSlateRoot = (root, options) => {
|
|
1584
|
+
if (!options.splitLineBreaks) {
|
|
1585
|
+
root.children = root.children.map((child) => {
|
|
1586
|
+
if (child.type === "html" && child.value === "<br />") return {
|
|
1587
|
+
children: [{
|
|
1588
|
+
type: "text",
|
|
1589
|
+
value: "\n"
|
|
1590
|
+
}],
|
|
1591
|
+
type: "paragraph"
|
|
1592
|
+
};
|
|
1593
|
+
return child;
|
|
1594
|
+
});
|
|
1595
|
+
return convertNodesDeserialize(root.children, {}, options);
|
|
1596
|
+
}
|
|
1597
|
+
const results = [];
|
|
1598
|
+
let startLine = root.position?.start.line ?? 1;
|
|
1599
|
+
const addEmptyParagraphs = (count) => {
|
|
1600
|
+
if (count > 0) results.push(...Array.from({ length: count }).map(() => ({
|
|
1601
|
+
children: [{ text: "" }],
|
|
1602
|
+
type: options.editor ? getPluginKey(options.editor, KEYS.p) ?? KEYS.p : KEYS.p
|
|
1603
|
+
})));
|
|
1604
|
+
};
|
|
1605
|
+
root.children?.forEach((child, index) => {
|
|
1606
|
+
const isFirstChild = index === 0;
|
|
1607
|
+
const isLastChild = index === root.children.length - 1;
|
|
1608
|
+
if (child.position) {
|
|
1609
|
+
addEmptyParagraphs(child.position.start.line - (isFirstChild ? startLine : startLine + 1));
|
|
1610
|
+
const transformValue = convertNodesDeserialize([child], {}, options);
|
|
1611
|
+
results.push(...transformValue);
|
|
1612
|
+
if (isLastChild) addEmptyParagraphs(root.position.end.line - child.position.end.line - 1);
|
|
1613
|
+
startLine = child.position.end.line;
|
|
1614
|
+
} else {
|
|
1615
|
+
const transformValue = convertNodesDeserialize([child], {}, options);
|
|
1616
|
+
results.push(...transformValue);
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
return results;
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
//#endregion
|
|
1623
|
+
//#region src/lib/deserializer/deserializeMd.ts
|
|
1624
|
+
const markdownToAstProcessor = (editor, data, options) => {
|
|
1625
|
+
const mergedOptions = getMergedOptionsDeserialize(editor, options);
|
|
1626
|
+
return unified().use(remarkParse).use(mergedOptions.remarkPlugins ?? []).parse(data);
|
|
1627
|
+
};
|
|
1628
|
+
const markdownToSlateNodes = (editor, data, options) => {
|
|
1629
|
+
const processedData = options?.withoutMdx ? data : htmlToJsx(data);
|
|
1630
|
+
const mergedOptions = getMergedOptionsDeserialize(editor, options);
|
|
1631
|
+
const toSlateProcessor = unified().use(remarkParse).use(mergedOptions.remarkPlugins ?? []).use(remarkToSlate, mergedOptions);
|
|
1632
|
+
if (options?.memoize) return parseMarkdownBlocks(processedData, options.parser).flatMap((token) => {
|
|
1633
|
+
if (token.type === "space") return {
|
|
1634
|
+
...editor.api.create.block(),
|
|
1635
|
+
_memo: token.raw
|
|
1636
|
+
};
|
|
1637
|
+
return toSlateProcessor.processSync(token.raw).result.map((result) => ({
|
|
1638
|
+
_memo: token.raw,
|
|
1639
|
+
...result
|
|
1640
|
+
}));
|
|
1641
|
+
});
|
|
1642
|
+
return toSlateProcessor.processSync(processedData).result;
|
|
1643
|
+
};
|
|
1644
|
+
const deserializeMd = (editor, data, options) => {
|
|
1645
|
+
let output = null;
|
|
1646
|
+
try {
|
|
1647
|
+
output = markdownToSlateNodes(editor, data, options);
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
options?.onError?.(error);
|
|
1650
|
+
if (!options?.withoutMdx) output = markdownToSlateNodesSafely(editor, data, options);
|
|
1651
|
+
}
|
|
1652
|
+
if (!output) return [];
|
|
1653
|
+
return output.map((item) => TextApi.isText(item) ? {
|
|
1654
|
+
children: [item],
|
|
1655
|
+
type: getPluginKey(editor, KEYS.p) ?? KEYS.p
|
|
1656
|
+
} : item);
|
|
1657
|
+
};
|
|
1658
|
+
const remarkToSlate = function(options = {}) {
|
|
1659
|
+
this.compiler = (node) => mdastToSlate(node, options);
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region src/lib/deserializer/utils/splitIncompleteMdx.ts
|
|
1664
|
+
/** Check if character is valid for tag name: A-Z / a-z / 0-9 / - _ : */
|
|
1665
|
+
const isNameChar = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 45 || c === 95 || c === 58;
|
|
1666
|
+
const splitIncompleteMdx = (data) => {
|
|
1667
|
+
const stack = [];
|
|
1668
|
+
const len = data.length;
|
|
1669
|
+
let i = 0;
|
|
1670
|
+
let cutPos = -1;
|
|
1671
|
+
while (i < len) {
|
|
1672
|
+
if (data.codePointAt(i) !== 60) {
|
|
1673
|
+
i++;
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
const tagStart = i;
|
|
1677
|
+
i++;
|
|
1678
|
+
if (i >= len) {
|
|
1679
|
+
cutPos = tagStart;
|
|
1680
|
+
break;
|
|
1681
|
+
}
|
|
1682
|
+
let closing = false;
|
|
1683
|
+
if (data[i] === "/") {
|
|
1684
|
+
closing = true;
|
|
1685
|
+
i++;
|
|
1686
|
+
}
|
|
1687
|
+
const nameStart = i;
|
|
1688
|
+
while (i < len && isNameChar(data.codePointAt(i))) i++;
|
|
1689
|
+
if (nameStart === i) {
|
|
1690
|
+
cutPos = tagStart;
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
const tagName = data.slice(nameStart, i).toLowerCase();
|
|
1694
|
+
let inQuote = null;
|
|
1695
|
+
let selfClosing = false;
|
|
1696
|
+
while (i < len) {
|
|
1697
|
+
const ch = data[i];
|
|
1698
|
+
if (inQuote) {
|
|
1699
|
+
if (ch === inQuote) inQuote = null;
|
|
1700
|
+
} else if (ch === "\"" || ch === "'") inQuote = ch;
|
|
1701
|
+
else if (ch === ">") {
|
|
1702
|
+
selfClosing = data[i - 1] === "/";
|
|
1703
|
+
i++;
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
i++;
|
|
1707
|
+
}
|
|
1708
|
+
if (i >= len) {
|
|
1709
|
+
cutPos = tagStart;
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
if (selfClosing) continue;
|
|
1713
|
+
if (closing) {
|
|
1714
|
+
for (let j = stack.length - 1; j >= 0; j--) if (stack[j].name === tagName) {
|
|
1715
|
+
stack.splice(j, 1);
|
|
1716
|
+
break;
|
|
1717
|
+
}
|
|
1718
|
+
} else stack.push({
|
|
1719
|
+
name: tagName,
|
|
1720
|
+
pos: tagStart
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
if (stack.length > 0) {
|
|
1724
|
+
const firstUnmatched = stack[0].pos;
|
|
1725
|
+
cutPos = cutPos === -1 ? firstUnmatched : Math.min(cutPos, firstUnmatched);
|
|
1726
|
+
}
|
|
1727
|
+
return cutPos === -1 ? data : [data.slice(0, cutPos), data.slice(cutPos)];
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region src/lib/deserializer/utils/markdownToSlateNodesSafely.ts
|
|
1732
|
+
const markdownToSlateNodesSafely = (editor, data, options) => {
|
|
1733
|
+
const result = splitIncompleteMdx(data);
|
|
1734
|
+
if (!Array.isArray(result)) return markdownToSlateNodes(editor, data, {
|
|
1735
|
+
...options,
|
|
1736
|
+
withoutMdx: true
|
|
1737
|
+
});
|
|
1738
|
+
const [completeString, incompleteString] = result;
|
|
1739
|
+
const incompleteNodes = deserializeInlineMd(editor, incompleteString, {
|
|
1740
|
+
...options,
|
|
1741
|
+
withoutMdx: true
|
|
1742
|
+
});
|
|
1743
|
+
const completeNodes = markdownToSlateNodes(editor, completeString, options);
|
|
1744
|
+
if (incompleteNodes.length === 0) return completeNodes;
|
|
1745
|
+
const newBlock = {
|
|
1746
|
+
children: incompleteNodes,
|
|
1747
|
+
type: getPluginType(editor, KEYS.p)
|
|
1748
|
+
};
|
|
1749
|
+
if (completeNodes.length === 0) return [newBlock];
|
|
1750
|
+
const lastBlock = completeNodes.at(-1);
|
|
1751
|
+
if (ElementApi.isElement(lastBlock) && editor.api.isVoid(lastBlock)) return [...completeNodes, newBlock];
|
|
1752
|
+
if (ElementApi.isElement(lastBlock) && lastBlock?.children) {
|
|
1753
|
+
lastBlock.children.push(...incompleteNodes);
|
|
1754
|
+
return completeNodes;
|
|
1755
|
+
}
|
|
1756
|
+
return completeNodes;
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
//#endregion
|
|
1760
|
+
//#region src/lib/deserializer/utils/parseMarkdownBlocks.ts
|
|
1761
|
+
const parseMarkdownBlocks = (content, { exclude = ["space"], trim = true } = {}) => {
|
|
1762
|
+
let tokens = [...marked.lexer(content)];
|
|
1763
|
+
if (exclude.length > 0) tokens = tokens.filter((token) => !exclude.includes(token.type));
|
|
1764
|
+
if (trim) tokens = tokens.map((token) => ({
|
|
1765
|
+
...token,
|
|
1766
|
+
raw: token.raw.trimEnd()
|
|
1767
|
+
}));
|
|
1768
|
+
return tokens;
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
//#endregion
|
|
1772
|
+
//#region src/lib/deserializer/convertNodesDeserialize.ts
|
|
1773
|
+
const convertNodesDeserialize = (nodes, deco, options) => {
|
|
1774
|
+
return nodes.reduce((acc, node) => {
|
|
1775
|
+
if (shouldIncludeNode(node, options)) acc.push(...buildSlateNode(node, deco, options));
|
|
1776
|
+
return acc;
|
|
1777
|
+
}, []);
|
|
1778
|
+
};
|
|
1779
|
+
const buildSlateNode = (mdastNode, deco, options) => {
|
|
1780
|
+
/** Handle custom mdx nodes */
|
|
1781
|
+
if (mdastNode.type === "mdxJsxTextElement" || mdastNode.type === "mdxJsxFlowElement") {
|
|
1782
|
+
const result = customMdxDeserialize(mdastNode, deco, options);
|
|
1783
|
+
return Array.isArray(result) ? result : [result];
|
|
1784
|
+
}
|
|
1785
|
+
const nodeParser = getDeserializerByKey(mdastToPlate(options.editor, mdastNode.type), options);
|
|
1786
|
+
if (nodeParser) {
|
|
1787
|
+
const result = nodeParser(mdastNode, deco, options);
|
|
1788
|
+
return Array.isArray(result) ? result : [result];
|
|
1789
|
+
}
|
|
1790
|
+
return [];
|
|
1791
|
+
};
|
|
1792
|
+
const shouldIncludeNode = (node, options) => {
|
|
1793
|
+
const { allowedNodes, allowNode, disallowedNodes } = options;
|
|
1794
|
+
if (!node.type) return true;
|
|
1795
|
+
const type = mdastToPlate(options.editor, node.type);
|
|
1796
|
+
if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
|
|
1797
|
+
if (allowedNodes) {
|
|
1798
|
+
if (!allowedNodes.includes(type)) return false;
|
|
1799
|
+
} else if (disallowedNodes?.includes(type)) return false;
|
|
1800
|
+
if (allowNode?.deserialize) return allowNode.deserialize({
|
|
1801
|
+
...node,
|
|
1802
|
+
type
|
|
1803
|
+
});
|
|
1804
|
+
return true;
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
//#endregion
|
|
1808
|
+
//#region src/lib/deserializer/convertChildrenDeserialize.ts
|
|
1809
|
+
const convertChildrenDeserialize = (children, deco, options) => {
|
|
1810
|
+
if (children.length === 0) return [{ text: "" }];
|
|
1811
|
+
return convertNodesDeserialize(children, deco, options);
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
//#endregion
|
|
1815
|
+
//#region src/lib/deserializer/convertTextsDeserialize.ts
|
|
1816
|
+
const convertTextsDeserialize = (mdastNode, deco, options) => mdastNode.children.reduce((acc, n) => {
|
|
1817
|
+
const key = mdastToPlate(options.editor, mdastNode.type);
|
|
1818
|
+
const type = getPluginType(options.editor, key);
|
|
1819
|
+
acc.push(...buildSlateNode(n, {
|
|
1820
|
+
...deco,
|
|
1821
|
+
[type]: true
|
|
1822
|
+
}, options));
|
|
1823
|
+
return acc;
|
|
1824
|
+
}, []);
|
|
1825
|
+
|
|
1826
|
+
//#endregion
|
|
1827
|
+
//#region src/lib/MarkdownPlugin.ts
|
|
1828
|
+
const MarkdownPlugin = createTSlatePlugin({
|
|
1829
|
+
key: KEYS.markdown,
|
|
1830
|
+
options: {
|
|
1831
|
+
allowedNodes: null,
|
|
1832
|
+
disallowedNodes: null,
|
|
1833
|
+
plainMarks: null,
|
|
1834
|
+
remarkPlugins: [],
|
|
1835
|
+
remarkStringifyOptions: null,
|
|
1836
|
+
rules: null
|
|
1837
|
+
}
|
|
1838
|
+
}).extendApi(({ editor }) => ({
|
|
1839
|
+
deserialize: bindFirst(deserializeMd, editor),
|
|
1840
|
+
deserializeInline: bindFirst(deserializeInlineMd, editor),
|
|
1841
|
+
serialize: bindFirst(serializeMd, editor)
|
|
1842
|
+
})).extend(({ api }) => ({ parser: {
|
|
1843
|
+
format: "text/plain",
|
|
1844
|
+
deserialize: ({ data }) => api.markdown.deserialize(data),
|
|
1845
|
+
query: ({ data, dataTransfer }) => {
|
|
1846
|
+
if (dataTransfer.getData("text/html")) return false;
|
|
1847
|
+
const { files } = dataTransfer;
|
|
1848
|
+
if (!files?.length && isUrl(data)) return false;
|
|
1849
|
+
return true;
|
|
1850
|
+
}
|
|
1851
|
+
} }));
|
|
1852
|
+
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region src/lib/plugins/remarkMdx.ts
|
|
1855
|
+
const remarkMdx = tagRemarkPlugin(baseRemarkMdx, REMARK_MDX_TAG);
|
|
1856
|
+
|
|
1857
|
+
//#endregion
|
|
1858
|
+
//#region src/lib/plugins/remarkMention.ts
|
|
1859
|
+
/**
|
|
1860
|
+
* A remark plugin that converts @username patterns and [display
|
|
1861
|
+
* text](mention:id) patterns in text nodes into mention nodes. This plugin runs
|
|
1862
|
+
* after remark-gfm and transforms mention patterns into special mention nodes
|
|
1863
|
+
* that can be later converted into Plate mention elements.
|
|
1864
|
+
*
|
|
1865
|
+
* Supports two formats:
|
|
1866
|
+
*
|
|
1867
|
+
* - @username - Simple mention format (no spaces allowed)
|
|
1868
|
+
* - [display text](mention:id) - Markdown link-style format (supports spaces)
|
|
1869
|
+
*/
|
|
1870
|
+
const remarkMention = () => (tree) => {
|
|
1871
|
+
visit(tree, "link", (node, index, parent) => {
|
|
1872
|
+
if (!parent || typeof index !== "number") return;
|
|
1873
|
+
if (node.url?.startsWith("mention:")) {
|
|
1874
|
+
let username = node.url.slice(8);
|
|
1875
|
+
username = decodeURIComponent(username);
|
|
1876
|
+
const displayText = node.children?.[0]?.value || username;
|
|
1877
|
+
const mentionNode = {
|
|
1878
|
+
children: [{
|
|
1879
|
+
type: "text",
|
|
1880
|
+
value: displayText
|
|
1881
|
+
}],
|
|
1882
|
+
displayText,
|
|
1883
|
+
type: "mention",
|
|
1884
|
+
username
|
|
1885
|
+
};
|
|
1886
|
+
parent.children[index] = mentionNode;
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
visit(tree, "text", (node, index, parent) => {
|
|
1890
|
+
if (!parent || typeof index !== "number") return;
|
|
1891
|
+
if (parent.type === "link") return;
|
|
1892
|
+
const atMentionPattern = /(?:^|\s)@([a-zA-Z0-9_-]+)(?=[\s.,;:!?)]|$)/g;
|
|
1893
|
+
const parts = [];
|
|
1894
|
+
let lastIndex = 0;
|
|
1895
|
+
const text = node.value;
|
|
1896
|
+
const allMatches = [];
|
|
1897
|
+
let match;
|
|
1898
|
+
while (true) {
|
|
1899
|
+
match = atMentionPattern.exec(text);
|
|
1900
|
+
if (!match) break;
|
|
1901
|
+
const mentionStart = match[0].startsWith(" ") ? match.index + 1 : match.index;
|
|
1902
|
+
const mentionEnd = mentionStart + match[0].length - (match[0].startsWith(" ") ? 1 : 0);
|
|
1903
|
+
allMatches.push({
|
|
1904
|
+
end: mentionEnd,
|
|
1905
|
+
node: {
|
|
1906
|
+
children: [{
|
|
1907
|
+
type: "text",
|
|
1908
|
+
value: `@${match[1]}`
|
|
1909
|
+
}],
|
|
1910
|
+
type: "mention",
|
|
1911
|
+
username: match[1]
|
|
1912
|
+
},
|
|
1913
|
+
start: mentionStart
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
allMatches.sort((a, b) => a.start - b.start);
|
|
1917
|
+
for (const matchInfo of allMatches) {
|
|
1918
|
+
if (matchInfo.start > lastIndex) parts.push({
|
|
1919
|
+
type: "text",
|
|
1920
|
+
value: text.slice(lastIndex, matchInfo.start)
|
|
1921
|
+
});
|
|
1922
|
+
parts.push(matchInfo.node);
|
|
1923
|
+
lastIndex = matchInfo.end;
|
|
1924
|
+
}
|
|
1925
|
+
if (lastIndex < text.length) parts.push({
|
|
1926
|
+
type: "text",
|
|
1927
|
+
value: text.slice(lastIndex)
|
|
1928
|
+
});
|
|
1929
|
+
if (parts.length > 0) parent.children.splice(index, 1, ...parts);
|
|
1930
|
+
});
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
//#endregion
|
|
1934
|
+
export { MarkdownPlugin, REMARK_MDX_TAG, basicMarkdownMarks, buildMdastNode, buildRules, buildSlateNode, columnRules, convertChildrenDeserialize, convertNodesDeserialize, convertNodesSerialize, convertTextsDeserialize, convertTextsSerialize, customMdxDeserialize, defaultRules, deserializeInlineMd, deserializeMd, fontRules, getCustomMark, getDeserializerByKey, getMergedOptionsDeserialize, getMergedOptionsSerialize, getRemarkPluginsWithoutMdx, getSerializerByKey, getStyleValue, htmlToJsx, listToMdastTree, markdownToAstProcessor, markdownToSlateNodes, markdownToSlateNodesSafely, mdastToPlate, mdastToSlate, mediaRules, parseAttributes, parseMarkdownBlocks, plateToMdast, propsToAttributes, remarkMdx, remarkMention, serializeInlineMd, serializeMd, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, unreachable, wrapWithBlockId };
|
|
1935
|
+
//# sourceMappingURL=index.js.map
|