@lexical/code 0.7.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/LexicalCode.dev.js +59 -234
  2. package/package.json +3 -3
@@ -29,60 +29,50 @@ var lexical = require('lexical');
29
29
  * LICENSE file in the root directory of this source tree.
30
30
  *
31
31
  */
32
-
33
32
  const mapToPrismLanguage = language => {
34
33
  // eslint-disable-next-line no-prototype-builtins
35
34
  return language != null && Prism.languages.hasOwnProperty(language) ? language : undefined;
36
35
  };
37
-
38
36
  function hasChildDOMNodeTag(node, tagName) {
39
37
  for (const child of node.childNodes) {
40
38
  if (child instanceof HTMLElement && child.tagName === tagName) {
41
39
  return true;
42
40
  }
43
-
44
41
  hasChildDOMNodeTag(child, tagName);
45
42
  }
46
-
47
43
  return false;
48
44
  }
49
-
50
45
  const LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';
51
- /** @noInheritDoc */
52
46
 
47
+ /** @noInheritDoc */
53
48
  class CodeNode extends lexical.ElementNode {
54
49
  /** @internal */
50
+
55
51
  static getType() {
56
52
  return 'code';
57
53
  }
58
-
59
54
  static clone(node) {
60
55
  return new CodeNode(node.__language, node.__key);
61
56
  }
62
-
63
57
  constructor(language, key) {
64
58
  super(key);
65
59
  this.__language = mapToPrismLanguage(language);
66
- } // View
67
-
60
+ }
68
61
 
62
+ // View
69
63
  createDOM(config) {
70
64
  const element = document.createElement('code');
71
65
  utils.addClassNamesToElement(element, config.theme.code);
72
66
  element.setAttribute('spellcheck', 'false');
73
67
  const language = this.getLanguage();
74
-
75
68
  if (language) {
76
69
  element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
77
70
  }
78
-
79
71
  return element;
80
72
  }
81
-
82
73
  updateDOM(prevNode, dom, config) {
83
74
  const language = this.__language;
84
75
  const prevLanguage = prevNode.__language;
85
-
86
76
  if (language) {
87
77
  if (language !== prevLanguage) {
88
78
  dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
@@ -90,10 +80,8 @@ class CodeNode extends lexical.ElementNode {
90
80
  } else if (prevLanguage) {
91
81
  dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE);
92
82
  }
93
-
94
83
  return false;
95
84
  }
96
-
97
85
  static importDOM() {
98
86
  return {
99
87
  // Typically <pre> is used for code blocks, and <code> for inline code styles
@@ -115,29 +103,26 @@ class CodeNode extends lexical.ElementNode {
115
103
  priority: 0
116
104
  }),
117
105
  table: node => {
118
- const table = node; // domNode is a <table> since we matched it by nodeName
119
-
106
+ const table = node;
107
+ // domNode is a <table> since we matched it by nodeName
120
108
  if (isGitHubCodeTable(table)) {
121
109
  return {
122
110
  conversion: convertTableElement,
123
111
  priority: 3
124
112
  };
125
113
  }
126
-
127
114
  return null;
128
115
  },
129
116
  td: node => {
130
117
  // element is a <td> since we matched it by nodeName
131
118
  const td = node;
132
119
  const table = td.closest('table');
133
-
134
120
  if (isGitHubCodeCell(td)) {
135
121
  return {
136
122
  conversion: convertTableCellElement,
137
123
  priority: 3
138
124
  };
139
125
  }
140
-
141
126
  if (table && isGitHubCodeTable(table)) {
142
127
  // Return a no-op if it's a table cell in a code table, but not a code line.
143
128
  // Otherwise it'll fall back to the T
@@ -146,26 +131,22 @@ class CodeNode extends lexical.ElementNode {
146
131
  priority: 3
147
132
  };
148
133
  }
149
-
150
134
  return null;
151
135
  },
152
136
  tr: node => {
153
137
  // element is a <tr> since we matched it by nodeName
154
138
  const tr = node;
155
139
  const table = tr.closest('table');
156
-
157
140
  if (table && isGitHubCodeTable(table)) {
158
141
  return {
159
142
  conversion: convertCodeNoop,
160
143
  priority: 3
161
144
  };
162
145
  }
163
-
164
146
  return null;
165
147
  }
166
148
  };
167
149
  }
