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