@lexical/markdown 0.8.1 → 0.9.1

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.
@@ -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
- return () => {
61
+ return node => {
59
62
  const output = [];
60
- const children = lexical.$getRoot().getChildren();
63
+ const children = (node || 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
- // Prevent adding opening tag is already opened by the previous sibling
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
- // Get next or previous text sibling a text node, including cases
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
- const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
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
 
@@ -215,77 +240,89 @@ const CODE_BLOCK_REG_EXP = /^```(\w{1,10})?\s?$/;
215
240
  function createMarkdownImport(transformers) {
216
241
  const byType = transformersByType(transformers);
217
242
  const textFormatTransformersIndex = createTextFormatTransformersIndex(byType.textFormat);
218
- return markdownString => {
243
+ return (markdownString, node) => {
219
244
  const lines = markdownString.split('\n');
220
245
  const linesLength = lines.length;
221
- const root = lexical.$getRoot();
246
+ const root = node || 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
- // Removing empty paragraphs as md does not really
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
  }
245
- root.selectEnd();
246
274
  };
247
275
  }
276
+
248
277
  function isEmptyParagraph(node) {
249
278
  if (!lexical.$isParagraphNode(node)) {
250
279
  return false;
251
280
  }
281
+
252
282
  const firstChild = node.getFirstChild();
253
283
  return firstChild == null || node.getChildrenSize() === 1 && lexical.$isTextNode(firstChild) && MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent());
254
284
  }
285
+
255
286
  function importBlocks(lineText, rootNode, elementTransformers, textFormatTransformersIndex, textMatchTransformers) {
256
287
  const lineTextTrimmed = lineText.trim();
257
288
  const textNode = lexical.$createTextNode(lineTextTrimmed);
258
289
  const elementNode = lexical.$createParagraphNode();
259
290
  elementNode.append(textNode);
260
291
  rootNode.append(elementNode);
292
+
261
293
  for (const {
262
294
  regExp,
263
295
  replace
264
296
  } of elementTransformers) {
265
297
  const match = lineText.match(regExp);
298
+
266
299
  if (match) {
267
300
  textNode.setTextContent(lineText.slice(match[0].length));
268
301
  replace(elementNode, [textNode], match, true);
269
302
  break;
270
303
  }
271
304
  }
272
- importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers);
273
305
 
274
- // If no transformer found and we left with original paragraph node
306
+ importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers); // If no transformer found and we left with original paragraph node
275
307
  // can check if its content can be appended to the previous node
276
308
  // if it's a paragraph, quote or list
309
+
277
310
  if (elementNode.isAttached() && lineTextTrimmed.length > 0) {
278
311
  const previousNode = elementNode.getPreviousSibling();
312
+
279
313
  if (lexical.$isParagraphNode(previousNode) || richText.$isQuoteNode(previousNode) || list.$isListNode(previousNode)) {
280
314
  let targetNode = previousNode;
315
+
281
316
  if (list.$isListNode(previousNode)) {
282
317
  const lastDescendant = previousNode.getLastDescendant();
318
+
283
319
  if (lastDescendant == null) {
284
320
  targetNode = null;
285
321
  } else {
286
322
  targetNode = utils.$findMatchingParent(lastDescendant, list.$isListItemNode);
287
323
  }
288
324
  }
325
+
289
326
  if (targetNode != null && targetNode.getTextContentSize() > 0) {
290
327
  targetNode.splice(targetNode.getChildrenSize(), 0, [lexical.$createLineBreakNode(), ...elementNode.getChildren()]);
291
328
  elementNode.remove();
@@ -293,13 +330,17 @@ function importBlocks(lineText, rootNode, elementTransformers, textFormatTransfo
293
330
  }
294
331
  }
295
332
  }
333
+
296
334
  function importCodeBlock(lines, startLineIndex, rootNode) {
297
335
  const openMatch = lines[startLineIndex].match(CODE_BLOCK_REG_EXP);
336
+
298
337
  if (openMatch) {
299
338
  let endLineIndex = startLineIndex;
300
339
  const linesLength = lines.length;
340
+
301
341
  while (++endLineIndex < linesLength) {
302
342
  const closeMatch = lines[endLineIndex].match(CODE_BLOCK_REG_EXP);
343
+
303
344
  if (closeMatch) {
304
345
  const codeBlockNode = code.$createCodeNode(openMatch[1]);
305
346
  const textNode = lexical.$createTextNode(lines.slice(startLineIndex + 1, endLineIndex).join('\n'));
@@ -309,133 +350,157 @@ function importCodeBlock(lines, startLineIndex, rootNode) {
309
350
  }
310
351
  }
311
352
  }
312
- return [null, startLineIndex];
313
- }
314
353
 
315
- // Processing text content and replaces text format tags.
354
+ return [null, startLineIndex];
355
+ } // Processing text content and replaces text format tags.
316
356
  // It takes outermost tag match and its content, creates text node with
317
357
  // format based on tag and then recursively executed over node's content
318
358
  //
319
359
  // E.g. for "*Hello **world**!*" string it will create text node with
320
360
  // "Hello **world**!" content and italic format and run recursively over
321
361
  // its content to transform "**world**" part
362
+
363
+
322
364
  function importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers) {
323
365
  const textContent = textNode.getTextContent();
324
366
  const match = findOutermostMatch(textContent, textFormatTransformersIndex);
367
+
325
368
  if (!match) {
326
369
  // Once text format processing is done run text match transformers, as it
327
370
  // only can span within single text node (unline formats that can cover multiple nodes)
328
371
  importTextMatchTransformers(textNode, textMatchTransformers);
329
372
  return;
330
373
  }
331
- let currentNode, remainderNode, leadingNode;
332
374
 
333
- // If matching full content there's no need to run splitText and can reuse existing textNode
375
+ let currentNode, remainderNode, leadingNode; // If matching full content there's no need to run splitText and can reuse existing textNode
334
376
  // to update its content and apply format. E.g. for **_Hello_** string after applying bold
335
377
  // format (**) it will reuse the same text node to apply italic (_)
378
+
336
379
  if (match[0] === textContent) {
337
380
  currentNode = textNode;
338
381
  } else {
339
382
  const startIndex = match.index || 0;
340
383
  const endIndex = startIndex + match[0].length;
384
+
341
385
  if (startIndex === 0) {
342
386
  [currentNode, remainderNode] = textNode.splitText(endIndex);
343
387
  } else {
344
388
  [leadingNode, currentNode, remainderNode] = textNode.splitText(startIndex, endIndex);
345
389
  }
346
390
  }
391
+
347
392
  currentNode.setTextContent(match[2]);
348
393
  const transformer = textFormatTransformersIndex.transformersByTag[match[1]];
394
+
349
395
  if (transformer) {
350
396
  for (const format of transformer.format) {
351
397
  if (!currentNode.hasFormat(format)) {
352
398
  currentNode.toggleFormat(format);
353
399
  }
354
400
  }
355
- }
401
+ } // Recursively run over inner text if it's not inline code
402
+
356
403
 
357
- // Recursively run over inner text if it's not inline code
358
404
  if (!currentNode.hasFormat('code')) {
359
405
  importTextFormatTransformers(currentNode, textFormatTransformersIndex, textMatchTransformers);
360
- }
406
+ } // Run over leading/remaining text if any
407
+
361
408
 
362
- // Run over leading/remaining text if any
363
409
  if (leadingNode) {
364
410
  importTextFormatTransformers(leadingNode, textFormatTransformersIndex, textMatchTransformers);
365
411
  }
412
+
366
413
  if (remainderNode) {
367
414
  importTextFormatTransformers(remainderNode, textFormatTransformersIndex, textMatchTransformers);
368
415
  }
369
416
  }
417
+
370
418
  function importTextMatchTransformers(textNode_, textMatchTransformers) {
371
419
  let textNode = textNode_;
420
+
372
421
  mainLoop: while (textNode) {
373
422
  for (const transformer of textMatchTransformers) {
374
423
  const match = textNode.getTextContent().match(transformer.importRegExp);
424
+
375
425
  if (!match) {
376
426
  continue;
377
427
  }
428
+
378
429
  const startIndex = match.index || 0;
379
430
  const endIndex = startIndex + match[0].length;
380
431
  let replaceNode, leftTextNode, rightTextNode;
432
+
381
433
  if (startIndex === 0) {
382
434
  [replaceNode, textNode] = textNode.splitText(endIndex);
383
435
  } else {
384
436
  [leftTextNode, replaceNode, rightTextNode] = textNode.splitText(startIndex, endIndex);
385
437
  }
438
+
386
439
  if (leftTextNode) {
387
440
  importTextMatchTransformers(leftTextNode, textMatchTransformers);
388
441
  }
442
+
389
443
  if (rightTextNode) {
390
444
  textNode = rightTextNode;
391
445
  }
446
+
392
447
  transformer.replace(replaceNode, match);
393
448
  continue mainLoop;
394
449
  }
450
+
395
451
  break;
396
452
  }
397
- }
453
+ } // Finds first "<tag>content<tag>" match that is not nested into another tag
454
+
398
455
 
399
- // Finds first "<tag>content<tag>" match that is not nested into another tag
400
456
  function findOutermostMatch(textContent, textTransformersIndex) {
401
457
  const openTagsMatch = textContent.match(textTransformersIndex.openTagsRegExp);
458
+
402
459
  if (openTagsMatch == null) {
403
460
  return null;
404
461
  }
462
+
405
463
  for (const match of openTagsMatch) {
406
464
  // Open tags reg exp might capture leading space so removing it
407
465
  // before using match to find transformer
408
466
  const tag = match.replace(/^\s/, '');
409
467
  const fullMatchRegExp = textTransformersIndex.fullMatchRegExpByTag[tag];
468
+
410
469
  if (fullMatchRegExp == null) {
411
470
  continue;
412
471
  }
472
+
413
473
  const fullMatch = textContent.match(fullMatchRegExp);
414
474
  const transformer = textTransformersIndex.transformersByTag[tag];
475
+
415
476
  if (fullMatch != null && transformer != null) {
416
477
  if (transformer.intraword !== false) {
417
478
  return fullMatch;
418
- }
419
-
420
- // For non-intraword transformers checking if it's within a word
479
+ } // For non-intraword transformers checking if it's within a word
421
480
  // or surrounded with space/punctuation/newline
481
+
482
+
422
483
  const {
423
484
  index = 0
424
485
  } = fullMatch;
425
486
  const beforeChar = textContent[index - 1];
426
487
  const afterChar = textContent[index + fullMatch[0].length];
488
+
427
489
  if ((!beforeChar || PUNCTUATION_OR_SPACE.test(beforeChar)) && (!afterChar || PUNCTUATION_OR_SPACE.test(afterChar))) {
428
490
  return fullMatch;
429
491
  }
430
492
  }
431
493
  }
494
+
432
495
  return null;
433
496
  }
497
+
434
498
  function createTextFormatTransformersIndex(textTransformers) {
435
499
  const transformersByTag = {};
436
500
  const fullMatchRegExpByTag = {};
437
501
  const openTagsRegExp = [];
438
502
  const escapeRegExp = `(?<![\\\\])`;
503
+
439
504
  for (const transformer of textTransformers) {
440
505
  const {
441
506
  tag
@@ -443,12 +508,14 @@ function createTextFormatTransformersIndex(textTransformers) {
443
508
  transformersByTag[tag] = transformer;
444
509
  const tagRegExp = tag.replace(/(\*|\^|\+)/g, '\\$1');
445
510
  openTagsRegExp.push(tagRegExp);
511
+
446
512
  if (IS_SAFARI || IS_IOS || IS_APPLE_WEBKIT) {
447
513
  fullMatchRegExpByTag[tag] = new RegExp(`(${tagRegExp})(?![${tagRegExp}\\s])(.*?[^${tagRegExp}\\s])${tagRegExp}(?!${tagRegExp})`);
448
514
  } else {
449
515
  fullMatchRegExpByTag[tag] = new RegExp(`(?<![\\\\${tagRegExp}])(${tagRegExp})((\\\\${tagRegExp})?.*?[^${tagRegExp}\\s](\\\\${tagRegExp})?)((?<!\\\\)|(?<=\\\\\\\\))(${tagRegExp})(?![\\\\${tagRegExp}])`);
450
516
  }
451
517
  }
518
+
452
519
  return {
453
520
  // Reg exp to find open tag + content + close tag
454
521
  fullMatchRegExpByTag,
@@ -465,27 +532,31 @@ function createTextFormatTransformersIndex(textTransformers) {
465
532
  * LICENSE file in the root directory of this source tree.
466
533
  *
467
534
  */
535
+
468
536
  function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers) {
469
537
  const grandParentNode = parentNode.getParent();
538
+
470
539
  if (!lexical.$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
471
540
  return false;
472
541
  }
473
- const textContent = anchorNode.getTextContent();
474
542
 
475
- // Checking for anchorOffset position to prevent any checks for cases when caret is too far
543
+ const textContent = anchorNode.getTextContent(); // Checking for anchorOffset position to prevent any checks for cases when caret is too far
476
544
  // from a line start to be a part of block-level markdown trigger.
477
545
  //
478
546
  // TODO:
479
547
  // Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
480
548
  // since otherwise it won't be a markdown shortcut, but tables are exception
549
+
481
550
  if (textContent[anchorOffset - 1] !== ' ') {
482
551
  return false;
483
552
  }
553
+
484
554
  for (const {
485
555
  regExp,
486
556
  replace
487
557
  } of elementTransformers) {
488
558
  const match = textContent.match(regExp);
559
+
489
560
  if (match && match[0].length === anchorOffset) {
490
561
  const nextSiblings = anchorNode.getNextSiblings();
491
562
  const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset);
@@ -495,115 +566,131 @@ function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTra
495
566
  return true;
496
567
  }
497
568
  }
569
+
498
570
  return false;
499
571
  }
572
+
500
573
  function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) {
501
574
  let textContent = anchorNode.getTextContent();
502
575
  const lastChar = textContent[anchorOffset - 1];
503
576
  const transformers = transformersByTrigger[lastChar];
577
+
504
578
  if (transformers == null) {
505
579
  return false;
506
- }
507
-
508
- // If typing in the middle of content, remove the tail to do
580
+ } // If typing in the middle of content, remove the tail to do
509
581
  // reg exp match up to a string end (caret position)
582
+
583
+
510
584
  if (anchorOffset < textContent.length) {
511
585
  textContent = textContent.slice(0, anchorOffset);
512
586
  }
587
+
513
588
  for (const transformer of transformers) {
514
589
  const match = textContent.match(transformer.regExp);
590
+
515
591
  if (match === null) {
516
592
  continue;
517
593
  }
594
+
518
595
  const startIndex = match.index || 0;
519
596
  const endIndex = startIndex + match[0].length;
520
597
  let replaceNode;
598
+
521
599
  if (startIndex === 0) {
522
600
  [replaceNode] = anchorNode.splitText(endIndex);
523
601
  } else {
524
602
  [, replaceNode] = anchorNode.splitText(startIndex, endIndex);
525
603
  }
604
+
526
605
  replaceNode.selectNext(0, 0);
527
606
  transformer.replace(replaceNode, match);
528
607
  return true;
529
608
  }
609
+
530
610
  return false;
531
611
  }
612
+
532
613
  function runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformers) {
533
614
  const textContent = anchorNode.getTextContent();
534
615
  const closeTagEndIndex = anchorOffset - 1;
535
- const closeChar = textContent[closeTagEndIndex];
536
- // Quick check if we're possibly at the end of inline markdown style
616
+ const closeChar = textContent[closeTagEndIndex]; // Quick check if we're possibly at the end of inline markdown style
617
+
537
618
  const matchers = textFormatTransformers[closeChar];
619
+
538
620
  if (!matchers) {
539
621
  return false;
540
622
  }
623
+
541
624
  for (const matcher of matchers) {
542
625
  const {
543
626
  tag
544
627
  } = matcher;
545
628
  const tagLength = tag.length;
546
- const closeTagStartIndex = closeTagEndIndex - tagLength + 1;
629
+ const closeTagStartIndex = closeTagEndIndex - tagLength + 1; // If tag is not single char check if rest of it matches with text content
547
630
 
548
- // If tag is not single char check if rest of it matches with text content
549
631
  if (tagLength > 1) {
550
632
  if (!isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) {
551
633
  continue;
552
634
  }
553
- }
635
+ } // Space before closing tag cancels inline markdown
636
+
554
637
 
555
- // Space before closing tag cancels inline markdown
556
638
  if (textContent[closeTagStartIndex - 1] === ' ') {
557
639
  continue;
558
- }
640
+ } // Some tags can not be used within words, hence should have newline/space/punctuation after it
641
+
559
642
 
560
- // Some tags can not be used within words, hence should have newline/space/punctuation after it
561
643
  const afterCloseTagChar = textContent[closeTagEndIndex + 1];
644
+
562
645
  if (matcher.intraword === false && afterCloseTagChar && !PUNCTUATION_OR_SPACE.test(afterCloseTagChar)) {
563
646
  continue;
564
647
  }
648
+
565
649
  const closeNode = anchorNode;
566
650
  let openNode = closeNode;
567
- let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag);
568
-
569
- // Go through text node siblings and search for opening tag
651
+ let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag); // Go through text node siblings and search for opening tag
570
652
  // if haven't found it within the same text node as closing tag
653
+
571
654
  let sibling = openNode;
655
+
572
656
  while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) {
573
657
  if (lexical.$isLineBreakNode(sibling)) {
574
658
  break;
575
659
  }
660
+
576
661
  if (lexical.$isTextNode(sibling)) {
577
662
  const siblingTextContent = sibling.getTextContent();
578
663
  openNode = sibling;
579
664
  openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag);
580
665
  }
581
- }
666
+ } // Opening tag is not found
667
+
582
668
 
583
- // Opening tag is not found
584
669
  if (openTagStartIndex < 0) {
585
670
  continue;
586
- }
671
+ } // No content between opening and closing tag
672
+
587
673
 
588
- // No content between opening and closing tag
589
674
  if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) {
590
675
  continue;
591
- }
676
+ } // Checking longer tags for repeating chars (e.g. *** vs **)
677
+
592
678
 
593
- // Checking longer tags for repeating chars (e.g. *** vs **)
594
679
  const prevOpenNodeText = openNode.getTextContent();
680
+
595
681
  if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) {
596
682
  continue;
597
- }
683
+ } // Some tags can not be used within words, hence should have newline/space/punctuation before it
684
+
598
685
 
599
- // Some tags can not be used within words, hence should have newline/space/punctuation before it
600
686
  const beforeOpenTagChar = prevOpenNodeText[openTagStartIndex - 1];
687
+
601
688
  if (matcher.intraword === false && beforeOpenTagChar && !PUNCTUATION_OR_SPACE.test(beforeOpenTagChar)) {
602
689
  continue;
603
- }
604
-
605
- // Clean text from opening and closing tags (starting from closing tag
690
+ } // Clean text from opening and closing tags (starting from closing tag
606
691
  // to prevent any offset shifts if we start from opening one)
692
+
693
+
607
694
  const prevCloseNodeText = closeNode.getTextContent();
608
695
  const closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1);
609
696
  closeNode.setTextContent(closeNodeText);
@@ -611,55 +698,62 @@ function runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransform
611
698
  openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength));
612
699
  const selection = lexical.$getSelection();
613
700
  const nextSelection = lexical.$createRangeSelection();
614
- lexical.$setSelection(nextSelection);
615
- // Adjust offset based on deleted chars
701
+ lexical.$setSelection(nextSelection); // Adjust offset based on deleted chars
702
+
616
703
  const newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1;
617
704
  nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text');
618
- nextSelection.focus.set(closeNode.__key, newOffset, 'text');
705
+ nextSelection.focus.set(closeNode.__key, newOffset, 'text'); // Apply formatting to selected text
619
706
 
620
- // Apply formatting to selected text
621
707
  for (const format of matcher.format) {
622
708
  if (!nextSelection.hasFormat(format)) {
623
709
  nextSelection.formatText(format);
624
710
  }
625
- }
711
+ } // Collapse selection up to the focus point
712
+
626
713
 
627
- // Collapse selection up to the focus point
628
- nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type);
714
+ nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type); // Remove formatting from collapsed selection
629
715
 
630
- // Remove formatting from collapsed selection
631
716
  for (const format of matcher.format) {
632
717
  if (nextSelection.hasFormat(format)) {
633
718
  nextSelection.toggleFormat(format);
634
719
  }
635
720
  }
721
+
636
722
  if (lexical.$isRangeSelection(selection)) {
637
723
  nextSelection.format = selection.format;
638
724
  }
725
+
639
726
  return true;
640
727
  }
728
+
641
729
  return false;
642
730
  }
731
+
643
732
  function getOpenTagStartIndex(string, maxIndex, tag) {
644
733
  const tagLength = tag.length;
734
+
645
735
  for (let i = maxIndex; i >= tagLength; i--) {
646
736
  const startIndex = i - tagLength;
647
- if (isEqualSubString(string, startIndex, tag, 0, tagLength) &&
648
- // Space after opening tag cancels transformation
737
+
738
+ if (isEqualSubString(string, startIndex, tag, 0, tagLength) && // Space after opening tag cancels transformation
649
739
  string[startIndex + tagLength] !== ' ') {
650
740
  return startIndex;
651
741
  }
652
742
  }
743
+
653
744
  return -1;
654
745
  }
746
+
655
747
  function isEqualSubString(stringA, aStart, stringB, bStart, length) {
656
748
  for (let i = 0; i < length; i++) {
657
749
  if (stringA[aStart + i] !== stringB[bStart + i]) {
658
750
  return false;
659
751
  }
660
752
  }
753
+
661
754
  return true;
662
755
  }
756
+
663
757
  function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
664
758
  const byType = transformersByType(transformers);
665
759
  const textFormatTransformersIndex = indexBy(byType.textFormat, ({
@@ -668,10 +762,13 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
668
762
  const textMatchTransformersIndex = indexBy(byType.textMatch, ({
669
763
  trigger
670
764
  }) => trigger);
765
+
671
766
  for (const transformer of transformers) {
672
767
  const type = transformer.type;
768
+
673
769
  if (type === 'element' || type === 'text-match') {
674
770
  const dependencies = transformer.dependencies;
771
+
675
772
  if (!editor.hasNodes(dependencies)) {
676
773
  {
677
774
  throw Error(`MarkdownShortcuts: missing dependency for transformer. Ensure node dependency is included in editor initial config.`);
@@ -679,15 +776,19 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
679
776
  }
680
777
  }
681
778
  }
779
+
682
780
  const transform = (parentNode, anchorNode, anchorOffset) => {
683
781
  if (runElementTransformers(parentNode, anchorNode, anchorOffset, byType.element)) {
684
782
  return;
685
783
  }
784
+
686
785
  if (runTextMatchTransformers(anchorNode, anchorOffset, textMatchTransformersIndex)) {
687
786
  return;
688
787
  }
788
+
689
789
  runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformersIndex);
690
790
  };
791
+
691
792
  return editor.registerUpdateListener(({
692
793
  tags,
693
794
  dirtyLeaves,
@@ -698,26 +799,35 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
698
799
  if (tags.has('historic')) {
699
800
  return;
700
801
  }
802
+
701
803
  const selection = editorState.read(lexical.$getSelection);
702
804
  const prevSelection = prevEditorState.read(lexical.$getSelection);
805
+
703
806
  if (!lexical.$isRangeSelection(prevSelection) || !lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
704
807
  return;
705
808
  }
809
+
706
810
  const anchorKey = selection.anchor.key;
707
811
  const anchorOffset = selection.anchor.offset;
812
+
708
813
  const anchorNode = editorState._nodeMap.get(anchorKey);
814
+
709
815
  if (!lexical.$isTextNode(anchorNode) || !dirtyLeaves.has(anchorKey) || anchorOffset !== 1 && anchorOffset !== prevSelection.anchor.offset + 1) {
710
816
  return;
711
817
  }
818
+
712
819
  editor.update(() => {
713
820
  // Markdown is not available inside code
714
821
  if (anchorNode.hasFormat('code')) {
715
822
  return;
716
823
  }
824
+
717
825
  const parentNode = anchorNode.getParent();
826
+
718
827
  if (parentNode === null || code.$isCodeNode(parentNode)) {
719
828
  return;
720
829
  }
830
+
721
831
  transform(parentNode, anchorNode, selection.anchor.offset);
722
832
  });
723
833
  });
@@ -730,6 +840,7 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
730
840
  * LICENSE file in the root directory of this source tree.
731
841
  *
732
842
  */
843
+
733
844
  const createBlockNode = createNode => {
734
845
  return (parentNode, children, match) => {
735
846
  const node = createNode(match);
@@ -737,15 +848,17 @@ const createBlockNode = createNode => {
737
848
  parentNode.replace(node);
738
849
  node.select(0, 0);
739
850
  };
740
- };
741
-
742
- // Amount of spaces that define indentation level
851
+ }; // Amount of spaces that define indentation level
743
852
  // TODO: should be an option
853
+
854
+
744
855
  const LIST_INDENT_SIZE = 4;
856
+
745
857
  const listReplace = listType => {
746
858
  return (parentNode, children, match) => {
747
859
  const previousNode = parentNode.getPreviousSibling();
748
860
  const listItem = list.$createListItemNode(listType === 'check' ? match[3] === 'x' : undefined);
861
+
749
862
  if (list.$isListNode(previousNode) && previousNode.getListType() === listType) {
750
863
  previousNode.append(listItem);
751
864
  parentNode.remove();
@@ -754,27 +867,33 @@ const listReplace = listType => {
754
867
  list$1.append(listItem);
755
868
  parentNode.replace(list$1);
756
869
  }
870
+
757
871
  listItem.append(...children);
758
872
  listItem.select(0, 0);
759
873
  const indent = Math.floor(match[1].length / LIST_INDENT_SIZE);
874
+
760
875
  if (indent) {
761
876
  listItem.setIndent(indent);
762
877
  }
763
878
  };
764
879
  };
880
+
765
881
  const listExport = (listNode, exportChildren, depth) => {
766
882
  const output = [];
767
883
  const children = listNode.getChildren();
768
884
  let index = 0;
885
+
769
886
  for (const listItemNode of children) {
770
887
  if (list.$isListItemNode(listItemNode)) {
771
888
  if (listItemNode.getChildrenSize() === 1) {
772
889
  const firstChild = listItemNode.getFirstChild();
890
+
773
891
  if (list.$isListNode(firstChild)) {
774
892
  output.push(listExport(firstChild, exportChildren, depth + 1));
775
893
  continue;
776
894
  }
777
895
  }
896
+
778
897
  const indent = ' '.repeat(depth * LIST_INDENT_SIZE);
779
898
  const listType = listNode.getListType();
780
899
  const prefix = listType === 'number' ? `${listNode.getStart() + index}. ` : listType === 'check' ? `- [${listItemNode.getChecked() ? 'x' : ' '}] ` : '- ';
@@ -782,14 +901,17 @@ const listExport = (listNode, exportChildren, depth) => {
782
901
  index++;
783
902
  }
784
903
  }
904
+
785
905
  return output.join('\n');
786
906
  };
907
+
787
908
  const HEADING = {
788
909
  dependencies: [richText.HeadingNode],
789
910
  export: (node, exportChildren) => {
790
911
  if (!richText.$isHeadingNode(node)) {
791
912
  return null;
792
913
  }
914
+
793
915
  const level = Number(node.getTag().slice(1));
794
916
  return '#'.repeat(level) + ' ' + exportChildren(node);
795
917
  },
@@ -806,17 +928,21 @@ const QUOTE = {
806
928
  if (!richText.$isQuoteNode(node)) {
807
929
  return null;
808
930
  }
931
+
809
932
  const lines = exportChildren(node).split('\n');
810
933
  const output = [];
934
+
811
935
  for (const line of lines) {
812
936
  output.push('> ' + line);
813
937
  }
938
+
814
939
  return output.join('\n');
815
940
  },
816
941
  regExp: /^>\s/,
817
942
  replace: (parentNode, children, _match, isImport) => {
818
943
  if (isImport) {
819
944
  const previousNode = parentNode.getPreviousSibling();
945
+
820
946
  if (richText.$isQuoteNode(previousNode)) {
821
947
  previousNode.splice(previousNode.getChildrenSize(), 0, [lexical.$createLineBreakNode(), ...children]);
822
948
  previousNode.select(0, 0);
@@ -824,6 +950,7 @@ const QUOTE = {
824
950
  return;
825
951
  }
826
952
  }
953
+
827
954
  const node = richText.$createQuoteNode();
828
955
  node.append(...children);
829
956
  parentNode.replace(node);
@@ -837,6 +964,7 @@ const CODE = {
837
964
  if (!code.$isCodeNode(node)) {
838
965
  return null;
839
966
  }
967
+
840
968
  const textContent = node.getTextContent();
841
969
  return '```' + (node.getLanguage() || '') + (textContent ? '\n' + textContent : '') + '\n' + '```';
842
970
  },
@@ -920,22 +1048,22 @@ const ITALIC_UNDERSCORE = {
920
1048
  intraword: false,
921
1049
  tag: '_',
922
1050
  type: 'text-format'
923
- };
924
-
925
- // Order of text transformers matters:
1051
+ }; // Order of text transformers matters:
926
1052
  //
927
1053
  // - code should go first as it prevents any transformations inside
928
1054
  // - then longer tags match (e.g. ** or __ should go before * or _)
1055
+
929
1056
  const LINK = {
930
1057
  dependencies: [link.LinkNode],
931
1058
  export: (node, exportChildren, exportFormat) => {
932
1059
  if (!link.$isLinkNode(node)) {
933
1060
  return null;
934
1061
  }
1062
+
935
1063
  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
1064
+ const firstChild = node.getFirstChild(); // Add text styles only if link has single text node inside. If it's more
938
1065
  // then one we ignore it as markdown does not support nested styles for links
1066
+
939
1067
  if (node.getChildrenSize() === 1 && lexical.$isTextNode(firstChild)) {
940
1068
  return exportFormat(firstChild, linkContent);
941
1069
  } else {
@@ -957,22 +1085,23 @@ const LINK = {
957
1085
  };
958
1086
 
959
1087
  /** @module @lexical/markdown */
960
- const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST];
961
-
962
- // Order of text format transformers matters:
1088
+ const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST]; // Order of text format transformers matters:
963
1089
  //
964
1090
  // - code should go first as it prevents any transformations inside
965
1091
  // - then longer tags match (e.g. ** or __ should go before * or _)
1092
+
966
1093
  const TEXT_FORMAT_TRANSFORMERS = [INLINE_CODE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, HIGHLIGHT, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH];
967
1094
  const TEXT_MATCH_TRANSFORMERS = [LINK];
968
1095
  const TRANSFORMERS = [...ELEMENT_TRANSFORMERS, ...TEXT_FORMAT_TRANSFORMERS, ...TEXT_MATCH_TRANSFORMERS];
969
- function $convertFromMarkdownString(markdown, transformers = TRANSFORMERS) {
1096
+
1097
+ function $convertFromMarkdownString(markdown, transformers = TRANSFORMERS, node) {
970
1098
  const importMarkdown = createMarkdownImport(transformers);
971
- return importMarkdown(markdown);
1099
+ return importMarkdown(markdown, node);
972
1100
  }
973
- function $convertToMarkdownString(transformers = TRANSFORMERS) {
1101
+
1102
+ function $convertToMarkdownString(transformers = TRANSFORMERS, node) {
974
1103
  const exportMarkdown = createMarkdownExport(transformers);
975
- return exportMarkdown();
1104
+ return exportMarkdown(node);
976
1105
  }
977
1106
 
978
1107
  exports.$convertFromMarkdownString = $convertFromMarkdownString;
@@ -5,15 +5,15 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  'use strict';var h=require("lexical"),t=require("@lexical/code"),A=require("@lexical/list"),B=require("@lexical/rich-text"),aa=require("@lexical/utils"),G=require("@lexical/link");function H(a,b){let c={};for(let d of a)a=b(d),c[a]?c[a].push(d):c[a]=[d];return c}function I(a){a=H(a,b=>b.type);return{element:a.element||[],textFormat:a["text-format"]||[],textMatch:a["text-match"]||[]}}let J=/[!-/:-@[-`{-~\s]/;
8
- function ba(a){let b=I(a),c=b.textFormat.filter(d=>1===d.format.length);return()=>{let d=[];var e=h.$getRoot().getChildren();for(let f of e)e=ca(f,b.element,c,b.textMatch),null!=e&&d.push(e);return d.join("\n\n")}}function ca(a,b,c,d){for(let e of b)if(b=e.export(a,f=>K(f,c,d)),null!=b)return b;return h.$isElementNode(a)?K(a,c,d):h.$isDecoratorNode(a)?a.getTextContent():null}
8
+ function ba(a){let b=I(a),c=b.textFormat.filter(d=>1===d.format.length);return d=>{let e=[];d=(d||h.$getRoot()).getChildren();for(let f of d)d=ca(f,b.element,c,b.textMatch),null!=d&&e.push(d);return e.join("\n\n")}}function ca(a,b,c,d){for(let e of b)if(b=e.export(a,f=>K(f,c,d)),null!=b)return b;return h.$isElementNode(a)?K(a,c,d):h.$isDecoratorNode(a)?a.getTextContent():null}
9
9
  function K(a,b,c){let d=[];a=a.getChildren();a:for(let e of a){for(let f of c)if(a=f.export(e,l=>K(l,b,c),(l,k)=>L(l,k,b)),null!=a){d.push(a);continue a}h.$isLineBreakNode(e)?d.push("\n"):h.$isTextNode(e)?d.push(L(e,e.getTextContent(),b)):h.$isElementNode(e)?d.push(K(e,b,c)):h.$isDecoratorNode(e)&&d.push(e.getTextContent())}return d.join("")}
10
10
  function L(a,b,c){let d=b.trim(),e=d,f=new Set;for(let k of c){c=k.format[0];let p=k.tag;if(M(a,c)&&!f.has(c)){f.add(c);var l=N(a,!0);M(l,c)||(e=p+e);l=N(a,!1);M(l,c)||(e+=p)}}return b.replace(d,e)}
11
11
  function N(a,b){let c=b?a.getPreviousSibling():a.getNextSibling();c||(a=a.getParentOrThrow(),a.isInline()&&(c=b?a.getPreviousSibling():a.getNextSibling()));for(;c;){if(h.$isElementNode(c)){if(!c.isInline())break;a=b?c.getLastDescendant():c.getFirstDescendant();if(h.$isTextNode(a))return a;c=b?c.getPreviousSibling():c.getNextSibling()}if(h.$isTextNode(c))return c;if(!h.$isElementNode(c))break}return null}function M(a,b){return h.$isTextNode(a)&&a.hasFormat(b)}
12
12
  let O="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement,da=O&&"documentMode"in document?document.documentMode:null;O&&/Mac|iPod|iPhone|iPad/.test(navigator.platform);O&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);O&&"InputEvent"in window&&!da?"getTargetRanges"in new window.InputEvent("input"):!1;
13
13
  let P=O&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent),Q=O&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,ea=O&&/^(?=.*Chrome).*/i.test(navigator.userAgent),R=O&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&!ea,fa=/^\s{0,3}$/,S=/^```(\w{1,10})?\s?$/;
14
- function ha(a){let b=I(a),c=ia(b.textFormat);return d=>{var e=d.split("\n"),f=e.length;d=h.$getRoot();d.clear();for(let g=0;g<f;g++){var l=e[g];a:{var k=e,p=g;var r=d;var y=k[p].match(S);if(y)for(var q=p,m=k.length;++q<m;)if(k[q].match(S)){y=t.$createCodeNode(y[1]);k=h.$createTextNode(k.slice(p+1,q).join("\n"));y.append(k);r.append(y);r=[y,q];break a}r=[null,p]}let [n,v]=r;if(null!=n)g=v;else{r=l;m=d;var w=b.element;q=c;k=b.textMatch;p=r.trim();y=h.$createTextNode(p);l=h.$createParagraphNode();l.append(y);
15
- m.append(l);for(let {regExp:x,replace:u}of w)if(m=r.match(x)){y.setTextContent(r.slice(m[0].length));u(l,[y],m,!0);break}T(y,q,k);l.isAttached()&&0<p.length&&(r=l.getPreviousSibling(),h.$isParagraphNode(r)||B.$isQuoteNode(r)||A.$isListNode(r))&&(q=r,A.$isListNode(r)&&(r=r.getLastDescendant(),q=null==r?null:aa.$findMatchingParent(r,A.$isListItemNode)),null!=q&&0<q.getTextContentSize()&&(q.splice(q.getChildrenSize(),0,[h.$createLineBreakNode(),...l.getChildren()]),l.remove()))}}e=d.getChildren();for(let g of e)e=
16
- g,h.$isParagraphNode(e)?(f=e.getFirstChild(),e=null==f||1===e.getChildrenSize()&&h.$isTextNode(f)&&fa.test(f.getTextContent())):e=!1,e&&g.remove();d.selectEnd()}}
14
+ function ha(a){let b=I(a),c=ia(b.textFormat);return(d,e)=>{d=d.split("\n");var f=d.length;e=e||h.$getRoot();e.clear();for(let g=0;g<f;g++){var l=d[g];a:{var k=d,p=g;var r=e;var y=k[p].match(S);if(y)for(var q=p,m=k.length;++q<m;)if(k[q].match(S)){y=t.$createCodeNode(y[1]);k=h.$createTextNode(k.slice(p+1,q).join("\n"));y.append(k);r.append(y);r=[y,q];break a}r=[null,p]}let [n,v]=r;if(null!=n)g=v;else{r=l;m=e;var w=b.element;q=c;k=b.textMatch;p=r.trim();y=h.$createTextNode(p);l=h.$createParagraphNode();
15
+ l.append(y);m.append(l);for(let {regExp:x,replace:u}of w)if(m=r.match(x)){y.setTextContent(r.slice(m[0].length));u(l,[y],m,!0);break}T(y,q,k);l.isAttached()&&0<p.length&&(r=l.getPreviousSibling(),h.$isParagraphNode(r)||B.$isQuoteNode(r)||A.$isListNode(r))&&(q=r,A.$isListNode(r)&&(r=r.getLastDescendant(),q=null==r?null:aa.$findMatchingParent(r,A.$isListItemNode)),null!=q&&0<q.getTextContentSize()&&(q.splice(q.getChildrenSize(),0,[h.$createLineBreakNode(),...l.getChildren()]),l.remove()))}}d=e.getChildren();
16
+ for(let g of d)d=g,h.$isParagraphNode(d)?(f=d.getFirstChild(),d=null==f||1===d.getChildrenSize()&&h.$isTextNode(f)&&fa.test(f.getTextContent())):d=!1,d&&g.remove()}}
17
17
  function T(a,b,c){var d=a.getTextContent();let e=ja(d,b);if(e){var f,l;if(e[0]===d)var k=a;else{d=e.index||0;let p=d+e[0].length;0===d?[k,f]=a.splitText(p):[l,k,f]=a.splitText(d,p)}k.setTextContent(e[2]);if(a=b.transformersByTag[e[1]])for(let p of a.format)k.hasFormat(p)||k.toggleFormat(p);k.hasFormat("code")||T(k,b,c);l&&T(l,b,c);f&&T(f,b,c)}else U(a,c)}
18
18
  function U(a,b){a:for(;a;){for(let c of b){let d=a.getTextContent().match(c.importRegExp);if(!d)continue;let e=d.index||0,f=e+d[0].length,l,k,p;0===e?[l,a]=a.splitText(f):[k,l,p]=a.splitText(e,f);k&&U(k,b);p&&(a=p);c.replace(l,d);continue a}break}}
19
19
  function ja(a,b){var c=a.match(b.openTagsRegExp);if(null==c)return null;for(let f of c){var d=f.replace(/^\s/,"");c=b.fullMatchRegExpByTag[d];if(null!=c&&(c=a.match(c),d=b.transformersByTag[d],null!=c&&null!=d)){if(!1!==d.intraword)return c;var {index:e=0}=c;d=a[e-1];e=a[e+c[0].length];if(!(d&&!J.test(d)||e&&!J.test(e)))return c}}return null}
@@ -25,7 +25,7 @@ export:(a,b)=>{if(!B.$isQuoteNode(a))return null;a=b(a).split("\n");b=[];for(con
25
25
  (a.getLanguage()||"")+(b?"\n"+b:"")+"\n```"},regExp:/^```(\w{1,10})?\s/,replace:ka(a=>t.$createCodeNode(a?a[1]:void 0)),type:"element"},oa={dependencies:[A.ListNode,A.ListItemNode],export:(a,b)=>A.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)[-*+]\s/,replace:X("bullet"),type:"element"},pa={dependencies:[A.ListNode,A.ListItemNode],export:(a,b)=>A.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,replace:X("check"),type:"element"},qa={dependencies:[A.ListNode,A.ListItemNode],export:(a,
26
26
  b)=>A.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)(\d{1,})\.\s/,replace:X("number"),type:"element"},ra={format:["code"],tag:"`",type:"text-format"},sa={format:["highlight"],tag:"==",type:"text-format"},ta={format:["bold","italic"],tag:"***",type:"text-format"},va={format:["bold","italic"],intraword:!1,tag:"___",type:"text-format"},wa={format:["bold"],tag:"**",type:"text-format"},xa={format:["bold"],intraword:!1,tag:"__",type:"text-format"},ya={format:["strikethrough"],tag:"~~",type:"text-format"},