168
-
169
150
  static importJSON(serializedNode) {
170
151
  const node = $createCodeNode(serializedNode.language);
171
152
  node.setFormat(serializedNode.format);
@@ -173,42 +154,38 @@ class CodeNode extends lexical.ElementNode {
173
154
  node.setDirection(serializedNode.direction);
174
155
  return node;
175
156
  }
176
-
177
157
  exportJSON() {
178
- return { ...super.exportJSON(),
158
+ return {
159
+ ...super.exportJSON(),
179
160
  language: this.getLanguage(),
180
161
  type: 'code',
181
162
  version: 1
182
163
  };
183
- } // Mutation
184
-
164
+ }
185
165
 
166
+ // Mutation
186
167
  insertNewAfter(selection, restoreSelection = true) {
187
168
  const children = this.getChildren();
188
169
  const childrenLength = children.length;
189
-
190
170
  if (childrenLength >= 2 && children[childrenLength - 1].getTextContent() === '\n' && children[childrenLength - 2].getTextContent() === '\n' && selection.isCollapsed() && selection.anchor.key === this.__key && selection.anchor.offset === childrenLength) {
191
171
  children[childrenLength - 1].remove();
192
172
  children[childrenLength - 2].remove();
193
173
  const newElement = lexical.$createParagraphNode();
194
174
  this.insertAfter(newElement, restoreSelection);
195
175
  return newElement;
196
- } // If the selection is within the codeblock, find all leading tabs and
176
+ }
177
+
178
+ // If the selection is within the codeblock, find all leading tabs and
197
179
  // spaces of the current line. Create a new line that has all those
198
180
  // tabs and spaces, such that leading indentation is preserved.
199
-
200
-
201
181
  const anchor = selection.anchor.getNode();
202
182
  const firstNode = getFirstCodeHighlightNodeOfLine(anchor);
203
-
204
183
  if (firstNode != null) {
205
184
  let leadingWhitespace = 0;
206
185
  const firstNodeText = firstNode.getTextContent();
207
-
208
186
  while (leadingWhitespace < firstNodeText.length && /[\t ]/.test(firstNodeText[leadingWhitespace])) {
209
187
  leadingWhitespace += 1;
210
188
  }
211
-
212
189
  if (leadingWhitespace > 0) {
213
190
  const whitespace = firstNodeText.substring(0, leadingWhitespace);
214
191
  const indentedChild = $createCodeHighlightNode(whitespace);
@@ -218,24 +195,18 @@ class CodeNode extends lexical.ElementNode {
218
195
  return indentedChild;
219
196
  }
220
197
  }
221
-
222
198
  return null;
223
199
  }
224
-
225
200
  canInsertTab() {
226
201
  const selection = lexical.$getSelection();
227
-
228
202
  if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
229
203
  return false;
230
204
  }
231
-
232
205
  return true;
233
206
  }
234
-
235
207
  canIndent() {
236
208
  return false;
237
209
  }
238
-
239
210
  collapseAtStart() {
240
211
  const paragraph = lexical.$createParagraphNode();
241
212
  const children = this.getChildren();
@@ -243,16 +214,13 @@ class CodeNode extends lexical.ElementNode {
243
214
  this.replace(paragraph);
244
215
  return true;
245
216
  }
246
-
247
217
  setLanguage(language) {
248
218
  const writable = this.getWritable();
249
219
  writable.__language = mapToPrismLanguage(language);
250
220
  }
251
-
252
221
  getLanguage() {
253
222
  return this.getLatest().__language;
254
223
  }
255
-
256
224
  }
257
225
  function $createCodeNode(language) {
258
226
  return lexical.$applyNodeReplacement(new CodeNode(language));
@@ -260,53 +228,44 @@ function $createCodeNode(language) {
260
228
  function $isCodeNode(node) {
261
229
  return node instanceof CodeNode;
262
230
  }
263
-
264
231
  function convertPreElement(domNode) {
265
232
  return {
266
233
  node: $createCodeNode(),
267
234
  preformatted: true
268
235
  };
269
236
  }
270
-
271
237
  function convertDivElement(domNode) {
272
238
  // domNode is a <div> since we matched it by nodeName
273
239
  const div = domNode;
274
240
  const isCode = isCodeElement(div);
275
-
276
241
  if (!isCode && !isCodeChildElement(div)) {
277
242
  return {
278
243
  node: null
279
244
  };
280
245
  }
281
-
282
246
  return {
283
247
  after: childLexicalNodes => {
284
248
  const domParent = domNode.parentNode;
285
-
286
249
  if (domParent != null && domNode !== domParent.lastChild) {
287
250
  childLexicalNodes.push(lexical.$createLineBreakNode());
288
251
  }
289
-
290
252
  return childLexicalNodes;
291
253
  },
292
254
  node: isCode ? $createCodeNode() : null,
293
255
  preformatted: isCode
294
256
  };
295
257
  }
296
-
297
258
  function convertTableElement() {
298
259
  return {
299
260
  node: $createCodeNode(),
300
261
  preformatted: true
301
262
  };
302
263
  }
303
-
304
264
  function convertCodeNoop() {
305
265
  return {
306
266
  node: null
307
267
  };
308
268
  }
309
-
310
269
  function convertTableCellElement(domNode) {
311
270
  // domNode is a <td> since we matched it by nodeName
312
271
  const cell = domNode;
@@ -316,35 +275,27 @@ function convertTableCellElement(domNode) {
316
275
  // Append newline between code lines
317
276
  childLexicalNodes.push(lexical.$createLineBreakNode());
318
277
  }
319
-
320
278
  return childLexicalNodes;
321
279
  },
322
280
  node: null
323
281
  };
324
282
  }
325
-
326
283
  function isCodeElement(div) {
327
284
  return div.style.fontFamily.match('monospace') !== null;
328
285
  }
329
-
330
286
  function isCodeChildElement(node) {
331
287
  let parent = node.parentElement;
332
-
333
288
  while (parent !== null) {
334
289
  if (isCodeElement(parent)) {
335
290
  return true;
336
291
  }
337
-
338
292
  parent = parent.parentElement;
339
293
  }
340
-
341
294
  return false;
342
295
  }
