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