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