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