343
-
344
296
  function isGitHubCodeCell(cell) {
345
297
  return cell.classList.contains('js-file-line');
346
298
  }
347
-
348
299
  function isGitHubCodeTable(table) {
349
300
  return table.classList.contains('js-file-line-container');
350
301
  }
@@ -386,60 +337,52 @@ function normalizeCodeLang(lang) {
386
337
  }
387
338
  function getLanguageFriendlyName(lang) {
388
339
  const _lang = normalizeCodeLang(lang);
389
-
390
340
  return CODE_LANGUAGE_FRIENDLY_NAME_MAP[_lang] || _lang;
391
341
  }
392
342
  const getDefaultCodeLanguage = () => DEFAULT_CODE_LANGUAGE;
393
- const getCodeLanguages = () => Object.keys(Prism.languages).filter( // Prism has several language helpers mixed into languages object
343
+ const getCodeLanguages = () => Object.keys(Prism.languages).filter(
344
+ // Prism has several language helpers mixed into languages object
394
345
  // so filtering them out here to get langs list
395
346
  language => typeof Prism.languages[language] !== 'function').sort();
396
- /** @noInheritDoc */
397
347
 
348
+ /** @noInheritDoc */
398
349
  class CodeHighlightNode extends lexical.TextNode {
399
350
  /** @internal */
351
+
400
352
  constructor(text, highlightType, key) {
401
353
  super(text, key);
402
354
  this.__highlightType = highlightType;
403
355
  }
404
-
405
356
  static getType() {
406
357
  return 'code-highlight';
407
358
  }
408
-
409
359
  static clone(node) {
410
360
  return new CodeHighlightNode(node.__text, node.__highlightType || undefined, node.__key);
411
361
  }
412
-
413
362
  getHighlightType() {
414
363
  const self = this.getLatest();
415
364
  return self.__highlightType;
416
365
  }
417
-
418
366
  createDOM(config) {
419
367
  const element = super.createDOM(config);
420
368
  const className = getHighlightThemeClass(config.theme, this.__highlightType);
421
369
  utils.addClassNamesToElement(element, className);
422
370
  return element;
423
371
  }
424
-
425
372
  updateDOM(prevNode, dom, config) {
426
373
  const update = super.updateDOM(prevNode, dom, config);
427
374
  const prevClassName = getHighlightThemeClass(config.theme, prevNode.__highlightType);
428
375
  const nextClassName = getHighlightThemeClass(config.theme, this.__highlightType);
429
-
430
376
  if (prevClassName !== nextClassName) {
431
377
  if (prevClassName) {
432
378
  utils.removeClassNamesFromElement(dom, prevClassName);
433
379
  }
434
-
435
380
  if (nextClassName) {
436
381
  utils.addClassNamesToElement(dom, nextClassName);
437
382
  }
438
383
  }
439
-
440
384
  return update;
441
385
  }
442
-
443
386
  static importJSON(serializedNode) {
444
387
  const node = $createCodeHighlightNode(serializedNode.text, serializedNode.highlightType);
445
388
  node.setFormat(serializedNode.format);
@@ -448,34 +391,29 @@ class CodeHighlightNode extends lexical.TextNode {
448
391
  node.setStyle(serializedNode.style);
449
392
  return node;
450
393
  }
451
-
452
394
  exportJSON() {
453
- return { ...super.exportJSON(),
395
+ return {
396
+ ...super.exportJSON(),
454
397
  highlightType: this.getHighlightType(),
455
398
  type: 'code-highlight',
456
399
  version: 1
457
400
  };
458
- } // Prevent formatting (bold, underline, etc)
459
-
401
+ }
460
402
 
403
+ // Prevent formatting (bold, underline, etc)
461
404
  setFormat(format) {
462
405
  return this;
463
406
  }
464
-
465
407
  isParentRequired() {
466
408
  return true;
467
409
  }
468
-
469
410
  createParentElementNode() {
470
411
  return $createCodeNode();
471
412
  }
472
-
473
413
  }
474
-
475
414
  function getHighlightThemeClass(theme, highlightType) {
476
415
  return highlightType && theme && theme.codeHighlight && theme.codeHighlight[highlightType];
477
416
  }
478
-
479
417
  function $createCodeHighlightNode(text, highlightType) {
480
418
  return lexical.$applyNodeReplacement(new CodeHighlightNode(text, highlightType));
481
419
  }
@@ -486,38 +424,30 @@ function getFirstCodeHighlightNodeOfLine(anchor) {
486
424
  let currentNode = null;
487
425
  const previousSiblings = anchor.getPreviousSiblings();
488
426
  previousSiblings.push(anchor);
489
-
490
427
  while (previousSiblings.length > 0) {
491
428
  const node = previousSiblings.pop();
492
-
493
429
  if ($isCodeHighlightNode(node)) {
494
430
  currentNode = node;
495
431
  }
496
-
497
432
  if (lexical.$isLineBreakNode(node)) {
498
433
  break;
499
434
  }
500
435
  }
501
-
502
436
  return currentNode;
503
437
  }
504
438
  function getLastCodeHighlightNodeOfLine(anchor) {
505
439
  let currentNode = null;
506
440
  const nextSiblings = anchor.getNextSiblings();
507
441
  nextSiblings.unshift(anchor);
508
-
509
442
  while (nextSiblings.length > 0) {
510
443
  const node = nextSiblings.shift();
511
-
512
444
  if ($isCodeHighlightNode(node)) {
513
445
  currentNode = node;
514
446
  }
515
-
516
447
  if (lexical.$isLineBreakNode(node)) {
517
448
  break;
518
449
  }
519
450
  }
520
-
521
451
  return currentNode;
522
452
  }
523
453
 
@@ -530,25 +460,19 @@ function getLastCodeHighlightNodeOfLine(anchor) {
530
460
  */
531
461
  const PrismTokenizer = {
532
462
  defaultLanguage: DEFAULT_CODE_LANGUAGE,
533
-
534
463
  tokenize(code, language) {
535
464
  return Prism.tokenize(code, Prism.languages[language || ''] || Prism.languages[this.defaultLanguage]);
536
465
  }
537
-
538
466
  };
539
-
540
467
  function isSpaceOrTabChar(char) {
541
468
  return char === ' ' || char === '\t';
542
469
  }
543
-
544
470
  function findFirstNotSpaceOrTabCharAtText(text, isForward) {
545
471
  const length = text.length;
546
472
  let offset = -1;
547
-
548
473
  if (isForward) {
549
474
  for (let i = 0; i < length; i++) {
550
475
  const char = text[i];
551
-
552
476
  if (!isSpaceOrTabChar(char)) {
553
477
  offset = i;
554
478
  break;
@@ -557,64 +481,51 @@ function findFirstNotSpaceOrTabCharAtText(text, isForward) {
557
481
  } else {
558
482
  for (let i = length - 1; i > -1; i--) {
559
483
  const char = text[i];
560
-
561
484
  if (!isSpaceOrTabChar(char)) {
562
485
  offset = i;
563
486
  break;
564
487
  }
565
488
  }
566
489
  }
567
-
568
490
  return offset;
569
491
  }
570
-
571
492
  function getStartOfCodeInLine(anchor) {
572
493
  let currentNode = null;
573
494
  let currentNodeOffset = -1;
574
495
  const previousSiblings = anchor.getPreviousSiblings();
575
496
  previousSiblings.push(anchor);
576
-
577
497
  while (previousSiblings.length > 0) {
578
498
  const node = previousSiblings.pop();
579
-
580
499
  if ($isCodeHighlightNode(node)) {
581
500
  const text = node.getTextContent();
582
501
  const offset = findFirstNotSpaceOrTabCharAtText(text, true);
583
-
584
502
  if (offset !== -1) {
585
503
  currentNode = node;
586
504
  currentNodeOffset = offset;
587
505
  }
588
506
  }
589
-
590
507
  if (lexical.$isLineBreakNode(node)) {
591
508
  break;
592
509
  }
593
510
  }
594
-
595
511
  if (currentNode === null) {
596
512
  const nextSiblings = anchor.getNextSiblings();
597
-
598
513
  while (nextSiblings.length > 0) {
599
514
  const node = nextSiblings.shift();
600
-
601
515
  if ($isCodeHighlightNode(node)) {
602
516
  const text = node.getTextContent();
603
517
  const offset = findFirstNotSpaceOrTabCharAtText(text, true);
604
-
605
518
  if (offset !== -1) {
606
519
  currentNode = node;
607
520
  currentNodeOffset = offset;
608
521
  break;
609
522
  }
610
523
  }
611
-
612
524
  if (lexical.$isLineBreakNode(node)) {
613
525
  break;
614
526
  }
615
527
  }
616
528
  }
617
-
618
529
  return {
619
530
  node: currentNode,
620
531
  offset: currentNodeOffset
@@ -625,59 +536,47 @@ function getEndOfCodeInLine(anchor) {
625
536
  let currentNodeOffset = -1;
626
537
  const nextSiblings = anchor.getNextSiblings();
627
538
  nextSiblings.unshift(anchor);
628
-
629
539
  while (nextSiblings.length > 0) {
630
540
  const node = nextSiblings.shift();
631
-
632
541
  if ($isCodeHighlightNode(node)) {
633
542
  const text = node.getTextContent();
634
543
  const offset = findFirstNotSpaceOrTabCharAtText(text, false);
635
-
636
544
  if (offset !== -1) {
637
545
  currentNode = node;
638
546
  currentNodeOffset = offset + 1;
639
547
  }
640
548
  }
641
-
642
549
  if (lexical.$isLineBreakNode(node)) {
643
550
  break;
644
551
  }
645
552
  }
646
-
647
553
  if (currentNode === null) {
648
554
  const previousSiblings = anchor.getPreviousSiblings();
649
-
650
555
  while (previousSiblings.length > 0) {
651
556
  const node = previousSiblings.pop();
652
-
653
557
  if ($isCodeHighlightNode(node)) {
654
558
  const text = node.getTextContent();
655
559
  const offset = findFirstNotSpaceOrTabCharAtText(text, false);
656
-
657
560
  if (offset !== -1) {
658
561
  currentNode = node;
659
562
  currentNodeOffset = offset + 1;
660
563
  break;
661
564
  }
662
565
  }
663
-
664
566
  if (lexical.$isLineBreakNode(node)) {
665
567
  break;
666
568
  }
667
569
  }
668
570
  }
669
-
670
571
  return {
671
572
  node: currentNode,
672
573
  offset: currentNodeOffset
673
574
  };
674
575
  }
675
-
676
576
  function textNodeTransform(node, editor, tokenizer) {
677
577
  // Since CodeNode has flat children structure we only need to check
678
578
  // if node's parent is a code node and run highlighting if so
679
579
  const parentNode = node.getParent();
680
-
681
580
  if ($isCodeNode(parentNode)) {
682
581
  codeNodeTransform(parentNode, editor, tokenizer);
683
582
  } else if ($isCodeHighlightNode(node)) {
@@ -686,35 +585,31 @@ function textNodeTransform(node, editor, tokenizer) {
686
585
  node.replace(lexical.$createTextNode(node.__text));
687
586
  }
688
587
  }
689
-
690
588
  function updateCodeGutter(node, editor) {
691
589
  const codeElement = editor.getElementByKey(node.getKey());
692
-
693
590
  if (codeElement === null) {
694
591
  return;
695
592
  }
696
-
697
593
  const children = node.getChildren();
698
- const childrenLength = children.length; // @ts-ignore: internal field
699
-
594
+ const childrenLength = children.length;
595
+ // @ts-ignore: internal field
700
596
  if (childrenLength === codeElement.__cachedChildrenLength) {
701
597
  // Avoid updating the attribute if the children length hasn't changed.
702
598
  return;
703
- } // @ts-ignore:: internal field
704
-
705
-
599
+ }
600
+ // @ts-ignore:: internal field
706
601
  codeElement.__cachedChildrenLength = childrenLength;
707
602
  let gutter = '1';
708
603
  let count = 1;
709
-
710
604
  for (let i = 0; i < childrenLength; i++) {
711
605
  if (lexical.$isLineBreakNode(children[i])) {
712
606
  gutter += '\n' + ++count;
713
607
  }
714
608
  }
715
-
716
609
  codeElement.setAttribute('data-gutter', gutter);
717
- } // Using `skipTransforms` to prevent extra transforms since reformatting the code
610
+ }
611
+
612
+ // Using `skipTransforms` to prevent extra transforms since reformatting the code
718
613
  // will not affect code block content itself.
719
614
  //
720
615
  // Using extra cache (`nodesCurrentlyHighlighting`) since both CodeNode and CodeHighlightNode
@@ -722,33 +617,28 @@ function updateCodeGutter(node, editor) {
722
617
  // in both cases we'll rerun whole reformatting over CodeNode, which is redundant.
723
618
  // Especially when pasting code into CodeBlock.
724
619
 
725
-
726
620
  const nodesCurrentlyHighlighting = new Set();
727
-
728
621
  function codeNodeTransform(node, editor, tokenizer) {
729
622
  const nodeKey = node.getKey();
730
-
731
623
  if (nodesCurrentlyHighlighting.has(nodeKey)) {
732
624
  return;
733
625
  }
626
+ nodesCurrentlyHighlighting.add(nodeKey);
734
627
 
735
- nodesCurrentlyHighlighting.add(nodeKey); // When new code block inserted it might not have language selected
736
-
628
+ // When new code block inserted it might not have language selected
737
629
  if (node.getLanguage() === undefined) {
738
630
  node.setLanguage(tokenizer.defaultLanguage);
739
- } // Using nested update call to pass `skipTransforms` since we don't want
631
+ }
632
+
633
+ // Using nested update call to pass `skipTransforms` since we don't want
740
634
  // each individual codehighlight node to be transformed again as it's already
741
635
  // in its final state
742
-
743
-
744
636
  editor.update(() => {
745
637
  updateAndRetainSelection(nodeKey, () => {
746
638
  const currentNode = lexical.$getNodeByKey(nodeKey);
747
-
748
639
  if (!$isCodeNode(currentNode) || !currentNode.isAttached()) {
749
640
  return false;
750
641
  }
751
-
752
642
  const code = currentNode.getTextContent();
753
643
  const tokens = tokenizer.tokenize(code, currentNode.getLanguage() || tokenizer.defaultLanguage);
754
644
  const highlightNodes = getHighlightNodes(tokens);
@@ -758,12 +648,10 @@ function codeNodeTransform(node, editor, tokenizer) {
758
648
  to,
759
649
  nodesForReplacement
760
650
  } = diffRange;
761
-
762
651
  if (from !== to || nodesForReplacement.length) {
763
652
  node.splice(from, to - from, nodesForReplacement);
764
653
  return true;
765
654
  }
766
-
767
655
  return false;
768
656
  });
769
657
  }, {
@@ -773,20 +661,16 @@ function codeNodeTransform(node, editor, tokenizer) {
773
661
  skipTransforms: true
774
662
  });
775
663
  }
776
-
777
664
  function getHighlightNodes(tokens) {
778
665
  const nodes = [];
779
666
  tokens.forEach(token => {
780
667
  if (typeof token === 'string') {
781
668
  const partials = token.split('\n');
782
-
783
669
  for (let i = 0; i < partials.length; i++) {
784
670
  const text = partials[i];
785
-
786
671
  if (text.length) {
787
672
  nodes.push($createCodeHighlightNode(text));
788
673
  }
789
-
790
674
  if (i < partials.length - 1) {
791
675
  nodes.push(lexical.$createLineBreakNode());
792
676
  }
@@ -795,7 +679,6 @@ function getHighlightNodes(tokens) {
795
679
  const {
796
680
  content
797
681
  } = token;
798
-
799
682
  if (typeof content === 'string') {
800
683
  nodes.push($createCodeHighlightNode(content, token.type));
801
684
  } else if (Array.isArray(content) && content.length === 1 && typeof content[0] === 'string') {
@@ -806,98 +689,83 @@ function getHighlightNodes(tokens) {
806
689
  }
807
690
  });
808
691
  return nodes;
809
- } // Wrapping update function into selection retainer, that tries to keep cursor at the same
810
- // position as before.
811
-
692
+ }
812
693
 
694
+ // Wrapping update function into selection retainer, that tries to keep cursor at the same
695
+ // position as before.
813
696
  function updateAndRetainSelection(nodeKey, updateFn) {
814
697
  const node = lexical.$getNodeByKey(nodeKey);
815
-
816
698
  if (!$isCodeNode(node) || !node.isAttached()) {
817
699
  return;
818
700
  }
819
-
820
- const selection = lexical.$getSelection(); // If it's not range selection (or null selection) there's no need to change it,
701
+ const selection = lexical.$getSelection();
702
+ // If it's not range selection (or null selection) there's no need to change it,
821
703
  // but we can still run highlighting logic
822
-
823
704
  if (!lexical.$isRangeSelection(selection)) {
824
705
  updateFn();
825
706
  return;
826
707
  }
827
-
828
708
  const anchor = selection.anchor;
829
709
  const anchorOffset = anchor.offset;
830
710
  const isNewLineAnchor = anchor.type === 'element' && lexical.$isLineBreakNode(node.getChildAtIndex(anchor.offset - 1));
831
- let textOffset = 0; // Calculating previous text offset (all text node prior to anchor + anchor own text offset)
711
+ let textOffset = 0;
832
712
 
713
+ // Calculating previous text offset (all text node prior to anchor + anchor own text offset)
833
714
  if (!isNewLineAnchor) {
834
715
  const anchorNode = anchor.getNode();
835
716
  textOffset = anchorOffset + anchorNode.getPreviousSiblings().reduce((offset, _node) => {
836
717
  return offset + _node.getTextContentSize();
837
718
  }, 0);
838
719
  }
839
-
840
720
  const hasChanges = updateFn();
841
-
842
721
  if (!hasChanges) {
843
722
  return;
844
- } // Non-text anchors only happen for line breaks, otherwise
845
- // selection will be within text node (code highlight node)
846
-
723
+ }
847
724
 
725
+ // Non-text anchors only happen for line breaks, otherwise
726
+ // selection will be within text node (code highlight node)
848
727
  if (isNewLineAnchor) {
849
728
  anchor.getNode().select(anchorOffset, anchorOffset);
850
729
  return;
851
- } // If it was non-element anchor then we walk through child nodes
852
- // and looking for a position of original text offset
853
-
730
+ }
854
731
 
732
+ // If it was non-element anchor then we walk through child nodes
733
+ // and looking for a position of original text offset
855
734
  node.getChildren().some(_node => {
856
735
  const isText = lexical.$isTextNode(_node);
857
-
858
736
  if (isText || lexical.$isLineBreakNode(_node)) {
859
737
  const textContentSize = _node.getTextContentSize();
860
-
861
738
  if (isText && textContentSize >= textOffset) {
862
739
  _node.select(textOffset, textOffset);
863
-
864
740
  return true;
865
741
  }
866
-
867
742
  textOffset -= textContentSize;
868
743
  }
869
-
870
744
  return false;
871
745
  });
872
- } // Finds minimal diff range between two nodes lists. It returns from/to range boundaries of prevNodes
873
- // that needs to be replaced with `nodes` (subset of nextNodes) to make prevNodes equal to nextNodes.
874
-
746
+ }
875
747
 
748
+ // Finds minimal diff range between two nodes lists. It returns from/to range boundaries of prevNodes
749
+ // that needs to be replaced with `nodes` (subset of nextNodes) to make prevNodes equal to nextNodes.
876
750
  function getDiffRange(prevNodes, nextNodes) {
877
751
  let leadingMatch = 0;
878
-
879
752
  while (leadingMatch < prevNodes.length) {
880
753
  if (!isEqual(prevNodes[leadingMatch], nextNodes[leadingMatch])) {
881
754
  break;
882
755
  }
883
-
884
756
  leadingMatch++;
885
757
  }
886
-
887
758
  const prevNodesLength = prevNodes.length;
888
759
  const nextNodesLength = nextNodes.length;
889
760
  const maxTrailingMatch = Math.min(prevNodesLength, nextNodesLength) - leadingMatch;
890
761
  let trailingMatch = 0;
891
-
892
762
  while (trailingMatch < maxTrailingMatch) {
893
763
  trailingMatch++;
894
-
895
764
  if (!isEqual(prevNodes[prevNodesLength - trailingMatch], nextNodes[nextNodesLength - trailingMatch])) {
896
765
  trailingMatch--;
897
766
  break;
898
767
  }
899
768
  }
900
-
901
769
  const from = leadingMatch;
902
770
  const to = prevNodesLength - trailingMatch;
903
771
  const nodesForReplacement = nextNodes.slice(leadingMatch, nextNodesLength - trailingMatch);
@@ -907,59 +775,45 @@ function getDiffRange(prevNodes, nextNodes) {
907
775
  to
908
776
  };
909
777
  }
910
-
911
778
  function isEqual(nodeA, nodeB) {
912
779
  // Only checking for code higlight nodes and linebreaks. If it's regular text node
913
780
  // returning false so that it's transformed into code highlight node
914
781
  if ($isCodeHighlightNode(nodeA) && $isCodeHighlightNode(nodeB)) {
915
782
  return nodeA.__text === nodeB.__text && nodeA.__highlightType === nodeB.__highlightType;
916
783
  }
917
-
918
784
  if (lexical.$isLineBreakNode(nodeA) && lexical.$isLineBreakNode(nodeB)) {
919
785
  return true;
920
786
  }
921
-
922
787
  return false;
923
788
  }
924
-
925
789
  function handleMultilineIndent(type) {
926
790
  const selection = lexical.$getSelection();
927
-
928
791
  if (!lexical.$isRangeSelection(selection) || selection.isCollapsed()) {
929
792
  return false;
930
- } // Only run multiline indent logic on selections exclusively composed of code highlights and linebreaks
931
-
793
+ }
932
794
 
795
+ // Only run multiline indent logic on selections exclusively composed of code highlights and linebreaks
933
796
  const nodes = selection.getNodes();
934
-
935
797
  for (let i = 0; i < nodes.length; i++) {
936
798
  const node = nodes[i];
937
-
938
799
  if (!$isCodeHighlightNode(node) && !lexical.$isLineBreakNode(node)) {
939
800
  return false;
940
801
  }
941
802
  }
942
-
943
803
  const startOfLine = getFirstCodeHighlightNodeOfLine(nodes[0]);
944
-
945
804
  if (startOfLine != null) {
946
805
  doIndent(startOfLine, type);
947
806
  }
948
-
949
807
  for (let i = 1; i < nodes.length; i++) {
950
808
  const node = nodes[i];
951
-
952
809
  if (lexical.$isLineBreakNode(nodes[i - 1]) && $isCodeHighlightNode(node)) {
953
810
  doIndent(node, type);
954
811
  }
955
812
  }
956
-
957
813
  return true;
958
814
  }
959
-
960
815
  function doIndent(node, type) {
961
816
  const text = node.getTextContent();
962
-
963
817
  if (type === lexical.INDENT_CONTENT_COMMAND) {
964
818
  // If the codeblock node doesn't start with whitespace, we don't want to
965
819
  // naively prepend a '\t'; Prism will then mangle all of our nodes when
@@ -984,17 +838,15 @@ function doIndent(node, type) {
984
838
  }
985
839
  }
986
840
  }
987
-
988
841
  function handleShiftLines(type, event) {
989
842
  // We only care about the alt+arrow keys
990
843
  const selection = lexical.$getSelection();
991
-
992
844
  if (!lexical.$isRangeSelection(selection)) {
993
845
  return false;
994
- } // I'm not quite sure why, but it seems like calling anchor.getNode() collapses the selection here
995
- // So first, get the anchor and the focus, then get their nodes
996
-
846
+ }
997
847
 
848
+ // I'm not quite sure why, but it seems like calling anchor.getNode() collapses the selection here
849
+ // So first, get the anchor and the focus, then get their nodes
998
850
  const {
999
851
  anchor,
1000
852
  focus
@@ -1003,21 +855,19 @@ function handleShiftLines(type, event) {
1003
855
  const focusOffset = focus.offset;
1004
856
  const anchorNode = anchor.getNode();
1005
857
  const focusNode = focus.getNode();
1006
- const arrowIsUp = type === lexical.KEY_ARROW_UP_COMMAND; // Ensure the selection is within the codeblock
858
+ const arrowIsUp = type === lexical.KEY_ARROW_UP_COMMAND;
1007
859
 
860
+ // Ensure the selection is within the codeblock
1008
861
  if (!$isCodeHighlightNode(anchorNode) || !$isCodeHighlightNode(focusNode)) {
1009
862
  return false;
1010
863
  }
1011
-
1012
864
  if (!event.altKey) {
1013
865
  // Handle moving selection out of the code block, given there are no
1014
866
  // sibling thats can natively take the selection.
1015
867
  if (selection.isCollapsed()) {
1016
868
  const codeNode = anchorNode.getParentOrThrow();
1017
-
1018
869
  if (arrowIsUp && anchorOffset === 0 && anchorNode.getPreviousSibling() === null) {
1019
870
  const codeNodeSibling = codeNode.getPreviousSibling();
1020
-
1021
871
  if (codeNodeSibling === null) {
1022
872
  codeNode.selectPrevious();
1023
873
  event.preventDefault();
@@ -1025,7 +875,6 @@ function handleShiftLines(type, event) {
1025
875
  }
1026
876
  } else if (!arrowIsUp && anchorOffset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null) {
1027
877
  const codeNodeSibling = codeNode.getNextSibling();
1028
-
1029
878
  if (codeNodeSibling === null) {
1030
879
  codeNode.selectNext();
1031
880
  event.preventDefault();
@@ -1033,50 +882,39 @@ function handleShiftLines(type, event) {
1033
882
  }
1034
883
  }
1035
884
  }
1036
-
1037
885
  return false;
1038
886
  }
1039
-
1040
887
  const start = getFirstCodeHighlightNodeOfLine(anchorNode);
1041
888
  const end = getLastCodeHighlightNodeOfLine(focusNode);
1042
-
1043
889
  if (start == null || end == null) {
1044
890
  return false;
1045
891
  }
1046
-
1047
892
  const range = start.getNodesBetween(end);
1048
-
1049
893
  for (let i = 0; i < range.length; i++) {
1050
894
  const node = range[i];
1051
-
1052
895
  if (!$isCodeHighlightNode(node) && !lexical.$isLineBreakNode(node)) {
1053
896
  return false;
1054
897
  }
1055
- } // After this point, we know the selection is within the codeblock. We may not be able to
898
+ }
899
+
900
+ // After this point, we know the selection is within the codeblock. We may not be able to
1056
901
  // actually move the lines around, but we want to return true either way to prevent
1057
902
  // the event's default behavior
1058
-
1059
-
1060
903
  event.preventDefault();
1061
904
  event.stopPropagation(); // required to stop cursor movement under Firefox
1062
905
 
1063
906
  const linebreak = arrowIsUp ? start.getPreviousSibling() : end.getNextSibling();
1064
-
1065
907
  if (!lexical.$isLineBreakNode(linebreak)) {
1066
908
  return true;
1067
909
  }
1068
-
1069
910
  const sibling = arrowIsUp ? linebreak.getPreviousSibling() : linebreak.getNextSibling();
1070
-
1071
911
  if (sibling == null) {
1072
912
  return true;
1073
913
  }
1074
-
1075
914
  const maybeInsertionPoint = arrowIsUp ? getFirstCodeHighlightNodeOfLine(sibling) : getLastCodeHighlightNodeOfLine(sibling);
1076
915
  let insertionPoint = maybeInsertionPoint != null ? maybeInsertionPoint : sibling;
1077
916
  linebreak.remove();
1078
917
  range.forEach(node => node.remove());
1079
-
1080
918
  if (type === lexical.KEY_ARROW_UP_COMMAND) {
1081
919
  range.forEach(node => insertionPoint.insertBefore(node));
1082
920
  insertionPoint.insertBefore(linebreak);
@@ -1088,18 +926,14 @@ function handleShiftLines(type, event) {
1088
926
  insertionPoint = node;
1089
927
  });
1090
928
  }
1091
-
1092
929
  selection.setTextNodeRange(anchorNode, anchorOffset, focusNode, focusOffset);
1093
930
  return true;
1094
931
  }
1095
-
1096
932
  function handleMoveTo(type, event) {
1097
933
  const selection = lexical.$getSelection();
1098
-
1099
934
  if (!lexical.$isRangeSelection(selection)) {
1100
935
  return false;
1101
936
  }
1102
-
1103
937
  const {
1104
938
  anchor,
1105
939
  focus
@@ -1107,14 +941,11 @@ function handleMoveTo(type, event) {
1107
941
  const anchorNode = anchor.getNode();
1108
942
  const focusNode = focus.getNode();
1109
943
  const isMoveToStart = type === lexical.MOVE_TO_START;
1110
-
1111
944
  if (!$isCodeHighlightNode(anchorNode) || !$isCodeHighlightNode(focusNode)) {
1112
945
  return false;
1113
946
  }
1114
-
1115
947
  let node;
1116
948
  let offset;
1117
-
1118
949
  if (isMoveToStart) {
1119
950
  ({
1120
951
  node,
@@ -1126,31 +957,25 @@ function handleMoveTo(type, event) {
1126
957
  offset
1127
958
  } = getEndOfCodeInLine(focusNode));
1128
959
  }
1129
-
1130
960
  if (node !== null && offset !== -1) {
1131
961
  selection.setTextNodeRange(node, offset, node, offset);
1132
962
  }
1133
-
1134
963
  event.preventDefault();
1135
964
  event.stopPropagation();
1136
965
  return true;
1137
966
  }
1138
-
1139
967
  function registerCodeHighlighting(editor, tokenizer) {
1140
968
  if (!editor.hasNodes([CodeNode, CodeHighlightNode])) {
1141
969
  throw new Error('CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor');
1142
970
  }
1143
-
1144
971
  if (tokenizer == null) {
1145
972
  tokenizer = PrismTokenizer;
1146
973
  }
1147
-
1148
974
  return utils.mergeRegister(editor.registerMutationListener(CodeNode, mutations => {
1149
975
  editor.update(() => {
1150
976
  for (const [key, type] of mutations) {
1151
977
  if (type !== 'destroyed') {
1152
978
  const node = lexical.$getNodeByKey(key);
1153
-
1154
979
  if (node !== null) {
1155
980
  updateCodeGutter(node, editor);
1156
981
  }
package/package.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "code"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.7.8",
11
+ "version": "0.8.0",
12
12
  "main": "LexicalCode.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.7.8"
14
+ "lexical": "0.8.0"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/utils": "0.7.8",
17
+ "@lexical/utils": "0.8.0",
18
18
  "prismjs": "^1.27.0"
19
19
  },
20
20
  "repository": {