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