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