@lexical/react 0.1.14 → 0.1.17
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/DEPRECATED_useLexicalAutoFormatter.dev.js +5 -741
- package/DEPRECATED_useLexicalAutoFormatter.prod.js +1 -21
- package/DEPRECATED_useLexicalCanShowPlaceholder.dev.js +4 -73
- package/DEPRECATED_useLexicalCanShowPlaceholder.prod.js +1 -2
- package/DEPRECATED_useLexicalCharacterLimit.dev.js +19 -72
- package/DEPRECATED_useLexicalCharacterLimit.prod.js +7 -8
- package/DEPRECATED_useLexicalDecorators.dev.js +1 -1
- package/DEPRECATED_useLexicalDecorators.prod.js +1 -1
- package/DEPRECATED_useLexicalEditorEvents.dev.js +1 -1
- package/DEPRECATED_useLexicalEditorEvents.prod.js +1 -1
- package/DEPRECATED_useLexicalHistory.dev.js +5 -307
- package/DEPRECATED_useLexicalHistory.prod.js +1 -7
- package/DEPRECATED_useLexicalList.dev.js +29 -25
- package/DEPRECATED_useLexicalList.prod.js +3 -1
- package/DEPRECATED_useLexicalPlainText.dev.js +8 -687
- package/DEPRECATED_useLexicalPlainText.prod.js +2 -15
- package/DEPRECATED_useLexicalRichText.dev.js +8 -772
- package/DEPRECATED_useLexicalRichText.prod.js +2 -17
- package/LICENSE +1 -1
- package/{withSubscriptions.prod.js → LexicalAutoFocusPlugin.d.ts} +3 -1
- package/{withSubscriptions.dev.js → LexicalAutoFocusPlugin.dev.js} +10 -5
- package/LexicalAutoFocusPlugin.js +9 -0
- package/{LexicalAutoFormatterPlugin.js.flow → LexicalAutoFocusPlugin.js.flow} +1 -1
- package/LexicalAutoFocusPlugin.prod.js +7 -0
- package/LexicalAutoLinkPlugin.dev.js +12 -15
- package/LexicalAutoLinkPlugin.prod.js +6 -6
- package/LexicalAutoScrollPlugin.d.ts +13 -0
- package/LexicalAutoScrollPlugin.dev.js +82 -0
- package/LexicalAutoScrollPlugin.js +9 -0
- package/{withSubscriptions.d.ts → LexicalAutoScrollPlugin.js.flow} +5 -5
- package/LexicalAutoScrollPlugin.prod.js +8 -0
- package/LexicalCharacterLimitPlugin.dev.js +19 -72
- package/LexicalCharacterLimitPlugin.prod.js +8 -9
- package/LexicalClearEditorPlugin.dev.js +15 -19
- package/LexicalClearEditorPlugin.prod.js +1 -1
- package/LexicalCollaborationPlugin.d.ts +8 -3
- package/LexicalCollaborationPlugin.dev.js +70 -47
- package/LexicalCollaborationPlugin.js.flow +9 -3
- package/LexicalCollaborationPlugin.prod.js +10 -8
- package/LexicalComposer.d.ts +2 -2
- package/LexicalComposer.dev.js +3 -19
- package/LexicalComposer.js.flow +2 -2
- package/LexicalComposer.prod.js +2 -3
- package/LexicalContentEditable.dev.js +3 -1
- package/LexicalContentEditable.prod.js +2 -2
- package/LexicalHashtagPlugin.dev.js +21 -92
- package/LexicalHashtagPlugin.prod.js +4 -7
- package/LexicalHistoryPlugin.dev.js +5 -307
- package/LexicalHistoryPlugin.prod.js +1 -7
- package/LexicalHorizontalRuleNode.d.ts +3 -1
- package/LexicalHorizontalRuleNode.dev.js +2 -0
- package/LexicalHorizontalRuleNode.js.flow +6 -2
- package/LexicalHorizontalRuleNode.prod.js +2 -2
- package/LexicalLinkPlugin.dev.js +19 -20
- package/LexicalLinkPlugin.prod.js +4 -3
- package/LexicalListPlugin.dev.js +29 -25
- package/LexicalListPlugin.prod.js +3 -2
- package/{LexicalAutoFormatterPlugin.d.ts → LexicalMarkdownShortcutPlugin.d.ts} +1 -1
- package/LexicalMarkdownShortcutPlugin.dev.js +42 -0
- package/LexicalMarkdownShortcutPlugin.js +9 -0
- package/{withSubscriptions.js.flow → LexicalMarkdownShortcutPlugin.js.flow} +1 -4
- package/LexicalMarkdownShortcutPlugin.prod.js +7 -0
- package/LexicalNestedComposer.dev.js +20 -15
- package/LexicalNestedComposer.prod.js +3 -3
- package/LexicalOnChangePlugin.dev.js +16 -3
- package/LexicalOnChangePlugin.prod.js +2 -1
- package/LexicalPlainTextPlugin.dev.js +9 -455
- package/LexicalPlainTextPlugin.prod.js +4 -12
- package/LexicalRichTextPlugin.dev.js +9 -540
- package/LexicalRichTextPlugin.prod.js +4 -13
- package/LexicalTablePlugin.dev.js +36 -35
- package/LexicalTablePlugin.prod.js +3 -3
- package/LexicalTreeView.dev.js +1 -1
- package/LexicalTreeView.prod.js +1 -1
- package/package.json +17 -11
- package/useLexicalIsTextContentEmpty.dev.js +4 -33
- package/useLexicalIsTextContentEmpty.prod.js +1 -2
- package/useLexicalNodeSelection.dev.js +1 -1
- package/useLexicalNodeSelection.prod.js +1 -1
- package/useLexicalTextEntity.d.ts +19 -0
- package/useLexicalTextEntity.dev.js +29 -0
- package/{withSubscriptions.js → useLexicalTextEntity.js} +2 -2
- package/useLexicalTextEntity.js.flow +18 -0
- package/useLexicalTextEntity.prod.js +7 -0
- package/LexicalAutoFormatterPlugin.dev.js +0 -778
- package/LexicalAutoFormatterPlugin.js +0 -9
- package/LexicalAutoFormatterPlugin.prod.js +0 -27
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
|
|
10
|
-
var list = require('@lexical/list');
|
|
11
|
-
var lexical = require('lexical');
|
|
12
|
-
var CodeNode = require('lexical/CodeNode');
|
|
13
|
-
var react = require('react');
|
|
14
|
-
var LexicalHorizontalRuleNode = require('@lexical/react/LexicalHorizontalRuleNode');
|
|
15
|
-
var HeadingNode = require('lexical/HeadingNode');
|
|
16
|
-
var QuoteNode = require('lexical/QuoteNode');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
20
|
-
*
|
|
21
|
-
* This source code is licensed under the MIT license found in the
|
|
22
|
-
* LICENSE file in the root directory of this source tree.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
// Caution, this function creates a string and should not be used within a tight loop.
|
|
27
|
-
// Use $getNodeWithOffsetsFromJoinedTextNodesFromElementNode below to convert
|
|
28
|
-
// indexes in the return string back into their corresponding node and offsets.
|
|
29
|
-
|
|
30
|
-
function $joinTextNodesInElementNode(elementNode, separator, stopAt) {
|
|
31
|
-
let textContent = '';
|
|
32
|
-
const children = elementNode.getChildren();
|
|
33
|
-
const length = children.length;
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < length; ++i) {
|
|
36
|
-
const child = children[i];
|
|
37
|
-
|
|
38
|
-
if (lexical.$isTextNode(child)) {
|
|
39
|
-
const childTextContent = child.getTextContent();
|
|
40
|
-
|
|
41
|
-
if (child.is(stopAt.node)) {
|
|
42
|
-
if (stopAt.offset > childTextContent.length) {
|
|
43
|
-
{
|
|
44
|
-
throw Error(`Node ${child.__key} and selection point do not match.`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
textContent += child.getTextContent().substr(0, stopAt.offset);
|
|
49
|
-
break;
|
|
50
|
-
} else {
|
|
51
|
-
textContent += childTextContent;
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
textContent += separator;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return textContent;
|
|
59
|
-
} // This function converts the offsetInJoinedText to
|
|
60
|
-
// a node and offset result or null if not found.
|
|
61
|
-
// This function is to be used in conjunction with joinTextNodesInElementNode above.
|
|
62
|
-
// The joinedTextContent should be return value from joinTextNodesInElementNode.
|
|
63
|
-
//
|
|
64
|
-
// The offsetInJoinedText is relative to the entire string which
|
|
65
|
-
// itself is relevant to the parent ElementNode.
|
|
66
|
-
//
|
|
67
|
-
// Example:
|
|
68
|
-
// Given a Paragraph with 2 TextNodes. The first is Hello, the second is World.
|
|
69
|
-
// The joinedTextContent would be "HelloWorld"
|
|
70
|
-
// The offsetInJoinedText might be for the letter "e" = 1 or "r" = 7.
|
|
71
|
-
// The return values would be {TextNode1, 1} or {TextNode2,2}, respectively.
|
|
72
|
-
|
|
73
|
-
function $findNodeWithOffsetFromJoinedText(elementNode, joinedTextLength, offsetInJoinedText, separatorLength) {
|
|
74
|
-
const children = elementNode.getChildren();
|
|
75
|
-
const childrenLength = children.length;
|
|
76
|
-
let runningLength = 0;
|
|
77
|
-
let isPriorNodeTextNode = false;
|
|
78
|
-
|
|
79
|
-
for (let i = 0; i < childrenLength; ++i) {
|
|
80
|
-
// We must examine the offsetInJoinedText that is located
|
|
81
|
-
// at the length of the string.
|
|
82
|
-
// For example, given "hello", the length is 5, yet
|
|
83
|
-
// the caller still wants the node + offset at the
|
|
84
|
-
// right edge of the "o".
|
|
85
|
-
if (runningLength > joinedTextLength) {
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const child = children[i];
|
|
90
|
-
const isChildNodeTestNode = lexical.$isTextNode(child);
|
|
91
|
-
const childContentLength = isChildNodeTestNode ? child.getTextContent().length : separatorLength;
|
|
92
|
-
const newRunningLength = runningLength + childContentLength;
|
|
93
|
-
const isJoinedOffsetWithinNode = isPriorNodeTextNode === false && runningLength === offsetInJoinedText || runningLength === 0 && runningLength === offsetInJoinedText || runningLength < offsetInJoinedText && offsetInJoinedText <= newRunningLength;
|
|
94
|
-
|
|
95
|
-
if (isJoinedOffsetWithinNode && lexical.$isTextNode(child)) {
|
|
96
|
-
// Check isTextNode again for flow.
|
|
97
|
-
return {
|
|
98
|
-
node: child,
|
|
99
|
-
offset: offsetInJoinedText - runningLength
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
runningLength = newRunningLength;
|
|
104
|
-
isPriorNodeTextNode = isChildNodeTestNode;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
112
|
-
*
|
|
113
|
-
* This source code is licensed under the MIT license found in the
|
|
114
|
-
* LICENSE file in the root directory of this source tree.
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*/
|
|
118
|
-
// from the prior and current EditorState.
|
|
119
|
-
// This is then used to determined if an auto format has been triggered.
|
|
120
|
-
|
|
121
|
-
const TRIGGER_STRING = '\u0020'; // The space key triggers markdown.
|
|
122
|
-
|
|
123
|
-
const TRIGGER_STRING_LENGTH = TRIGGER_STRING.length;
|
|
124
|
-
const SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES = '\u0004'; // Select an unused unicode character to separate text and non-text nodes.
|
|
125
|
-
|
|
126
|
-
const autoFormatBase = {
|
|
127
|
-
nodeTransformationKind: null,
|
|
128
|
-
regEx: /(?:)/,
|
|
129
|
-
requiresParagraphStart: false
|
|
130
|
-
};
|
|
131
|
-
const paragraphStartBase = { ...autoFormatBase,
|
|
132
|
-
requiresParagraphStart: true
|
|
133
|
-
};
|
|
134
|
-
const markdownHeader1 = { ...paragraphStartBase,
|
|
135
|
-
nodeTransformationKind: 'paragraphH1',
|
|
136
|
-
regEx: /^(?:# )/
|
|
137
|
-
};
|
|
138
|
-
const markdownHeader2 = { ...paragraphStartBase,
|
|
139
|
-
nodeTransformationKind: 'paragraphH2',
|
|
140
|
-
regEx: /^(?:## )/
|
|
141
|
-
};
|
|
142
|
-
const markdownHeader3 = { ...paragraphStartBase,
|
|
143
|
-
nodeTransformationKind: 'paragraphH2',
|
|
144
|
-
regEx: /^(?:### )/
|
|
145
|
-
};
|
|
146
|
-
const markdownBlockQuote = { ...paragraphStartBase,
|
|
147
|
-
nodeTransformationKind: 'paragraphBlockQuote',
|
|
148
|
-
regEx: /^(?:> )/
|
|
149
|
-
};
|
|
150
|
-
const markdownUnorderedListDash = { ...paragraphStartBase,
|
|
151
|
-
nodeTransformationKind: 'paragraphUnorderedList',
|
|
152
|
-
regEx: /^(?:- )/
|
|
153
|
-
};
|
|
154
|
-
const markdownUnorderedListAsterisk = { ...paragraphStartBase,
|
|
155
|
-
nodeTransformationKind: 'paragraphUnorderedList',
|
|
156
|
-
regEx: /^(?:\* )/
|
|
157
|
-
};
|
|
158
|
-
const markdownCodeBlock = { ...paragraphStartBase,
|
|
159
|
-
nodeTransformationKind: 'paragraphCodeBlock',
|
|
160
|
-
regEx: /^(```)([a-z]*)( )/
|
|
161
|
-
};
|
|
162
|
-
const markdownOrderedList = { ...paragraphStartBase,
|
|
163
|
-
nodeTransformationKind: 'paragraphOrderedList',
|
|
164
|
-
regEx: /^(\d+)\.\s/
|
|
165
|
-
};
|
|
166
|
-
const markdownHorizontalRule = { ...paragraphStartBase,
|
|
167
|
-
nodeTransformationKind: 'horizontalRule',
|
|
168
|
-
regEx: /^(?:\*\*\* )/
|
|
169
|
-
};
|
|
170
|
-
const markdownHorizontalRuleUsingDashes = { ...paragraphStartBase,
|
|
171
|
-
nodeTransformationKind: 'horizontalRule',
|
|
172
|
-
regEx: /^(?:--- )/
|
|
173
|
-
};
|
|
174
|
-
const markdownItalic = { ...autoFormatBase,
|
|
175
|
-
nodeTransformationKind: 'italic',
|
|
176
|
-
regEx: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*\s)$/
|
|
177
|
-
};
|
|
178
|
-
const markdownBold = { ...autoFormatBase,
|
|
179
|
-
nodeTransformationKind: 'bold',
|
|
180
|
-
regEx: /(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*\s)$/
|
|
181
|
-
};
|
|
182
|
-
const markdownBoldWithUnderlines = { ...autoFormatBase,
|
|
183
|
-
nodeTransformationKind: 'bold',
|
|
184
|
-
regEx: /(__)(\s*)([^__]*)(\s*)(__\s)$/
|
|
185
|
-
};
|
|
186
|
-
const markdownBoldItalic = { ...autoFormatBase,
|
|
187
|
-
nodeTransformationKind: 'bold_italic',
|
|
188
|
-
regEx: /(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*\s)$/
|
|
189
|
-
}; // Markdown does not support underline, but we can allow folks to use
|
|
190
|
-
// the HTML tags for underline.
|
|
191
|
-
|
|
192
|
-
const fakeMarkdownUnderline = { ...autoFormatBase,
|
|
193
|
-
nodeTransformationKind: 'underline',
|
|
194
|
-
regEx: /(\<u\>)(\s*\b)([^\<]*)(\b\s*)(\<\/u\>\s)$/
|
|
195
|
-
};
|
|
196
|
-
const markdownStrikethrough = { ...autoFormatBase,
|
|
197
|
-
nodeTransformationKind: 'strikethrough',
|
|
198
|
-
regEx: /(~~)(\s*\b)([^~~]*)(\b\s*)(~~\s)$/
|
|
199
|
-
};
|
|
200
|
-
const allAutoFormatCriteriaForTextNodes = [markdownBoldItalic, markdownItalic, markdownBold, markdownBoldWithUnderlines, fakeMarkdownUnderline, markdownStrikethrough];
|
|
201
|
-
const allAutoFormatCriteria = [markdownHeader1, markdownHeader2, markdownHeader3, markdownBlockQuote, markdownUnorderedListDash, markdownUnorderedListAsterisk, markdownOrderedList, markdownCodeBlock, markdownHorizontalRule, markdownHorizontalRuleUsingDashes, ...allAutoFormatCriteriaForTextNodes];
|
|
202
|
-
function getAllAutoFormatCriteriaForTextNodes() {
|
|
203
|
-
return allAutoFormatCriteriaForTextNodes;
|
|
204
|
-
}
|
|
205
|
-
function getAllAutoFormatCriteria() {
|
|
206
|
-
return allAutoFormatCriteria;
|
|
207
|
-
}
|
|
208
|
-
function getInitialScanningContext(textNodeWithOffset, triggerState) {
|
|
209
|
-
return {
|
|
210
|
-
autoFormatCriteria: {
|
|
211
|
-
nodeTransformationKind: 'noTransformation',
|
|
212
|
-
regEx: /(?:)/,
|
|
213
|
-
// Empty reg ex will do until the precise criteria is discovered.
|
|
214
|
-
requiresParagraphStart: null
|
|
215
|
-
},
|
|
216
|
-
joinedText: null,
|
|
217
|
-
matchResultContext: {
|
|
218
|
-
offsetInJoinedTextForCollapsedSelection: 0,
|
|
219
|
-
regExCaptureGroups: []
|
|
220
|
-
},
|
|
221
|
-
textNodeWithOffset,
|
|
222
|
-
triggerState
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function getMatchResultContextWithRegEx(textToSearch, matchMustAppearAtStartOfString, matchMustAppearAtEndOfString, regEx) {
|
|
227
|
-
const matchResultContext = {
|
|
228
|
-
offsetInJoinedTextForCollapsedSelection: 0,
|
|
229
|
-
regExCaptureGroups: []
|
|
230
|
-
};
|
|
231
|
-
const regExMatches = textToSearch.match(regEx);
|
|
232
|
-
|
|
233
|
-
if (regExMatches !== null && regExMatches.length > 0 && (matchMustAppearAtStartOfString === false || regExMatches.index === 0) && (matchMustAppearAtEndOfString === false || regExMatches.index + regExMatches[0].length === textToSearch.length)) {
|
|
234
|
-
matchResultContext.offsetInJoinedTextForCollapsedSelection = textToSearch.length;
|
|
235
|
-
const captureGroupsCount = regExMatches.length;
|
|
236
|
-
let runningLength = regExMatches.index;
|
|
237
|
-
|
|
238
|
-
for (let captureGroupIndex = 0; captureGroupIndex < captureGroupsCount; captureGroupIndex++) {
|
|
239
|
-
const textContent = regExMatches[captureGroupIndex];
|
|
240
|
-
matchResultContext.regExCaptureGroups.push({
|
|
241
|
-
anchorTextNodeWithOffset: null,
|
|
242
|
-
focusTextNodeWithOffset: null,
|
|
243
|
-
offsetInParent: runningLength,
|
|
244
|
-
text: textContent,
|
|
245
|
-
textLength: textContent.length - (captureGroupIndex + 1 === captureGroupsCount ? TRIGGER_STRING_LENGTH : 0)
|
|
246
|
-
}); // The 0th capture group is special in that it's text contents is
|
|
247
|
-
// a join of all subsequent capture groups. So, skip this group
|
|
248
|
-
// when calculating the runningLength.
|
|
249
|
-
|
|
250
|
-
if (captureGroupIndex > 0) {
|
|
251
|
-
runningLength += textContent.length;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return matchResultContext;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function getMatchResultContextForParagraphs(autoFormatCriteria, scanningContext) {
|
|
262
|
-
const textNodeWithOffset = scanningContext.textNodeWithOffset; // At start of paragraph.
|
|
263
|
-
|
|
264
|
-
if (textNodeWithOffset.node.getPreviousSibling() === null) {
|
|
265
|
-
const textToSearch = scanningContext.textNodeWithOffset.node.getTextContent();
|
|
266
|
-
return getMatchResultContextWithRegEx(textToSearch, true, false, autoFormatCriteria.regEx);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function getMatchResultContextForText(autoFormatCriteria, scanningContext) {
|
|
273
|
-
if (scanningContext.joinedText == null) {
|
|
274
|
-
const parentNode = scanningContext.textNodeWithOffset.node.getParentOrThrow();
|
|
275
|
-
|
|
276
|
-
if (lexical.$isElementNode(parentNode)) {
|
|
277
|
-
if (scanningContext.joinedText == null) {
|
|
278
|
-
// Lazy calculate the text to search.
|
|
279
|
-
scanningContext.joinedText = $joinTextNodesInElementNode(parentNode, SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES, scanningContext.textNodeWithOffset);
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
{
|
|
283
|
-
throw Error(`Expected node ${parentNode.__key} to to be a ElementNode.`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return getMatchResultContextWithRegEx(scanningContext.joinedText, false, true, autoFormatCriteria.regEx);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function getMatchResultContextForCriteria(autoFormatCriteria, scanningContext) {
|
|
292
|
-
if (autoFormatCriteria.requiresParagraphStart !== null && autoFormatCriteria.requiresParagraphStart === true) {
|
|
293
|
-
return getMatchResultContextForParagraphs(autoFormatCriteria, scanningContext);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return getMatchResultContextForText(autoFormatCriteria, scanningContext);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function getNewNodeForCriteria(scanningContext, element) {
|
|
300
|
-
let newNode = null;
|
|
301
|
-
const children = element.getChildren();
|
|
302
|
-
const autoFormatCriteria = scanningContext.autoFormatCriteria;
|
|
303
|
-
const matchResultContext = scanningContext.matchResultContext;
|
|
304
|
-
|
|
305
|
-
if (autoFormatCriteria.nodeTransformationKind != null) {
|
|
306
|
-
switch (autoFormatCriteria.nodeTransformationKind) {
|
|
307
|
-
case 'paragraphH1':
|
|
308
|
-
{
|
|
309
|
-
newNode = HeadingNode.$createHeadingNode('h1');
|
|
310
|
-
newNode.append(...children);
|
|
311
|
-
return newNode;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
case 'paragraphH2':
|
|
315
|
-
{
|
|
316
|
-
newNode = HeadingNode.$createHeadingNode('h2');
|
|
317
|
-
newNode.append(...children);
|
|
318
|
-
return newNode;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
case 'paragraphH3':
|
|
322
|
-
{
|
|
323
|
-
newNode = HeadingNode.$createHeadingNode('h3');
|
|
324
|
-
newNode.append(...children);
|
|
325
|
-
return newNode;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
case 'paragraphBlockQuote':
|
|
329
|
-
{
|
|
330
|
-
newNode = QuoteNode.$createQuoteNode();
|
|
331
|
-
newNode.append(...children);
|
|
332
|
-
return newNode;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
case 'paragraphUnorderedList':
|
|
336
|
-
{
|
|
337
|
-
newNode = list.$createListNode('ul');
|
|
338
|
-
const listItem = list.$createListItemNode();
|
|
339
|
-
listItem.append(...children);
|
|
340
|
-
newNode.append(listItem);
|
|
341
|
-
return newNode;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
case 'paragraphOrderedList':
|
|
345
|
-
{
|
|
346
|
-
const startAsString = matchResultContext.regExCaptureGroups.length > 1 ? matchResultContext.regExCaptureGroups[matchResultContext.regExCaptureGroups.length - 1].text : '1';
|
|
347
|
-
const start = parseInt(startAsString, 10);
|
|
348
|
-
newNode = list.$createListNode('ol', start);
|
|
349
|
-
const listItem = list.$createListItemNode();
|
|
350
|
-
listItem.append(...children);
|
|
351
|
-
newNode.append(listItem);
|
|
352
|
-
return newNode;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
case 'paragraphCodeBlock':
|
|
356
|
-
{
|
|
357
|
-
// Toggle code and paragraph nodes.
|
|
358
|
-
if (scanningContext.triggerState != null && scanningContext.triggerState.isCodeBlock) {
|
|
359
|
-
newNode = lexical.$createParagraphNode();
|
|
360
|
-
} else {
|
|
361
|
-
newNode = CodeNode.$createCodeNode();
|
|
362
|
-
const codingLanguage = matchResultContext.regExCaptureGroups.length >= 3 ? matchResultContext.regExCaptureGroups[2].text : null;
|
|
363
|
-
|
|
364
|
-
if (codingLanguage != null && codingLanguage.length > 0) {
|
|
365
|
-
newNode.setLanguage(codingLanguage);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
newNode.append(...children);
|
|
370
|
-
return newNode;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
case 'horizontalRule':
|
|
374
|
-
{
|
|
375
|
-
// return null for newNode. Insert the HR here.
|
|
376
|
-
const horizontalRuleNode = LexicalHorizontalRuleNode.$createHorizontalRuleNode();
|
|
377
|
-
element.insertBefore(horizontalRuleNode);
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return newNode;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function updateTextNode(node, count) {
|
|
387
|
-
const textNode = node.spliceText(0, count, '', true);
|
|
388
|
-
|
|
389
|
-
if (textNode.getTextContent() === '') {
|
|
390
|
-
textNode.selectPrevious();
|
|
391
|
-
textNode.remove();
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function transformTextNodeForAutoFormatCriteria(scanningContext) {
|
|
396
|
-
if (scanningContext.autoFormatCriteria.requiresParagraphStart) {
|
|
397
|
-
transformTextNodeForParagraphs(scanningContext);
|
|
398
|
-
} else {
|
|
399
|
-
transformTextNodeForText(scanningContext);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function transformTextNodeForParagraphs(scanningContext) {
|
|
404
|
-
const textNodeWithOffset = scanningContext.textNodeWithOffset;
|
|
405
|
-
const element = textNodeWithOffset.node.getParentOrThrow();
|
|
406
|
-
const text = scanningContext.matchResultContext.regExCaptureGroups[0].text;
|
|
407
|
-
updateTextNode(textNodeWithOffset.node, text.length);
|
|
408
|
-
const elementNode = getNewNodeForCriteria(scanningContext, element);
|
|
409
|
-
|
|
410
|
-
if (elementNode !== null) {
|
|
411
|
-
element.replace(elementNode);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function getTextFormatType(nodeTransformationKind) {
|
|
416
|
-
switch (nodeTransformationKind) {
|
|
417
|
-
case 'italic':
|
|
418
|
-
case 'bold':
|
|
419
|
-
case 'underline':
|
|
420
|
-
case 'strikethrough':
|
|
421
|
-
return [nodeTransformationKind];
|
|
422
|
-
|
|
423
|
-
case 'bold_italic':
|
|
424
|
-
{
|
|
425
|
-
return ['bold', 'italic'];
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function transformTextNodeForText(scanningContext) {
|
|
433
|
-
const autoFormatCriteria = scanningContext.autoFormatCriteria;
|
|
434
|
-
const matchResultContext = scanningContext.matchResultContext;
|
|
435
|
-
|
|
436
|
-
if (autoFormatCriteria.nodeTransformationKind != null) {
|
|
437
|
-
if (matchResultContext.regExCaptureGroups.length !== 6) {
|
|
438
|
-
// For BIUS and other formatts which have a pattern + text + pattern,
|
|
439
|
-
// the expected reg ex pattern should have 6 groups.
|
|
440
|
-
// If it does not, then break and fail silently.
|
|
441
|
-
// e2e tests validate the regEx pattern.
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const formatting = getTextFormatType(autoFormatCriteria.nodeTransformationKind);
|
|
446
|
-
|
|
447
|
-
if (formatting != null) {
|
|
448
|
-
const captureGroupsToDelete = [1, 5];
|
|
449
|
-
const formatCaptureGroup = 3;
|
|
450
|
-
matchResultContext.regExCaptureGroups = getCaptureGroupsByResolvingAllDetails(scanningContext);
|
|
451
|
-
|
|
452
|
-
if (captureGroupsToDelete.length > 0) {
|
|
453
|
-
// Remove unwanted text in reg ex pattern.
|
|
454
|
-
removeTextInCaptureGroups(captureGroupsToDelete, matchResultContext);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
formatTextInCaptureGroupIndex(formatting, formatCaptureGroup, matchResultContext);
|
|
458
|
-
makeCollapsedSelectionAtOffsetInJoinedText(matchResultContext.offsetInJoinedTextForCollapsedSelection, matchResultContext.offsetInJoinedTextForCollapsedSelection + 1, scanningContext.textNodeWithOffset.node.getParentOrThrow());
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} // Some Capture Group Details were left lazily unresolved as their calculation
|
|
462
|
-
// was not necessary during the scanning phase. Now that the nodeTransformationKind is
|
|
463
|
-
// known, the details may be fully resolved without incurring unwasted performance cost.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
function getCaptureGroupsByResolvingAllDetails(scanningContext) {
|
|
467
|
-
const autoFormatCriteria = scanningContext.autoFormatCriteria;
|
|
468
|
-
const matchResultContext = scanningContext.matchResultContext;
|
|
469
|
-
const textNodeWithOffset = scanningContext.textNodeWithOffset;
|
|
470
|
-
const regExCaptureGroups = matchResultContext.regExCaptureGroups;
|
|
471
|
-
const captureGroupsCount = regExCaptureGroups.length;
|
|
472
|
-
const parentElementNode = textNodeWithOffset.node.getParentOrThrow();
|
|
473
|
-
|
|
474
|
-
if (scanningContext.joinedText == null) {
|
|
475
|
-
{
|
|
476
|
-
throw Error(`joinedText was not calculated`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const joinedTextLength = scanningContext.joinedText.length;
|
|
481
|
-
|
|
482
|
-
for (let captureGroupIndex = 1; captureGroupIndex < captureGroupsCount; captureGroupIndex++) {
|
|
483
|
-
const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
|
|
484
|
-
captureGroupDetail.anchorTextNodeWithOffset = $findNodeWithOffsetFromJoinedText(parentElementNode, joinedTextLength, captureGroupDetail.offsetInParent, TRIGGER_STRING_LENGTH);
|
|
485
|
-
captureGroupDetail.focusTextNodeWithOffset = $findNodeWithOffsetFromJoinedText(parentElementNode, joinedTextLength, captureGroupDetail.offsetInParent + captureGroupDetail.textLength, TRIGGER_STRING_LENGTH);
|
|
486
|
-
|
|
487
|
-
if (captureGroupDetail.textLength < 0) {
|
|
488
|
-
{
|
|
489
|
-
throw Error(`Bad regEx pattern found for ${autoFormatCriteria.nodeTransformationKind}`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return regExCaptureGroups;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function removeTextInCaptureGroups(regExCaptureGroupsToDelete, matchResultContext) {
|
|
498
|
-
const regExCaptureGroups = matchResultContext.regExCaptureGroups;
|
|
499
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
500
|
-
|
|
501
|
-
for (let i = regExCaptureGroupsToDelete.length - 1; i >= 0; i--) {
|
|
502
|
-
if (i < regExCaptureGroupsCount) {
|
|
503
|
-
const captureGroupIndex = regExCaptureGroupsToDelete[i];
|
|
504
|
-
const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
|
|
505
|
-
const anchorTextNodeWithOffset = captureGroupDetail.anchorTextNodeWithOffset;
|
|
506
|
-
const focusTextNodeWithOffset = captureGroupDetail.focusTextNodeWithOffset;
|
|
507
|
-
|
|
508
|
-
if (anchorTextNodeWithOffset != null && focusTextNodeWithOffset != null && captureGroupDetail.textLength > 0) {
|
|
509
|
-
const newSelection = lexical.$createRangeSelection();
|
|
510
|
-
newSelection.anchor.set(anchorTextNodeWithOffset.node.getKey(), anchorTextNodeWithOffset.offset, 'text');
|
|
511
|
-
newSelection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset, 'text');
|
|
512
|
-
lexical.$setSelection(newSelection);
|
|
513
|
-
const currentSelection = lexical.$getSelection();
|
|
514
|
-
|
|
515
|
-
if (lexical.$isRangeSelection(currentSelection)) {
|
|
516
|
-
currentSelection.removeText(); // Shift offsets for capture groups which are within the same node
|
|
517
|
-
|
|
518
|
-
if (anchorTextNodeWithOffset.node.getKey() === focusTextNodeWithOffset.node.getKey()) {
|
|
519
|
-
const delta = focusTextNodeWithOffset.offset - anchorTextNodeWithOffset.offset;
|
|
520
|
-
|
|
521
|
-
if (!(delta > 0)) {
|
|
522
|
-
throw Error(`Expected anchor and focus offsets to have ascending character order.`);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
shiftCaptureGroupOffsets(-delta, focusTextNodeWithOffset.offset, anchorTextNodeWithOffset.node, captureGroupIndex, matchResultContext);
|
|
526
|
-
} else {
|
|
527
|
-
const focusDelta = focusTextNodeWithOffset.offset;
|
|
528
|
-
|
|
529
|
-
if (focusDelta > 0) {
|
|
530
|
-
shiftCaptureGroupOffsets(-focusDelta, focusDelta, focusTextNodeWithOffset.node, captureGroupIndex, matchResultContext);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function shiftCaptureGroupOffsets(delta, applyAtOrAfterOffset, node, startingCaptureGroupIndex, matchResultContext) {
|
|
540
|
-
matchResultContext.offsetInJoinedTextForCollapsedSelection += delta;
|
|
541
|
-
|
|
542
|
-
if (!(matchResultContext.offsetInJoinedTextForCollapsedSelection > 0)) {
|
|
543
|
-
throw Error(`The text content string length does not correlate with insertions/deletions of new text.`);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const regExCaptureGroups = matchResultContext.regExCaptureGroups;
|
|
547
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
548
|
-
|
|
549
|
-
for (let captureGroupIndex = startingCaptureGroupIndex + 1; captureGroupIndex < regExCaptureGroupsCount; captureGroupIndex++) {
|
|
550
|
-
const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
|
|
551
|
-
|
|
552
|
-
if (captureGroupDetail.anchorTextNodeWithOffset != null && captureGroupDetail.anchorTextNodeWithOffset.offset >= applyAtOrAfterOffset && captureGroupDetail.anchorTextNodeWithOffset.node.is(node)) {
|
|
553
|
-
captureGroupDetail.anchorTextNodeWithOffset.offset += delta;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (captureGroupDetail.focusTextNodeWithOffset != null && captureGroupDetail.focusTextNodeWithOffset.offset >= applyAtOrAfterOffset && captureGroupDetail.focusTextNodeWithOffset.node.is(node)) {
|
|
557
|
-
captureGroupDetail.focusTextNodeWithOffset.offset += delta;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function formatTextInCaptureGroupIndex(formatTypes, captureGroupIndex, matchResultContext) {
|
|
563
|
-
const regExCaptureGroups = matchResultContext.regExCaptureGroups;
|
|
564
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
565
|
-
|
|
566
|
-
if (!(captureGroupIndex < regExCaptureGroupsCount)) {
|
|
567
|
-
throw Error(`The capture group count in the RegEx does match the actual capture group count.`);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
|
|
571
|
-
const anchorTextNodeWithOffset = captureGroupDetail.anchorTextNodeWithOffset;
|
|
572
|
-
const focusTextNodeWithOffset = captureGroupDetail.focusTextNodeWithOffset;
|
|
573
|
-
|
|
574
|
-
if (anchorTextNodeWithOffset != null && focusTextNodeWithOffset != null && captureGroupDetail.textLength > 0) {
|
|
575
|
-
const newSelection = lexical.$createRangeSelection();
|
|
576
|
-
newSelection.anchor.set(anchorTextNodeWithOffset.node.getKey(), anchorTextNodeWithOffset.offset, 'text');
|
|
577
|
-
newSelection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset, 'text');
|
|
578
|
-
lexical.$setSelection(newSelection);
|
|
579
|
-
const currentSelection = lexical.$getSelection();
|
|
580
|
-
|
|
581
|
-
if (lexical.$isRangeSelection(currentSelection)) {
|
|
582
|
-
for (let i = 0; i < formatTypes.length; i++) {
|
|
583
|
-
currentSelection.formatText(formatTypes[i]);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const finalSelection = lexical.$createRangeSelection();
|
|
587
|
-
finalSelection.anchor.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset + 1, 'text');
|
|
588
|
-
finalSelection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset + 1, 'text');
|
|
589
|
-
lexical.$setSelection(finalSelection);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function makeCollapsedSelectionAtOffsetInJoinedText(offsetInJoinedText, joinedTextLength, parentElementNode) {
|
|
595
|
-
const textNodeWithOffset = $findNodeWithOffsetFromJoinedText(parentElementNode, joinedTextLength, offsetInJoinedText, TRIGGER_STRING_LENGTH);
|
|
596
|
-
|
|
597
|
-
if (textNodeWithOffset != null) {
|
|
598
|
-
const newSelection = lexical.$createRangeSelection();
|
|
599
|
-
newSelection.anchor.set(textNodeWithOffset.node.getKey(), textNodeWithOffset.offset, 'text');
|
|
600
|
-
newSelection.focus.set(textNodeWithOffset.node.getKey(), textNodeWithOffset.offset, 'text');
|
|
601
|
-
lexical.$setSelection(newSelection);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
607
|
-
*
|
|
608
|
-
* This source code is licensed under the MIT license found in the
|
|
609
|
-
* LICENSE file in the root directory of this source tree.
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*/
|
|
613
|
-
|
|
614
|
-
function getCriteriaWithMatchResultContext(autoFormatCriteriaArray, scanningContext) {
|
|
615
|
-
const currentTriggerState = scanningContext.triggerState;
|
|
616
|
-
const count = autoFormatCriteriaArray.length;
|
|
617
|
-
|
|
618
|
-
for (let i = 0; i < count; i++) {
|
|
619
|
-
const autoFormatCriteria = autoFormatCriteriaArray[i]; // Skip code block nodes, unless the nodeTransformationKind calls for toggling the code block.
|
|
620
|
-
|
|
621
|
-
if (currentTriggerState != null && currentTriggerState.isCodeBlock === false || autoFormatCriteria.nodeTransformationKind === 'paragraphCodeBlock') {
|
|
622
|
-
const matchResultContext = getMatchResultContextForCriteria(autoFormatCriteria, scanningContext);
|
|
623
|
-
|
|
624
|
-
if (matchResultContext != null) {
|
|
625
|
-
return {
|
|
626
|
-
autoFormatCriteria: autoFormatCriteria,
|
|
627
|
-
matchResultContext
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
return {
|
|
634
|
-
autoFormatCriteria: null,
|
|
635
|
-
matchResultContext: null
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function getTextNodeForAutoFormatting(selection) {
|
|
640
|
-
if (!lexical.$isRangeSelection(selection)) {
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const node = selection.anchor.getNode();
|
|
645
|
-
|
|
646
|
-
if (!lexical.$isTextNode(node)) {
|
|
647
|
-
return null;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
return {
|
|
651
|
-
node,
|
|
652
|
-
offset: selection.anchor.offset
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function updateAutoFormatting(editor, scanningContext) {
|
|
657
|
-
editor.update(() => {
|
|
658
|
-
transformTextNodeForAutoFormatCriteria(scanningContext);
|
|
659
|
-
}, {
|
|
660
|
-
tag: 'history-push'
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function findScanningContextWithValidMatch(editorState, currentTriggerState) {
|
|
665
|
-
let scanningContext = null;
|
|
666
|
-
editorState.read(() => {
|
|
667
|
-
const textNodeWithOffset = getTextNodeForAutoFormatting(lexical.$getSelection());
|
|
668
|
-
|
|
669
|
-
if (textNodeWithOffset === null) {
|
|
670
|
-
return;
|
|
671
|
-
} // Please see the declaration of ScanningContext for a detailed explanation.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const initialScanningContext = getInitialScanningContext(textNodeWithOffset, currentTriggerState);
|
|
675
|
-
const criteriaWithMatchResultContext = getCriteriaWithMatchResultContext( // Do not apply paragraph node changes like blockQuote or H1 to listNodes. Also, do not attempt to transform a list into a list using * or -.
|
|
676
|
-
currentTriggerState.isParentAListItemNode === false ? getAllAutoFormatCriteria() : getAllAutoFormatCriteriaForTextNodes(), initialScanningContext);
|
|
677
|
-
|
|
678
|
-
if (criteriaWithMatchResultContext.autoFormatCriteria === null || criteriaWithMatchResultContext.matchResultContext === null) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
scanningContext = initialScanningContext; // Lazy fill-in the particular format criteria and any matching result information.
|
|
683
|
-
|
|
684
|
-
scanningContext.autoFormatCriteria = criteriaWithMatchResultContext.autoFormatCriteria;
|
|
685
|
-
scanningContext.matchResultContext = criteriaWithMatchResultContext.matchResultContext;
|
|
686
|
-
});
|
|
687
|
-
return scanningContext;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function findScanningContext(editorState, currentTriggerState, priorTriggerState) {
|
|
691
|
-
if (currentTriggerState == null || priorTriggerState == null) {
|
|
692
|
-
return null;
|
|
693
|
-
} // The below checks needs to execute relativey quickly, so perform the light-weight ones first.
|
|
694
|
-
// The substr check is a quick way to avoid autoformat parsing in that it looks for the autoformat
|
|
695
|
-
// trigger which is the trigger string (" ").
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const triggerStringLength = TRIGGER_STRING.length;
|
|
699
|
-
const currentTextContentLength = currentTriggerState.textContent.length;
|
|
700
|
-
const triggerOffset = currentTriggerState.anchorOffset - triggerStringLength;
|
|
701
|
-
|
|
702
|
-
if ((currentTriggerState.hasParentNode === true && currentTriggerState.isSimpleText && currentTriggerState.isSelectionCollapsed && currentTriggerState.nodeKey === priorTriggerState.nodeKey && currentTriggerState.anchorOffset !== priorTriggerState.anchorOffset && triggerOffset >= 0 && triggerOffset + triggerStringLength <= currentTextContentLength && currentTriggerState.textContent.substr(triggerOffset, triggerStringLength) === TRIGGER_STRING && currentTriggerState.textContent !== priorTriggerState.textContent) === false) {
|
|
703
|
-
return null;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
return findScanningContextWithValidMatch(editorState, currentTriggerState);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
function getTriggerState(editorState) {
|
|
710
|
-
let criteria = null;
|
|
711
|
-
editorState.read(() => {
|
|
712
|
-
const selection = lexical.$getSelection();
|
|
713
|
-
|
|
714
|
-
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const node = selection.anchor.getNode();
|
|
719
|
-
const parentNode = node.getParent();
|
|
720
|
-
const isParentAListItemNode = parentNode !== null && list.$isListItemNode(parentNode);
|
|
721
|
-
const hasParentNode = parentNode !== null;
|
|
722
|
-
criteria = {
|
|
723
|
-
anchorOffset: selection.anchor.offset,
|
|
724
|
-
hasParentNode,
|
|
725
|
-
isCodeBlock: CodeNode.$isCodeNode(node),
|
|
726
|
-
isParentAListItemNode,
|
|
727
|
-
isSelectionCollapsed: selection.isCollapsed(),
|
|
728
|
-
isSimpleText: lexical.$isTextNode(node) && node.isSimpleText(),
|
|
729
|
-
nodeKey: node.getKey(),
|
|
730
|
-
textContent: node.getTextContent()
|
|
731
|
-
};
|
|
732
|
-
});
|
|
733
|
-
return criteria;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function useAutoFormatter(editor) {
|
|
737
|
-
react.useEffect(() => {
|
|
738
|
-
// The priorTriggerState is compared against the currentTriggerState to determine
|
|
739
|
-
// if the user has performed some typing event that warrants an auto format.
|
|
740
|
-
// For example, typing "#" and then " ", shoud trigger an format.
|
|
741
|
-
// However, given "#A B", where the user delets "A" should not.
|
|
742
|
-
let priorTriggerState = null;
|
|
743
|
-
return editor.addListener('update', ({
|
|
744
|
-
tags
|
|
745
|
-
}) => {
|
|
746
|
-
// Examine historic so that we are not running autoformatting within markdown.
|
|
747
|
-
if (tags.has('historic') === false) {
|
|
748
|
-
const editorState = editor.getEditorState();
|
|
749
|
-
const currentTriggerState = getTriggerState(editorState);
|
|
750
|
-
const scanningContext = currentTriggerState == null ? null : findScanningContext(editorState, currentTriggerState, priorTriggerState);
|
|
751
|
-
|
|
752
|
-
if (scanningContext != null) {
|
|
753
|
-
updateAutoFormatting(editor, scanningContext);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
priorTriggerState = currentTriggerState;
|
|
757
|
-
} else {
|
|
758
|
-
priorTriggerState = null;
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
}, [editor]);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
766
|
-
*
|
|
767
|
-
* This source code is licensed under the MIT license found in the
|
|
768
|
-
* LICENSE file in the root directory of this source tree.
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*/
|
|
772
|
-
function AutoFormatterPlugin() {
|
|
773
|
-
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
774
|
-
useAutoFormatter(editor);
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
module.exports = AutoFormatterPlugin;
|