27
27
  za={format:["italic"],tag:"*",type:"text-format"},Aa={format:["italic"],intraword:!1,tag:"_",type:"text-format"},Ba={dependencies:[G.LinkNode],export:(a,b,c)=>{if(!G.$isLinkNode(a))return null;b=`[${a.getTextContent()}](${a.getURL()})`;const d=a.getFirstChild();return 1===a.getChildrenSize()&&h.$isTextNode(d)?c(d,b):b},importRegExp:/(?:\[([^[]+)\])(?:\(([^()]+)\))/,regExp:/(?:\[([^[]+)\])(?:\(([^()]+)\))$/,replace:(a,b)=>{const [,c,d]=b;b=G.$createLinkNode(d);const e=h.$createTextNode(c);e.setFormat(a.getFormat());
28
- b.append(e);a.replace(b)},trigger:")",type:"text-match"},Ca=[la,ma,na,oa,qa],Da=[ra,ta,va,wa,xa,sa,za,Aa,ya],Ea=[Ba],Z=[...Ca,...Da,...Ea];exports.$convertFromMarkdownString=function(a,b=Z){return ha(b)(a)};exports.$convertToMarkdownString=function(a=Z){return ba(a)()};exports.BOLD_ITALIC_STAR=ta;exports.BOLD_ITALIC_UNDERSCORE=va;exports.BOLD_STAR=wa;exports.BOLD_UNDERSCORE=xa;exports.CHECK_LIST=pa;exports.CODE=na;exports.ELEMENT_TRANSFORMERS=Ca;exports.HEADING=la;exports.HIGHLIGHT=sa;
28
+ b.append(e);a.replace(b)},trigger:")",type:"text-match"},Ca=[la,ma,na,oa,qa],Da=[ra,ta,va,wa,xa,sa,za,Aa,ya],Ea=[Ba],Z=[...Ca,...Da,...Ea];exports.$convertFromMarkdownString=function(a,b=Z,c){return ha(b)(a,c)};exports.$convertToMarkdownString=function(a=Z,b){return ba(a)(b)};exports.BOLD_ITALIC_STAR=ta;exports.BOLD_ITALIC_UNDERSCORE=va;exports.BOLD_STAR=wa;exports.BOLD_UNDERSCORE=xa;exports.CHECK_LIST=pa;exports.CODE=na;exports.ELEMENT_TRANSFORMERS=Ca;exports.HEADING=la;exports.HIGHLIGHT=sa;
29
29
  exports.INLINE_CODE=ra;exports.ITALIC_STAR=za;exports.ITALIC_UNDERSCORE=Aa;exports.LINK=Ba;exports.ORDERED_LIST=qa;exports.QUOTE=ma;exports.STRIKETHROUGH=ya;exports.TEXT_FORMAT_TRANSFORMERS=Da;exports.TEXT_MATCH_TRANSFORMERS=Ea;exports.TRANSFORMERS=Z;exports.UNORDERED_LIST=oa;
