@lexical/markdown 0.8.1 → 0.9.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/LexicalMarkdown.dev.js +218 -87
- package/package.json +8 -8
package/LexicalMarkdown.dev.js
CHANGED
|
@@ -20,16 +20,20 @@ var link = require('@lexical/link');
|
|
|
20
20
|
* LICENSE file in the root directory of this source tree.
|
|
21
21
|
*
|
|
22
22
|
*/
|
|
23
|
+
|
|
23
24
|
function indexBy(list, callback) {
|
|
24
25
|
const index = {};
|
|
26
|
+
|
|
25
27
|
for (const item of list) {
|
|
26
28
|
const key = callback(item);
|
|
29
|
+
|
|
27
30
|
if (index[key]) {
|
|
28
31
|
index[key].push(item);
|
|
29
32
|
} else {
|
|
30
33
|
index[key] = [item];
|
|
31
34
|
}
|
|
32
35
|
}
|
|
36
|
+
|
|
33
37
|
return index;
|
|
34
38
|
}
|
|
35
39
|
function transformersByType(transformers) {
|
|
@@ -50,30 +54,35 @@ const PUNCTUATION_OR_SPACE = /[!-/:-@[-`{-~\s]/;
|
|
|
50
54
|
*
|
|
51
55
|
*/
|
|
52
56
|
function createMarkdownExport(transformers) {
|
|
53
|
-
const byType = transformersByType(transformers);
|
|
54
|
-
|
|
55
|
-
// Export only uses text formats that are responsible for single format
|
|
57
|
+
const byType = transformersByType(transformers); // Export only uses text formats that are responsible for single format
|
|
56
58
|
// e.g. it will filter out *** (bold, italic) and instead use separate ** and *
|
|
59
|
+
|
|
57
60
|
const textFormatTransformers = byType.textFormat.filter(transformer => transformer.format.length === 1);
|
|
58
61
|
return () => {
|
|
59
62
|
const output = [];
|
|
60
63
|
const children = lexical.$getRoot().getChildren();
|
|
64
|
+
|
|
61
65
|
for (const child of children) {
|
|
62
66
|
const result = exportTopLevelElements(child, byType.element, textFormatTransformers, byType.textMatch);
|
|
67
|
+
|
|
63
68
|
if (result != null) {
|
|
64
69
|
output.push(result);
|
|
65
70
|
}
|
|
66
71
|
}
|
|
72
|
+
|
|
67
73
|
return output.join('\n\n');
|
|
68
74
|
};
|
|
69
75
|
}
|
|
76
|
+
|
|
70
77
|
function exportTopLevelElements(node, elementTransformers, textTransformersIndex, textMatchTransformers) {
|
|
71
78
|
for (const transformer of elementTransformers) {
|
|
72
79
|
const result = transformer.export(node, _node => exportChildren(_node, textTransformersIndex, textMatchTransformers));
|
|
80
|
+
|
|
73
81
|
if (result != null) {
|
|
74
82
|
return result;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
85
|
+
|
|
77
86
|
if (lexical.$isElementNode(node)) {
|
|
78
87
|
return exportChildren(node, textTransformersIndex, textMatchTransformers);
|
|
79
88
|
} else if (lexical.$isDecoratorNode(node)) {
|
|
@@ -82,17 +91,21 @@ function exportTopLevelElements(node, elementTransformers, textTransformersIndex
|
|
|
82
91
|
return null;
|
|
83
92
|
}
|
|
84
93
|
}
|
|
94
|
+
|
|
85
95
|
function exportChildren(node, textTransformersIndex, textMatchTransformers) {
|
|
86
96
|
const output = [];
|
|
87
97
|
const children = node.getChildren();
|
|
98
|
+
|
|
88
99
|
mainLoop: for (const child of children) {
|
|
89
100
|
for (const transformer of textMatchTransformers) {
|
|
90
101
|
const result = transformer.export(child, parentNode => exportChildren(parentNode, textTransformersIndex, textMatchTransformers), (textNode, textContent) => exportTextFormat(textNode, textContent, textTransformersIndex));
|
|
102
|
+
|
|
91
103
|
if (result != null) {
|
|
92
104
|
output.push(result);
|
|
93
105
|
continue mainLoop;
|
|
94
106
|
}
|
|
95
107
|
}
|
|
108
|
+
|
|
96
109
|
if (lexical.$isLineBreakNode(child)) {
|
|
97
110
|
output.push('\n');
|
|
98
111
|
} else if (lexical.$isTextNode(child)) {
|
|
@@ -103,8 +116,10 @@ function exportChildren(node, textTransformersIndex, textMatchTransformers) {
|
|
|
103
116
|
output.push(child.getTextContent());
|
|
104
117
|
}
|
|
105
118
|
}
|
|
119
|
+
|
|
106
120
|
return output.join('');
|
|
107
121
|
}
|
|
122
|
+
|
|
108
123
|
function exportTextFormat(node, textContent, textTransformers) {
|
|
109
124
|
// This function handles the case of a string looking like this: " foo "
|
|
110
125
|
// Where it would be invalid markdown to generate: "** foo **"
|
|
@@ -113,61 +128,74 @@ function exportTextFormat(node, textContent, textTransformers) {
|
|
|
113
128
|
const frozenString = textContent.trim();
|
|
114
129
|
let output = frozenString;
|
|
115
130
|
const applied = new Set();
|
|
131
|
+
|
|
116
132
|
for (const transformer of textTransformers) {
|
|
117
133
|
const format = transformer.format[0];
|
|
118
134
|
const tag = transformer.tag;
|
|
135
|
+
|
|
119
136
|
if (hasFormat(node, format) && !applied.has(format)) {
|
|
120
137
|
// Multiple tags might be used for the same format (*, _)
|
|
121
|
-
applied.add(format);
|
|
122
|
-
|
|
138
|
+
applied.add(format); // Prevent adding opening tag is already opened by the previous sibling
|
|
139
|
+
|
|
123
140
|
const previousNode = getTextSibling(node, true);
|
|
141
|
+
|
|
124
142
|
if (!hasFormat(previousNode, format)) {
|
|
125
143
|
output = tag + output;
|
|
126
|
-
}
|
|
144
|
+
} // Prevent adding closing tag if next sibling will do it
|
|
145
|
+
|
|
127
146
|
|
|
128
|
-
// Prevent adding closing tag if next sibling will do it
|
|
129
147
|
const nextNode = getTextSibling(node, false);
|
|
148
|
+
|
|
130
149
|
if (!hasFormat(nextNode, format)) {
|
|
131
150
|
output += tag;
|
|
132
151
|
}
|
|
133
152
|
}
|
|
134
|
-
}
|
|
153
|
+
} // Replace trimmed version of textContent ensuring surrounding whitespace is not modified
|
|
135
154
|
|
|
136
|
-
// Replace trimmed version of textContent ensuring surrounding whitespace is not modified
|
|
137
|
-
return textContent.replace(frozenString, output);
|
|
138
|
-
}
|
|
139
155
|
|
|
140
|
-
|
|
156
|
+
return textContent.replace(frozenString, output);
|
|
157
|
+
} // Get next or previous text sibling a text node, including cases
|
|
141
158
|
// when it's a child of inline element (e.g. link)
|
|
159
|
+
|
|
160
|
+
|
|
142
161
|
function getTextSibling(node, backward) {
|
|
143
162
|
let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
|
|
163
|
+
|
|
144
164
|
if (!sibling) {
|
|
145
165
|
const parent = node.getParentOrThrow();
|
|
166
|
+
|
|
146
167
|
if (parent.isInline()) {
|
|
147
168
|
sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
|
|
148
169
|
}
|
|
149
170
|
}
|
|
171
|
+
|
|
150
172
|
while (sibling) {
|
|
151
173
|
if (lexical.$isElementNode(sibling)) {
|
|
152
174
|
if (!sibling.isInline()) {
|
|
153
175
|
break;
|
|
154
176
|
}
|
|
177
|
+
|
|
155
178
|
const descendant = backward ? sibling.getLastDescendant() : sibling.getFirstDescendant();
|
|
179
|
+
|
|
156
180
|
if (lexical.$isTextNode(descendant)) {
|
|
157
181
|
return descendant;
|
|
158
182
|
} else {
|
|
159
183
|
sibling = backward ? sibling.getPreviousSibling() : sibling.getNextSibling();
|
|
160
184
|
}
|
|
161
185
|
}
|
|
186
|
+
|
|
162
187
|
if (lexical.$isTextNode(sibling)) {
|
|
163
188
|
return sibling;
|
|
164
189
|
}
|
|
190
|
+
|
|
165
191
|
if (!lexical.$isElementNode(sibling)) {
|
|
166
192
|
return null;
|
|
167
193
|
}
|
|
168
194
|
}
|
|
195
|
+
|
|
169
196
|
return null;
|
|
170
197
|
}
|
|
198
|
+
|
|
171
199
|
function hasFormat(node, format) {
|
|
172
200
|
return lexical.$isTextNode(node) && node.hasFormat(format);
|
|
173
201
|
}
|
|
@@ -179,7 +207,6 @@ function hasFormat(node, format) {
|
|
|
179
207
|
* LICENSE file in the root directory of this source tree.
|
|
180
208
|
*
|
|
181
209
|
*/
|
|
182
|
-
|
|
183
210
|
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
184
211
|
|
|
185
212
|
/**
|
|
@@ -194,12 +221,10 @@ CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
|
194
221
|
CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
195
222
|
CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
|
|
196
223
|
const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
|
|
197
|
-
const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
198
|
-
|
|
199
|
-
// Keep these in case we need to use them in the future.
|
|
224
|
+
const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // Keep these in case we need to use them in the future.
|
|
200
225
|
// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
|
|
201
|
-
|
|
202
|
-
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
|
|
226
|
+
|
|
227
|
+
const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent); // export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
|
|
203
228
|
|
|
204
229
|
const IS_APPLE_WEBKIT = CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && !IS_CHROME;
|
|
205
230
|
|
|
@@ -220,72 +245,86 @@ function createMarkdownImport(transformers) {
|
|
|
220
245
|
const linesLength = lines.length;
|
|
221
246
|
const root = lexical.$getRoot();
|
|
222
247
|
root.clear();
|
|
248
|
+
|
|
223
249
|
for (let i = 0; i < linesLength; i++) {
|
|
224
|
-
const lineText = lines[i];
|
|
225
|
-
// Codeblocks are processed first as anything inside such block
|
|
250
|
+
const lineText = lines[i]; // Codeblocks are processed first as anything inside such block
|
|
226
251
|
// is ignored for further processing
|
|
227
252
|
// TODO:
|
|
228
253
|
// Abstract it to be dynamic as other transformers (add multiline match option)
|
|
254
|
+
|
|
229
255
|
const [codeBlockNode, shiftedIndex] = importCodeBlock(lines, i, root);
|
|
256
|
+
|
|
230
257
|
if (codeBlockNode != null) {
|
|
231
258
|
i = shiftedIndex;
|
|
232
259
|
continue;
|
|
233
260
|
}
|
|
234
|
-
importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch);
|
|
235
|
-
}
|
|
236
261
|
|
|
237
|
-
|
|
262
|
+
importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch);
|
|
263
|
+
} // Removing empty paragraphs as md does not really
|
|
238
264
|
// allow empty lines and uses them as dilimiter
|
|
265
|
+
|
|
266
|
+
|
|
239
267
|
const children = root.getChildren();
|
|
268
|
+
|
|
240
269
|
for (const child of children) {
|
|
241
270
|
if (isEmptyParagraph(child)) {
|
|
242
271
|
child.remove();
|
|
243
272
|
}
|
|
244
273
|
}
|
|
274
|
+
|
|
245
275
|
root.selectEnd();
|
|
246
276
|
};
|
|
247
277
|
}
|
|
278
|
+
|
|
248
279
|
function isEmptyParagraph(node) {
|
|
249
280
|
if (!lexical.$isParagraphNode(node)) {
|
|
250
281
|
return false;
|
|
251
282
|
}
|
|
283
|
+
|
|
252
284
|
const firstChild = node.getFirstChild();
|
|
253
285
|
return firstChild == null || node.getChildrenSize() === 1 && lexical.$isTextNode(firstChild) && MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent());
|
|
254
286
|
}
|
|
287
|
+
|
|
255
288
|
function importBlocks(lineText, rootNode, elementTransformers, textFormatTransformersIndex, textMatchTransformers) {
|
|
256
289
|
const lineTextTrimmed = lineText.trim();
|
|
257
290
|
const textNode = lexical.$createTextNode(lineTextTrimmed);
|
|
258
291
|
const elementNode = lexical.$createParagraphNode();
|
|
259
292
|
elementNode.append(textNode);
|
|
260
293
|
rootNode.append(elementNode);
|
|
294
|
+
|
|
261
295
|
for (const {
|
|
262
296
|
regExp,
|
|
263
297
|
replace
|
|
264
298
|
} of elementTransformers) {
|
|
265
299
|
const match = lineText.match(regExp);
|
|
300
|
+
|
|
266
301
|
if (match) {
|
|
267
302
|
textNode.setTextContent(lineText.slice(match[0].length));
|
|
268
303
|
replace(elementNode, [textNode], match, true);
|
|
269
304
|
break;
|
|
270
305
|
}
|
|
271
306
|
}
|
|
272
|
-
importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers);
|
|
273
307
|
|
|
274
|
-
// If no transformer found and we left with original paragraph node
|
|
308
|
+
importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers); // If no transformer found and we left with original paragraph node
|
|
275
309
|
// can check if its content can be appended to the previous node
|
|
276
310
|
// if it's a paragraph, quote or list
|
|
311
|
+
|
|
277
312
|
if (elementNode.isAttached() && lineTextTrimmed.length > 0) {
|
|
278
313
|
const previousNode = elementNode.getPreviousSibling();
|
|
314
|
+
|
|
279
315
|
if (lexical.$isParagraphNode(previousNode) || richText.$isQuoteNode(previousNode) || list.$isListNode(previousNode)) {
|
|
280
316
|
let targetNode = previousNode;
|
|
317
|
+
|
|
281
318
|
if (list.$isListNode(previousNode)) {
|
|
282
319
|
const lastDescendant = previousNode.getLastDescendant();
|
|
320
|
+
|
|
283
321
|
if (lastDescendant == null) {
|
|
284
322
|
targetNode = null;
|
|
285
323
|
} else {
|
|
286
324
|
targetNode = utils.$findMatchingParent(lastDescendant, list.$isListItemNode);
|
|
287
325
|
}
|
|
288
326
|
}
|
|
327
|
+
|
|
289
328
|
if (targetNode != null && targetNode.getTextContentSize() > 0) {
|
|
290
329
|
targetNode.splice(targetNode.getChildrenSize(), 0, [lexical.$createLineBreakNode(), ...elementNode.getChildren()]);
|
|
291
330
|
elementNode.remove();
|
|
@@ -293,13 +332,17 @@ function importBlocks(lineText, rootNode, elementTransformers, textFormatTransfo
|
|
|
293
332
|
}
|
|
294
333
|
}
|
|
295
334
|
}
|
|
335
|
+
|
|
296
336
|
function importCodeBlock(lines, startLineIndex, rootNode) {
|
|
297
337
|
const openMatch = lines[startLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
338
|
+
|
|
298
339
|
if (openMatch) {
|
|
299
340
|
let endLineIndex = startLineIndex;
|
|
300
341
|
const linesLength = lines.length;
|
|
342
|
+
|
|
301
343
|
while (++endLineIndex < linesLength) {
|
|
302
344
|
const closeMatch = lines[endLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
345
|
+
|
|
303
346
|
if (closeMatch) {
|
|
304
347
|
const codeBlockNode = code.$createCodeNode(openMatch[1]);
|
|
305
348
|
const textNode = lexical.$createTextNode(lines.slice(startLineIndex + 1, endLineIndex).join('\n'));
|
|
@@ -309,133 +352,157 @@ function importCodeBlock(lines, startLineIndex, rootNode) {
|
|
|
309
352
|
}
|
|
310
353
|
}
|
|
311
354
|
}
|
|
312
|
-
return [null, startLineIndex];
|
|
313
|
-
}
|
|
314
355
|
|
|
315
|
-
|
|
356
|
+
return [null, startLineIndex];
|
|
357
|
+
} // Processing text content and replaces text format tags.
|
|
316
358
|
// It takes outermost tag match and its content, creates text node with
|
|
317
359
|
// format based on tag and then recursively executed over node's content
|
|
318
360
|
//
|
|
319
361
|
// E.g. for "*Hello **world**!*" string it will create text node with
|
|
320
362
|
// "Hello **world**!" content and italic format and run recursively over
|
|
321
363
|
// its content to transform "**world**" part
|
|
364
|
+
|
|
365
|
+
|
|
322
366
|
function importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers) {
|
|
323
367
|
const textContent = textNode.getTextContent();
|
|
324
368
|
const match = findOutermostMatch(textContent, textFormatTransformersIndex);
|
|
369
|
+
|
|
325
370
|
if (!match) {
|
|
326
371
|
// Once text format processing is done run text match transformers, as it
|
|
327
372
|
// only can span within single text node (unline formats that can cover multiple nodes)
|
|
328
373
|
importTextMatchTransformers(textNode, textMatchTransformers);
|
|
329
374
|
return;
|
|
330
375
|
}
|
|
331
|
-
let currentNode, remainderNode, leadingNode;
|
|
332
376
|
|
|
333
|
-
// If matching full content there's no need to run splitText and can reuse existing textNode
|
|
377
|
+
let currentNode, remainderNode, leadingNode; // If matching full content there's no need to run splitText and can reuse existing textNode
|
|
334
378
|
// to update its content and apply format. E.g. for **_Hello_** string after applying bold
|
|
335
379
|
// format (**) it will reuse the same text node to apply italic (_)
|
|
380
|
+
|
|
336
381
|
if (match[0] === textContent) {
|
|
337
382
|
currentNode = textNode;
|
|
338
383
|
} else {
|
|
339
384
|
const startIndex = match.index || 0;
|
|
340
385
|
const endIndex = startIndex + match[0].length;
|
|
386
|
+
|
|
341
387
|
if (startIndex === 0) {
|
|
342
388
|
[currentNode, remainderNode] = textNode.splitText(endIndex);
|
|
343
389
|
} else {
|
|
344
390
|
[leadingNode, currentNode, remainderNode] = textNode.splitText(startIndex, endIndex);
|
|
345
391
|
}
|
|
346
392
|
}
|
|
393
|
+
|
|
347
394
|
currentNode.setTextContent(match[2]);
|
|
348
395
|
const transformer = textFormatTransformersIndex.transformersByTag[match[1]];
|
|
396
|
+
|
|
349
397
|
if (transformer) {
|
|
350
398
|
for (const format of transformer.format) {
|
|
351
399
|
if (!currentNode.hasFormat(format)) {
|
|
352
400
|
currentNode.toggleFormat(format);
|
|
353
401
|
}
|
|
354
402
|
}
|
|
355
|
-
}
|
|
403
|
+
} // Recursively run over inner text if it's not inline code
|
|
404
|
+
|
|
356
405
|
|
|
357
|
-
// Recursively run over inner text if it's not inline code
|
|
358
406
|
if (!currentNode.hasFormat('code')) {
|
|
359
407
|
importTextFormatTransformers(currentNode, textFormatTransformersIndex, textMatchTransformers);
|
|
360
|
-
}
|
|
408
|
+
} // Run over leading/remaining text if any
|
|
409
|
+
|
|
361
410
|
|
|
362
|
-
// Run over leading/remaining text if any
|
|
363
411
|
if (leadingNode) {
|
|
364
412
|
importTextFormatTransformers(leadingNode, textFormatTransformersIndex, textMatchTransformers);
|
|
365
413
|
}
|
|
414
|
+
|
|
366
415
|
if (remainderNode) {
|
|
367
416
|
importTextFormatTransformers(remainderNode, textFormatTransformersIndex, textMatchTransformers);
|
|
368
417
|
}
|
|
369
418
|
}
|
|
419
|
+
|
|
370
420
|
function importTextMatchTransformers(textNode_, textMatchTransformers) {
|
|
371
421
|
let textNode = textNode_;
|
|
422
|
+
|
|
372
423
|
mainLoop: while (textNode) {
|
|
373
424
|
for (const transformer of textMatchTransformers) {
|
|
374
425
|
const match = textNode.getTextContent().match(transformer.importRegExp);
|
|
426
|
+
|
|
375
427
|
if (!match) {
|
|
376
428
|
continue;
|
|
377
429
|
}
|
|
430
|
+
|
|
378
431
|
const startIndex = match.index || 0;
|
|
379
432
|
const endIndex = startIndex + match[0].length;
|
|
380
433
|
let replaceNode, leftTextNode, rightTextNode;
|
|
434
|
+
|
|
381
435
|
if (startIndex === 0) {
|
|
382
436
|
[replaceNode, textNode] = textNode.splitText(endIndex);
|
|
383
437
|
} else {
|
|
384
438
|
[leftTextNode, replaceNode, rightTextNode] = textNode.splitText(startIndex, endIndex);
|
|
385
439
|
}
|
|
440
|
+
|
|
386
441
|
if (leftTextNode) {
|
|
387
442
|
importTextMatchTransformers(leftTextNode, textMatchTransformers);
|
|
388
443
|
}
|
|
444
|
+
|
|
389
445
|
if (rightTextNode) {
|
|
390
446
|
textNode = rightTextNode;
|
|
391
447
|
}
|
|
448
|
+
|
|
392
449
|
transformer.replace(replaceNode, match);
|
|
393
450
|
continue mainLoop;
|
|
394
451
|
}
|
|
452
|
+
|
|
395
453
|
break;
|
|
396
454
|
}
|
|
397
|
-
}
|
|
455
|
+
} // Finds first "<tag>content<tag>" match that is not nested into another tag
|
|
456
|
+
|
|
398
457
|
|
|
399
|
-
// Finds first "<tag>content<tag>" match that is not nested into another tag
|
|
400
458
|
function findOutermostMatch(textContent, textTransformersIndex) {
|
|
401
459
|
const openTagsMatch = textContent.match(textTransformersIndex.openTagsRegExp);
|
|
460
|
+
|
|
402
461
|
if (openTagsMatch == null) {
|
|
403
462
|
return null;
|
|
404
463
|
}
|
|
464
|
+
|
|
405
465
|
for (const match of openTagsMatch) {
|
|
406
466
|
// Open tags reg exp might capture leading space so removing it
|
|
407
467
|
// before using match to find transformer
|
|
408
468
|
const tag = match.replace(/^\s/, '');
|
|
409
469
|
const fullMatchRegExp = textTransformersIndex.fullMatchRegExpByTag[tag];
|
|
470
|
+
|
|
410
471
|
if (fullMatchRegExp == null) {
|
|
411
472
|
continue;
|
|
412
473
|
}
|
|
474
|
+
|
|
413
475
|
const fullMatch = textContent.match(fullMatchRegExp);
|
|
414
476
|
const transformer = textTransformersIndex.transformersByTag[tag];
|
|
477
|
+
|
|
415
478
|
if (fullMatch != null && transformer != null) {
|
|
416
479
|
if (transformer.intraword !== false) {
|
|
417
480
|
return fullMatch;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// For non-intraword transformers checking if it's within a word
|
|
481
|
+
} // For non-intraword transformers checking if it's within a word
|
|
421
482
|
// or surrounded with space/punctuation/newline
|
|
483
|
+
|
|
484
|
+
|
|
422
485
|
const {
|
|
423
486
|
index = 0
|
|
424
487
|
} = fullMatch;
|
|
425
488
|
const beforeChar = textContent[index - 1];
|
|
426
489
|
const afterChar = textContent[index + fullMatch[0].length];
|
|
490
|
+
|
|
427
491
|
if ((!beforeChar || PUNCTUATION_OR_SPACE.test(beforeChar)) && (!afterChar || PUNCTUATION_OR_SPACE.test(afterChar))) {
|
|
428
492
|
return fullMatch;
|
|
429
493
|
}
|
|
430
494
|
}
|
|
431
495
|
}
|
|
496
|
+
|
|
432
497
|
return null;
|
|
433
498
|
}
|
|
499
|
+
|
|
434
500
|
function createTextFormatTransformersIndex(textTransformers) {
|
|
435
501
|
const transformersByTag = {};
|
|
436
502
|
const fullMatchRegExpByTag = {};
|
|
437
503
|
const openTagsRegExp = [];
|
|
438
504
|
const escapeRegExp = `(?<![\\\\])`;
|
|
505
|
+
|
|
439
506
|
for (const transformer of textTransformers) {
|
|
440
507
|
const {
|
|
441
508
|
tag
|
|
@@ -443,12 +510,14 @@ function createTextFormatTransformersIndex(textTransformers) {
|
|
|
443
510
|
transformersByTag[tag] = transformer;
|
|
444
511
|
const tagRegExp = tag.replace(/(\*|\^|\+)/g, '\\$1');
|
|
445
512
|
openTagsRegExp.push(tagRegExp);
|
|
513
|
+
|
|
446
514
|
if (IS_SAFARI || IS_IOS || IS_APPLE_WEBKIT) {
|
|
447
515
|
fullMatchRegExpByTag[tag] = new RegExp(`(${tagRegExp})(?![${tagRegExp}\\s])(.*?[^${tagRegExp}\\s])${tagRegExp}(?!${tagRegExp})`);
|
|
448
516
|
} else {
|
|
449
517
|
fullMatchRegExpByTag[tag] = new RegExp(`(?<![\\\\${tagRegExp}])(${tagRegExp})((\\\\${tagRegExp})?.*?[^${tagRegExp}\\s](\\\\${tagRegExp})?)((?<!\\\\)|(?<=\\\\\\\\))(${tagRegExp})(?![\\\\${tagRegExp}])`);
|
|
450
518
|
}
|
|
451
519
|
}
|
|
520
|
+
|
|
452
521
|
return {
|
|
453
522
|
// Reg exp to find open tag + content + close tag
|
|
454
523
|
fullMatchRegExpByTag,
|
|
@@ -465,27 +534,31 @@ function createTextFormatTransformersIndex(textTransformers) {
|
|
|
465
534
|
* LICENSE file in the root directory of this source tree.
|
|
466
535
|
*
|
|
467
536
|
*/
|
|
537
|
+
|
|
468
538
|
function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers) {
|
|
469
539
|
const grandParentNode = parentNode.getParent();
|
|
540
|
+
|
|
470
541
|
if (!lexical.$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
|
|
471
542
|
return false;
|
|
472
543
|
}
|
|
473
|
-
const textContent = anchorNode.getTextContent();
|
|
474
544
|
|
|
475
|
-
// Checking for anchorOffset position to prevent any checks for cases when caret is too far
|
|
545
|
+
const textContent = anchorNode.getTextContent(); // Checking for anchorOffset position to prevent any checks for cases when caret is too far
|
|
476
546
|
// from a line start to be a part of block-level markdown trigger.
|
|
477
547
|
//
|
|
478
548
|
// TODO:
|
|
479
549
|
// Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
|
|
480
550
|
// since otherwise it won't be a markdown shortcut, but tables are exception
|
|
551
|
+
|
|
481
552
|
if (textContent[anchorOffset - 1] !== ' ') {
|
|
482
553
|
return false;
|
|
483
554
|
}
|
|
555
|
+
|
|
484
556
|
for (const {
|
|
485
557
|
regExp,
|
|
486
558
|
replace
|
|
487
559
|
} of elementTransformers) {
|
|
488
560
|
const match = textContent.match(regExp);
|
|
561
|
+
|
|
489
562
|
if (match && match[0].length === anchorOffset) {
|
|
490
563
|
const nextSiblings = anchorNode.getNextSiblings();
|
|
491
564
|
const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset);
|
|
@@ -495,115 +568,131 @@ function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTra
|
|
|
495
568
|
return true;
|
|
496
569
|
}
|
|
497
570
|
}
|
|
571
|
+
|
|
498
572
|
return false;
|
|
499
573
|
}
|
|
574
|
+
|
|
500
575
|
function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) {
|
|
501
576
|
let textContent = anchorNode.getTextContent();
|
|
502
577
|
const lastChar = textContent[anchorOffset - 1];
|
|
503
578
|
const transformers = transformersByTrigger[lastChar];
|
|
579
|
+
|
|
504
580
|
if (transformers == null) {
|
|
505
581
|
return false;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// If typing in the middle of content, remove the tail to do
|
|
582
|
+
} // If typing in the middle of content, remove the tail to do
|
|
509
583
|
// reg exp match up to a string end (caret position)
|
|
584
|
+
|
|
585
|
+
|
|
510
586
|
if (anchorOffset < textContent.length) {
|
|
511
587
|
textContent = textContent.slice(0, anchorOffset);
|
|
512
588
|
}
|
|
589
|
+
|
|
513
590
|
for (const transformer of transformers) {
|
|
514
591
|
const match = textContent.match(transformer.regExp);
|
|
592
|
+
|
|
515
593
|
if (match === null) {
|
|
516
594
|
continue;
|
|
517
595
|
}
|
|
596
|
+
|
|
518
597
|
const startIndex = match.index || 0;
|
|
519
598
|
const endIndex = startIndex + match[0].length;
|
|
520
599
|
let replaceNode;
|
|
600
|
+
|
|
521
601
|
if (startIndex === 0) {
|
|
522
602
|
[replaceNode] = anchorNode.splitText(endIndex);
|
|
523
603
|
} else {
|
|
524
604
|
[, replaceNode] = anchorNode.splitText(startIndex, endIndex);
|
|
525
605
|
}
|
|
606
|
+
|
|
526
607
|
replaceNode.selectNext(0, 0);
|
|
527
608
|
transformer.replace(replaceNode, match);
|
|
528
609
|
return true;
|
|
529
610
|
}
|
|
611
|
+
|
|
530
612
|
return false;
|
|
531
613
|
}
|
|
614
|
+
|
|
532
615
|
function runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformers) {
|
|
533
616
|
const textContent = anchorNode.getTextContent();
|
|
534
617
|
const closeTagEndIndex = anchorOffset - 1;
|
|
535
|
-
const closeChar = textContent[closeTagEndIndex];
|
|
536
|
-
|
|
618
|
+
const closeChar = textContent[closeTagEndIndex]; // Quick check if we're possibly at the end of inline markdown style
|
|
619
|
+
|
|
537
620
|
const matchers = textFormatTransformers[closeChar];
|
|
621
|
+
|
|
538
622
|
if (!matchers) {
|
|
539
623
|
return false;
|
|
540
624
|
}
|
|
625
|
+
|
|
541
626
|
for (const matcher of matchers) {
|
|
542
627
|
const {
|
|
543
628
|
tag
|
|
544
629
|
} = matcher;
|
|
545
630
|
const tagLength = tag.length;
|
|
546
|
-
const closeTagStartIndex = closeTagEndIndex - tagLength + 1;
|
|
631
|
+
const closeTagStartIndex = closeTagEndIndex - tagLength + 1; // If tag is not single char check if rest of it matches with text content
|
|
547
632
|
|
|
548
|
-
// If tag is not single char check if rest of it matches with text content
|
|
549
633
|
if (tagLength > 1) {
|
|
550
634
|
if (!isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) {
|
|
551
635
|
continue;
|
|
552
636
|
}
|
|
553
|
-
}
|
|
637
|
+
} // Space before closing tag cancels inline markdown
|
|
638
|
+
|
|
554
639
|
|
|
555
|
-
// Space before closing tag cancels inline markdown
|
|
556
640
|
if (textContent[closeTagStartIndex - 1] === ' ') {
|
|
557
641
|
continue;
|
|
558
|
-
}
|
|
642
|
+
} // Some tags can not be used within words, hence should have newline/space/punctuation after it
|
|
643
|
+
|
|
559
644
|
|
|
560
|
-
// Some tags can not be used within words, hence should have newline/space/punctuation after it
|
|
561
645
|
const afterCloseTagChar = textContent[closeTagEndIndex + 1];
|
|
646
|
+
|
|
562
647
|
if (matcher.intraword === false && afterCloseTagChar && !PUNCTUATION_OR_SPACE.test(afterCloseTagChar)) {
|
|
563
648
|
continue;
|
|
564
649
|
}
|
|
650
|
+
|
|
565
651
|
const closeNode = anchorNode;
|
|
566
652
|
let openNode = closeNode;
|
|
567
|
-
let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag);
|
|
568
|
-
|
|
569
|
-
// Go through text node siblings and search for opening tag
|
|
653
|
+
let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag); // Go through text node siblings and search for opening tag
|
|
570
654
|
// if haven't found it within the same text node as closing tag
|
|
655
|
+
|
|
571
656
|
let sibling = openNode;
|
|
657
|
+
|
|
572
658
|
while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) {
|
|
573
659
|
if (lexical.$isLineBreakNode(sibling)) {
|
|
574
660
|
break;
|
|
575
661
|
}
|
|
662
|
+
|
|
576
663
|
if (lexical.$isTextNode(sibling)) {
|
|
577
664
|
const siblingTextContent = sibling.getTextContent();
|
|
578
665
|
openNode = sibling;
|
|
579
666
|
openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag);
|
|
580
667
|
}
|
|
581
|
-
}
|
|
668
|
+
} // Opening tag is not found
|
|
669
|
+
|
|
582
670
|
|
|
583
|
-
// Opening tag is not found
|
|
584
671
|
if (openTagStartIndex < 0) {
|
|
585
672
|
continue;
|
|
586
|
-
}
|
|
673
|
+
} // No content between opening and closing tag
|
|
674
|
+
|
|
587
675
|
|
|
588
|
-
// No content between opening and closing tag
|
|
589
676
|
if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) {
|
|
590
677
|
continue;
|
|
591
|
-
}
|
|
678
|
+
} // Checking longer tags for repeating chars (e.g. *** vs **)
|
|
679
|
+
|
|
592
680
|
|
|
593
|
-
// Checking longer tags for repeating chars (e.g. *** vs **)
|
|
594
681
|
const prevOpenNodeText = openNode.getTextContent();
|
|
682
|
+
|
|
595
683
|
if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) {
|
|
596
684
|
continue;
|
|
597
|
-
}
|
|
685
|
+
} // Some tags can not be used within words, hence should have newline/space/punctuation before it
|
|
686
|
+
|
|
598
687
|
|
|
599
|
-
// Some tags can not be used within words, hence should have newline/space/punctuation before it
|
|
600
688
|
const beforeOpenTagChar = prevOpenNodeText[openTagStartIndex - 1];
|
|
689
|
+
|
|
601
690
|
if (matcher.intraword === false && beforeOpenTagChar && !PUNCTUATION_OR_SPACE.test(beforeOpenTagChar)) {
|
|
602
691
|
continue;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Clean text from opening and closing tags (starting from closing tag
|
|
692
|
+
} // Clean text from opening and closing tags (starting from closing tag
|
|
606
693
|
// to prevent any offset shifts if we start from opening one)
|
|
694
|
+
|
|
695
|
+
|
|
607
696
|
const prevCloseNodeText = closeNode.getTextContent();
|
|
608
697
|
const closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1);
|
|
609
698
|
closeNode.setTextContent(closeNodeText);
|
|
@@ -611,55 +700,62 @@ function runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransform
|
|
|
611
700
|
openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength));
|
|
612
701
|
const selection = lexical.$getSelection();
|
|
613
702
|
const nextSelection = lexical.$createRangeSelection();
|
|
614
|
-
lexical.$setSelection(nextSelection);
|
|
615
|
-
|
|
703
|
+
lexical.$setSelection(nextSelection); // Adjust offset based on deleted chars
|
|
704
|
+
|
|
616
705
|
const newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1;
|
|
617
706
|
nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text');
|
|
618
|
-
nextSelection.focus.set(closeNode.__key, newOffset, 'text');
|
|
707
|
+
nextSelection.focus.set(closeNode.__key, newOffset, 'text'); // Apply formatting to selected text
|
|
619
708
|
|
|
620
|
-
// Apply formatting to selected text
|
|
621
709
|
for (const format of matcher.format) {
|
|
622
710
|
if (!nextSelection.hasFormat(format)) {
|
|
623
711
|
nextSelection.formatText(format);
|
|
624
712
|
}
|
|
625
|
-
}
|
|
713
|
+
} // Collapse selection up to the focus point
|
|
714
|
+
|
|
626
715
|
|
|
627
|
-
|
|
628
|
-
nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type);
|
|
716
|
+
nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type); // Remove formatting from collapsed selection
|
|
629
717
|
|
|
630
|
-
// Remove formatting from collapsed selection
|
|
631
718
|
for (const format of matcher.format) {
|
|
632
719
|
if (nextSelection.hasFormat(format)) {
|
|
633
720
|
nextSelection.toggleFormat(format);
|
|
634
721
|
}
|
|
635
722
|
}
|
|
723
|
+
|
|
636
724
|
if (lexical.$isRangeSelection(selection)) {
|
|
637
725
|
nextSelection.format = selection.format;
|
|
638
726
|
}
|
|
727
|
+
|
|
639
728
|
return true;
|
|
640
729
|
}
|
|
730
|
+
|
|
641
731
|
return false;
|
|
642
732
|
}
|
|
733
|
+
|
|
643
734
|
function getOpenTagStartIndex(string, maxIndex, tag) {
|
|
644
735
|
const tagLength = tag.length;
|
|
736
|
+
|
|
645
737
|
for (let i = maxIndex; i >= tagLength; i--) {
|
|
646
738
|
const startIndex = i - tagLength;
|
|
647
|
-
|
|
648
|
-
// Space after opening tag cancels transformation
|
|
739
|
+
|
|
740
|
+
if (isEqualSubString(string, startIndex, tag, 0, tagLength) && // Space after opening tag cancels transformation
|
|
649
741
|
string[startIndex + tagLength] !== ' ') {
|
|
650
742
|
return startIndex;
|
|
651
743
|
}
|
|
652
744
|
}
|
|
745
|
+
|
|
653
746
|
return -1;
|
|
654
747
|
}
|
|
748
|
+
|
|
655
749
|
function isEqualSubString(stringA, aStart, stringB, bStart, length) {
|
|
656
750
|
for (let i = 0; i < length; i++) {
|
|
657
751
|
if (stringA[aStart + i] !== stringB[bStart + i]) {
|
|
658
752
|
return false;
|
|
659
753
|
}
|
|
660
754
|
}
|
|
755
|
+
|
|
661
756
|
return true;
|
|
662
757
|
}
|
|
758
|
+
|
|
663
759
|
function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
664
760
|
const byType = transformersByType(transformers);
|
|
665
761
|
const textFormatTransformersIndex = indexBy(byType.textFormat, ({
|
|
@@ -668,10 +764,13 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
|
668
764
|
const textMatchTransformersIndex = indexBy(byType.textMatch, ({
|
|
669
765
|
trigger
|
|
670
766
|
}) => trigger);
|
|
767
|
+
|
|
671
768
|
for (const transformer of transformers) {
|
|
672
769
|
const type = transformer.type;
|
|
770
|
+
|
|
673
771
|
if (type === 'element' || type === 'text-match') {
|
|
674
772
|
const dependencies = transformer.dependencies;
|
|
773
|
+
|
|
675
774
|
if (!editor.hasNodes(dependencies)) {
|
|
676
775
|
{
|
|
677
776
|
throw Error(`MarkdownShortcuts: missing dependency for transformer. Ensure node dependency is included in editor initial config.`);
|
|
@@ -679,15 +778,19 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
|
679
778
|
}
|
|
680
779
|
}
|
|
681
780
|
}
|
|
781
|
+
|
|
682
782
|
const transform = (parentNode, anchorNode, anchorOffset) => {
|
|
683
783
|
if (runElementTransformers(parentNode, anchorNode, anchorOffset, byType.element)) {
|
|
684
784
|
return;
|
|
685
785
|
}
|
|
786
|
+
|
|
686
787
|
if (runTextMatchTransformers(anchorNode, anchorOffset, textMatchTransformersIndex)) {
|
|
687
788
|
return;
|
|
688
789
|
}
|
|
790
|
+
|
|
689
791
|
runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformersIndex);
|
|
690
792
|
};
|
|
793
|
+
|
|
691
794
|
return editor.registerUpdateListener(({
|
|
692
795
|
tags,
|
|
693
796
|
dirtyLeaves,
|
|
@@ -698,26 +801,35 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
|
698
801
|
if (tags.has('historic')) {
|
|
699
802
|
return;
|
|
700
803
|
}
|
|
804
|
+
|
|
701
805
|
const selection = editorState.read(lexical.$getSelection);
|
|
702
806
|
const prevSelection = prevEditorState.read(lexical.$getSelection);
|
|
807
|
+
|
|
703
808
|
if (!lexical.$isRangeSelection(prevSelection) || !lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
704
809
|
return;
|
|
705
810
|
}
|
|
811
|
+
|
|
706
812
|
const anchorKey = selection.anchor.key;
|
|
707
813
|
const anchorOffset = selection.anchor.offset;
|
|
814
|
+
|
|
708
815
|
const anchorNode = editorState._nodeMap.get(anchorKey);
|
|
816
|
+
|
|
709
817
|
if (!lexical.$isTextNode(anchorNode) || !dirtyLeaves.has(anchorKey) || anchorOffset !== 1 && anchorOffset !== prevSelection.anchor.offset + 1) {
|
|
710
818
|
return;
|
|
711
819
|
}
|
|
820
|
+
|
|
712
821
|
editor.update(() => {
|
|
713
822
|
// Markdown is not available inside code
|
|
714
823
|
if (anchorNode.hasFormat('code')) {
|
|
715
824
|
return;
|
|
716
825
|
}
|
|
826
|
+
|
|
717
827
|
const parentNode = anchorNode.getParent();
|
|
828
|
+
|
|
718
829
|
if (parentNode === null || code.$isCodeNode(parentNode)) {
|
|
719
830
|
return;
|
|
720
831
|
}
|
|
832
|
+
|
|
721
833
|
transform(parentNode, anchorNode, selection.anchor.offset);
|
|
722
834
|
});
|
|
723
835
|
});
|
|
@@ -730,6 +842,7 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
|
730
842
|
* LICENSE file in the root directory of this source tree.
|
|
731
843
|
*
|
|
732
844
|
*/
|
|
845
|
+
|
|
733
846
|
const createBlockNode = createNode => {
|
|
734
847
|
return (parentNode, children, match) => {
|
|
735
848
|
const node = createNode(match);
|
|
@@ -737,15 +850,17 @@ const createBlockNode = createNode => {
|
|
|
737
850
|
parentNode.replace(node);
|
|
738
851
|
node.select(0, 0);
|
|
739
852
|
};
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
// Amount of spaces that define indentation level
|
|
853
|
+
}; // Amount of spaces that define indentation level
|
|
743
854
|
// TODO: should be an option
|
|
855
|
+
|
|
856
|
+
|
|
744
857
|
const LIST_INDENT_SIZE = 4;
|
|
858
|
+
|
|
745
859
|
const listReplace = listType => {
|
|
746
860
|
return (parentNode, children, match) => {
|
|
747
861
|
const previousNode = parentNode.getPreviousSibling();
|
|
748
862
|
const listItem = list.$createListItemNode(listType === 'check' ? match[3] === 'x' : undefined);
|
|
863
|
+
|
|
749
864
|
if (list.$isListNode(previousNode) && previousNode.getListType() === listType) {
|
|
750
865
|
previousNode.append(listItem);
|
|
751
866
|
parentNode.remove();
|
|
@@ -754,27 +869,33 @@ const listReplace = listType => {
|
|
|
754
869
|
list$1.append(listItem);
|
|
755
870
|
parentNode.replace(list$1);
|
|
756
871
|
}
|
|
872
|
+
|
|
757
873
|
listItem.append(...children);
|
|
758
874
|
listItem.select(0, 0);
|
|
759
875
|
const indent = Math.floor(match[1].length / LIST_INDENT_SIZE);
|
|
876
|
+
|
|
760
877
|
if (indent) {
|
|
761
878
|
listItem.setIndent(indent);
|
|
762
879
|
}
|
|
763
880
|
};
|
|
764
881
|
};
|
|
882
|
+
|
|
765
883
|
const listExport = (listNode, exportChildren, depth) => {
|
|
766
884
|
const output = [];
|
|
767
885
|
const children = listNode.getChildren();
|
|
768
886
|
let index = 0;
|
|
887
|
+
|
|
769
888
|
for (const listItemNode of children) {
|
|
770
889
|
if (list.$isListItemNode(listItemNode)) {
|
|
771
890
|
if (listItemNode.getChildrenSize() === 1) {
|
|
772
891
|
const firstChild = listItemNode.getFirstChild();
|
|
892
|
+
|
|
773
893
|
if (list.$isListNode(firstChild)) {
|
|
774
894
|
output.push(listExport(firstChild, exportChildren, depth + 1));
|
|
775
895
|
continue;
|
|
776
896
|
}
|
|
777
897
|
}
|
|
898
|
+
|
|
778
899
|
const indent = ' '.repeat(depth * LIST_INDENT_SIZE);
|
|
779
900
|
const listType = listNode.getListType();
|
|
780
901
|
const prefix = listType === 'number' ? `${listNode.getStart() + index}. ` : listType === 'check' ? `- [${listItemNode.getChecked() ? 'x' : ' '}] ` : '- ';
|
|
@@ -782,14 +903,17 @@ const listExport = (listNode, exportChildren, depth) => {
|
|
|
782
903
|
index++;
|
|
783
904
|
}
|
|
784
905
|
}
|
|
906
|
+
|
|
785
907
|
return output.join('\n');
|
|
786
908
|
};
|
|
909
|
+
|
|
787
910
|
const HEADING = {
|
|
788
911
|
dependencies: [richText.HeadingNode],
|
|
789
912
|
export: (node, exportChildren) => {
|
|
790
913
|
if (!richText.$isHeadingNode(node)) {
|
|
791
914
|
return null;
|
|
792
915
|
}
|
|
916
|
+
|
|
793
917
|
const level = Number(node.getTag().slice(1));
|
|
794
918
|
return '#'.repeat(level) + ' ' + exportChildren(node);
|
|
795
919
|
},
|
|
@@ -806,17 +930,21 @@ const QUOTE = {
|
|
|
806
930
|
if (!richText.$isQuoteNode(node)) {
|
|
807
931
|
return null;
|
|
808
932
|
}
|
|
933
|
+
|
|
809
934
|
const lines = exportChildren(node).split('\n');
|
|
810
935
|
const output = [];
|
|
936
|
+
|
|
811
937
|
for (const line of lines) {
|
|
812
938
|
output.push('> ' + line);
|
|
813
939
|
}
|
|
940
|
+
|
|
814
941
|
return output.join('\n');
|
|
815
942
|
},
|
|
816
943
|
regExp: /^>\s/,
|
|
817
944
|
replace: (parentNode, children, _match, isImport) => {
|
|
818
945
|
if (isImport) {
|
|
819
946
|
const previousNode = parentNode.getPreviousSibling();
|
|
947
|
+
|
|
820
948
|
if (richText.$isQuoteNode(previousNode)) {
|
|
821
949
|
previousNode.splice(previousNode.getChildrenSize(), 0, [lexical.$createLineBreakNode(), ...children]);
|
|
822
950
|
previousNode.select(0, 0);
|
|
@@ -824,6 +952,7 @@ const QUOTE = {
|
|
|
824
952
|
return;
|
|
825
953
|
}
|
|
826
954
|
}
|
|
955
|
+
|
|
827
956
|
const node = richText.$createQuoteNode();
|
|
828
957
|
node.append(...children);
|
|
829
958
|
parentNode.replace(node);
|
|
@@ -837,6 +966,7 @@ const CODE = {
|
|
|
837
966
|
if (!code.$isCodeNode(node)) {
|
|
838
967
|
return null;
|
|
839
968
|
}
|
|
969
|
+
|
|
840
970
|
const textContent = node.getTextContent();
|
|
841
971
|
return '```' + (node.getLanguage() || '') + (textContent ? '\n' + textContent : '') + '\n' + '```';
|
|
842
972
|
},
|
|
@@ -920,22 +1050,22 @@ const ITALIC_UNDERSCORE = {
|
|
|
920
1050
|
intraword: false,
|
|
921
1051
|
tag: '_',
|
|
922
1052
|
type: 'text-format'
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
// Order of text transformers matters:
|
|
1053
|
+
}; // Order of text transformers matters:
|
|
926
1054
|
//
|
|
927
1055
|
// - code should go first as it prevents any transformations inside
|
|
928
1056
|
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
1057
|
+
|
|
929
1058
|
const LINK = {
|
|
930
1059
|
dependencies: [link.LinkNode],
|
|
931
1060
|
export: (node, exportChildren, exportFormat) => {
|
|
932
1061
|
if (!link.$isLinkNode(node)) {
|
|
933
1062
|
return null;
|
|
934
1063
|
}
|
|
1064
|
+
|
|
935
1065
|
const linkContent = `[${node.getTextContent()}](${node.getURL()})`;
|
|
936
|
-
const firstChild = node.getFirstChild();
|
|
937
|
-
// Add text styles only if link has single text node inside. If it's more
|
|
1066
|
+
const firstChild = node.getFirstChild(); // Add text styles only if link has single text node inside. If it's more
|
|
938
1067
|
// then one we ignore it as markdown does not support nested styles for links
|
|
1068
|
+
|
|
939
1069
|
if (node.getChildrenSize() === 1 && lexical.$isTextNode(firstChild)) {
|
|
940
1070
|
return exportFormat(firstChild, linkContent);
|
|
941
1071
|
} else {
|
|
@@ -957,19 +1087,20 @@ const LINK = {
|
|
|
957
1087
|
};
|
|
958
1088
|
|
|
959
1089
|
/** @module @lexical/markdown */
|
|
960
|
-
const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST];
|
|
961
|
-
|
|
962
|
-
// Order of text format transformers matters:
|
|
1090
|
+
const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST]; // Order of text format transformers matters:
|
|
963
1091
|
//
|
|
964
1092
|
// - code should go first as it prevents any transformations inside
|
|
965
1093
|
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
1094
|
+
|
|
966
1095
|
const TEXT_FORMAT_TRANSFORMERS = [INLINE_CODE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, HIGHLIGHT, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH];
|
|
967
1096
|
const TEXT_MATCH_TRANSFORMERS = [LINK];
|
|
968
1097
|
const TRANSFORMERS = [...ELEMENT_TRANSFORMERS, ...TEXT_FORMAT_TRANSFORMERS, ...TEXT_MATCH_TRANSFORMERS];
|
|
1098
|
+
|
|
969
1099
|
function $convertFromMarkdownString(markdown, transformers = TRANSFORMERS) {
|
|
970
1100
|
const importMarkdown = createMarkdownImport(transformers);
|
|
971
1101
|
return importMarkdown(markdown);
|
|
972
1102
|
}
|
|
1103
|
+
|
|
973
1104
|
function $convertToMarkdownString(transformers = TRANSFORMERS) {
|
|
974
1105
|
const exportMarkdown = createMarkdownExport(transformers);
|
|
975
1106
|
return exportMarkdown();
|
package/package.json
CHANGED
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
"markdown"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.9.0",
|
|
12
12
|
"main": "LexicalMarkdown.js",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"lexical": "0.
|
|
14
|
+
"lexical": "0.9.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@lexical/utils": "0.
|
|
18
|
-
"@lexical/code": "0.
|
|
19
|
-
"@lexical/text": "0.
|
|
20
|
-
"@lexical/rich-text": "0.
|
|
21
|
-
"@lexical/list": "0.
|
|
22
|
-
"@lexical/link": "0.
|
|
17
|
+
"@lexical/utils": "0.9.0",
|
|
18
|
+
"@lexical/code": "0.9.0",
|
|
19
|
+
"@lexical/text": "0.9.0",
|
|
20
|
+
"@lexical/rich-text": "0.9.0",
|
|
21
|
+
"@lexical/list": "0.9.0",
|
|
22
|
+
"@lexical/link": "0.9.0"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|