30
30
  exports.registerMarkdownShortcuts=function(a,b=Z){let c=I(b),d=H(c.textFormat,({tag:f})=>f[f.length-1]),e=H(c.textMatch,({trigger:f})=>f);for(let f of b)if(b=f.type,("element"===b||"text-match"===b)&&!a.hasNodes(f.dependencies))throw Error("Minified Lexical error #79; visit https://lexical.dev/docs/error?code=79 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.");return a.registerUpdateListener(({tags:f,dirtyLeaves:l,editorState:k,prevEditorState:p})=>
31
31
  {if(!f.has("historic")){var r=k.read(h.$getSelection);f=p.read(h.$getSelection);if(h.$isRangeSelection(f)&&h.$isRangeSelection(r)&&r.isCollapsed()){p=r.anchor.key;var y=r.anchor.offset,q=k._nodeMap.get(p);h.$isTextNode(q)&&l.has(p)&&(1===y||y===f.anchor.offset+1)&&a.update(()=>{if(!q.hasFormat("code")){var m=q.getParent();if(null!==m&&!t.$isCodeNode(m)){var w=r.anchor.offset;b:{var g=c.element,n=m.getParent();if(h.$isRootOrShadowRoot(n)&&m.getFirstChild()===q&&(n=q.getTextContent()," "===n[w-1]))for(let {regExp:D,
@@ -6,4 +6,5 @@
6
6
  *
7
7
  */
8
8
  import type { Transformer } from '@lexical/markdown';
9
- export declare function createMarkdownExport(transformers: Array<Transformer>): () => string;
9
+ import type { ElementNode } from 'lexical';
10
+ export declare function createMarkdownExport(transformers: Array<Transformer>): (node?: ElementNode) => string;
@@ -6,4 +6,5 @@
6
6
  *
7
7
  */
8
8
  import type { Transformer } from '@lexical/markdown';
9
- export declare function createMarkdownImport(transformers: Array<Transformer>): (markdownString: string) => void;
9
+ import { ElementNode } from 'lexical';
10
+ export declare function createMarkdownImport(transformers: Array<Transformer>): (markdownString: string, node?: ElementNode) => void;
package/index.d.ts CHANGED
@@ -7,12 +7,13 @@
7
7
  *
8
8
  */
9
9
  import type { ElementTransformer, TextFormatTransformer, TextMatchTransformer, Transformer } from './MarkdownTransformers';
10
+ import type { ElementNode } from 'lexical';
10
11
  import { registerMarkdownShortcuts } from './MarkdownShortcuts';
11
12
  import { BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, CHECK_LIST, CODE, HEADING, HIGHLIGHT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, LINK, ORDERED_LIST, QUOTE, STRIKETHROUGH, UNORDERED_LIST } from './MarkdownTransformers';
12
13
  declare const ELEMENT_TRANSFORMERS: Array<ElementTransformer>;
13
14
  declare const TEXT_FORMAT_TRANSFORMERS: Array<TextFormatTransformer>;
14
15
  declare const TEXT_MATCH_TRANSFORMERS: Array<TextMatchTransformer>;
15
16
  declare const TRANSFORMERS: Array<Transformer>;
16
- declare function $convertFromMarkdownString(markdown: string, transformers?: Array<Transformer>): void;
17
- declare function $convertToMarkdownString(transformers?: Array<Transformer>): string;
17
+ declare function $convertFromMarkdownString(markdown: string, transformers?: Array<Transformer>, node?: ElementNode): void;
18
+ declare function $convertToMarkdownString(transformers?: Array<Transformer>, node?: ElementNode): string;
18
19
  export { $convertFromMarkdownString, $convertToMarkdownString, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, CHECK_LIST, CODE, ELEMENT_TRANSFORMERS, ElementTransformer, HEADING, HIGHLIGHT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, LINK, ORDERED_LIST, QUOTE, registerMarkdownShortcuts, STRIKETHROUGH, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, TextFormatTransformer, TextMatchTransformer, Transformer, TRANSFORMERS, UNORDERED_LIST, };
package/package.json CHANGED
@@ -8,18 +8,18 @@
8
8
  "markdown"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.8.1",
11
+ "version": "0.9.1",
12
12
  "main": "LexicalMarkdown.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.8.1"
14
+ "lexical": "0.9.1"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/utils": "0.8.1",
18
- "@lexical/code": "0.8.1",
19
- "@lexical/text": "0.8.1",
20
- "@lexical/rich-text": "0.8.1",
21
- "@lexical/list": "0.8.1",
22
- "@lexical/link": "0.8.1"
17
+ "@lexical/utils": "0.9.1",
18
+ "@lexical/code": "0.9.1",
19
+ "@lexical/text": "0.9.1",
20
+ "@lexical/rich-text": "0.9.1",
21
+ "@lexical/list": "0.9.1",
22
+ "@lexical/link": "0.9.1"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -1,8 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- export declare function $convertToMarkdownString(): string;