@ekz/lexical-text 0.40.0
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/EkzLexicalText.dev.js +345 -0
- package/EkzLexicalText.dev.mjs +337 -0
- package/EkzLexicalText.js +11 -0
- package/EkzLexicalText.mjs +18 -0
- package/EkzLexicalText.node.mjs +16 -0
- package/EkzLexicalText.prod.js +9 -0
- package/EkzLexicalText.prod.mjs +9 -0
- package/LICENSE +21 -0
- package/LexicalText.js.flow +43 -0
- package/README.md +5 -0
- package/canShowPlaceholder.d.ts +13 -0
- package/findTextIntersectionFromCharacters.d.ts +19 -0
- package/index.d.ts +18 -0
- package/isRootTextContentEmpty.d.ts +14 -0
- package/package.json +42 -0
- package/registerLexicalTextEntity.d.ts +32 -0
- package/rootTextContent.d.ts +5 -0
|
@@ -0,0 +1,345 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var lexical = require('@ekz/lexical');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
15
|
+
*
|
|
16
|
+
* This source code is licensed under the MIT license found in the
|
|
17
|
+
* LICENSE file in the root directory of this source tree.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the root's text content.
|
|
23
|
+
* @returns The root's text content.
|
|
24
|
+
*/
|
|
25
|
+
function $rootTextContent() {
|
|
26
|
+
const root = lexical.$getRoot();
|
|
27
|
+
return root.getTextContent();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
32
|
+
*
|
|
33
|
+
* This source code is licensed under the MIT license found in the
|
|
34
|
+
* LICENSE file in the root directory of this source tree.
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Determines if the root has any text content and can trim any whitespace if it does.
|
|
40
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
41
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
42
|
+
* @returns true if text content is empty, false if there is text or isEditorComposing is true.
|
|
43
|
+
*/
|
|
44
|
+
function $isRootTextContentEmpty(isEditorComposing, trim = true) {
|
|
45
|
+
if (isEditorComposing) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
let text = $rootTextContent();
|
|
49
|
+
if (trim) {
|
|
50
|
+
text = text.trim();
|
|
51
|
+
}
|
|
52
|
+
return text === '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns a function that executes {@link $isRootTextContentEmpty}
|
|
57
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
58
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
59
|
+
* @returns A function that executes $isRootTextContentEmpty based on arguments.
|
|
60
|
+
*/
|
|
61
|
+
function $isRootTextContentEmptyCurry(isEditorComposing, trim) {
|
|
62
|
+
return () => $isRootTextContentEmpty(isEditorComposing, trim);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
67
|
+
*
|
|
68
|
+
* This source code is licensed under the MIT license found in the
|
|
69
|
+
* LICENSE file in the root directory of this source tree.
|
|
70
|
+
*
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Determines if the input should show the placeholder. If anything is in
|
|
75
|
+
* in the root the placeholder should not be shown.
|
|
76
|
+
* @param isComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
77
|
+
* @returns true if the input should show the placeholder, false otherwise.
|
|
78
|
+
*/
|
|
79
|
+
function $canShowPlaceholder(isComposing) {
|
|
80
|
+
if (!$isRootTextContentEmpty(isComposing, false)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const root = lexical.$getRoot();
|
|
84
|
+
const children = root.getChildren();
|
|
85
|
+
const childrenLength = children.length;
|
|
86
|
+
if (childrenLength > 1) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
90
|
+
const topBlock = children[i];
|
|
91
|
+
if (lexical.$isDecoratorNode(topBlock)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (lexical.$isElementNode(topBlock)) {
|
|
95
|
+
if (!lexical.$isParagraphNode(topBlock)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (topBlock.__indent !== 0) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const topBlockChildren = topBlock.getChildren();
|
|
102
|
+
const topBlockChildrenLength = topBlockChildren.length;
|
|
103
|
+
for (let s = 0; s < topBlockChildrenLength; s++) {
|
|
104
|
+
const child = topBlockChildren[i];
|
|
105
|
+
if (!lexical.$isTextNode(child)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns a function that executes {@link $canShowPlaceholder}
|
|
116
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
117
|
+
* @returns A function that executes $canShowPlaceholder with arguments.
|
|
118
|
+
*/
|
|
119
|
+
function $canShowPlaceholderCurry(isEditorComposing) {
|
|
120
|
+
return () => $canShowPlaceholder(isEditorComposing);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
125
|
+
*
|
|
126
|
+
* This source code is licensed under the MIT license found in the
|
|
127
|
+
* LICENSE file in the root directory of this source tree.
|
|
128
|
+
*
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Finds a TextNode with a size larger than targetCharacters and returns
|
|
133
|
+
* the node along with the remaining length of the text.
|
|
134
|
+
* @param root - The RootNode.
|
|
135
|
+
* @param targetCharacters - The number of characters whose TextNode must be larger than.
|
|
136
|
+
* @returns The TextNode and the intersections offset, or null if no TextNode is found.
|
|
137
|
+
*/
|
|
138
|
+
function $findTextIntersectionFromCharacters(root, targetCharacters) {
|
|
139
|
+
let node = root.getFirstChild();
|
|
140
|
+
let currentCharacters = 0;
|
|
141
|
+
mainLoop: while (node !== null) {
|
|
142
|
+
if (lexical.$isElementNode(node)) {
|
|
143
|
+
const child = node.getFirstChild();
|
|
144
|
+
if (child !== null) {
|
|
145
|
+
node = child;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
} else if (lexical.$isTextNode(node)) {
|
|
149
|
+
const characters = node.getTextContentSize();
|
|
150
|
+
if (currentCharacters + characters > targetCharacters) {
|
|
151
|
+
return {
|
|
152
|
+
node,
|
|
153
|
+
offset: targetCharacters - currentCharacters
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
currentCharacters += characters;
|
|
157
|
+
}
|
|
158
|
+
const sibling = node.getNextSibling();
|
|
159
|
+
if (sibling !== null) {
|
|
160
|
+
node = sibling;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let parent = node.getParent();
|
|
164
|
+
while (parent !== null) {
|
|
165
|
+
const parentSibling = parent.getNextSibling();
|
|
166
|
+
if (parentSibling !== null) {
|
|
167
|
+
node = parentSibling;
|
|
168
|
+
continue mainLoop;
|
|
169
|
+
}
|
|
170
|
+
parent = parent.getParent();
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
179
|
+
*
|
|
180
|
+
* This source code is licensed under the MIT license found in the
|
|
181
|
+
* LICENSE file in the root directory of this source tree.
|
|
182
|
+
*
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
// Do not require this module directly! Use normal `invariant` calls.
|
|
186
|
+
|
|
187
|
+
function formatDevErrorMessage(message) {
|
|
188
|
+
throw new Error(message);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Returns a tuple that can be rested (...) into mergeRegister to clean up
|
|
193
|
+
* node transforms listeners that transforms text into another node, eg. a HashtagNode.
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* useEffect(() => {
|
|
197
|
+
return mergeRegister(
|
|
198
|
+
...registerLexicalTextEntity(editor, getMatch, targetNode, createNode),
|
|
199
|
+
);
|
|
200
|
+
}, [createNode, editor, getMatch, targetNode]);
|
|
201
|
+
* ```
|
|
202
|
+
* Where targetNode is the type of node containing the text you want to transform (like a text input),
|
|
203
|
+
* then getMatch uses a regex to find a matching text and creates the proper node to include the matching text.
|
|
204
|
+
* @param editor - The lexical editor.
|
|
205
|
+
* @param getMatch - Finds a matching string that satisfies a regex expression.
|
|
206
|
+
* @param targetNode - The node type that contains text to match with. eg. HashtagNode
|
|
207
|
+
* @param createNode - A function that creates a new node to contain the matched text. eg createHashtagNode
|
|
208
|
+
* @returns An array containing the plain text and reverse node transform listeners.
|
|
209
|
+
*/
|
|
210
|
+
function registerLexicalTextEntity(editor, getMatch, targetNode, createNode) {
|
|
211
|
+
const isTargetNode = node => {
|
|
212
|
+
return node instanceof targetNode;
|
|
213
|
+
};
|
|
214
|
+
const $replaceWithSimpleText = node => {
|
|
215
|
+
const textNode = lexical.$createTextNode(node.getTextContent());
|
|
216
|
+
textNode.setFormat(node.getFormat());
|
|
217
|
+
node.replace(textNode);
|
|
218
|
+
};
|
|
219
|
+
const getMode = node => {
|
|
220
|
+
return node.getLatest().__mode;
|
|
221
|
+
};
|
|
222
|
+
const $textNodeTransform = node => {
|
|
223
|
+
if (!node.isSimpleText()) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let prevSibling = node.getPreviousSibling();
|
|
227
|
+
let text = node.getTextContent();
|
|
228
|
+
let currentNode = node;
|
|
229
|
+
let match;
|
|
230
|
+
if (lexical.$isTextNode(prevSibling)) {
|
|
231
|
+
const previousText = prevSibling.getTextContent();
|
|
232
|
+
const combinedText = previousText + text;
|
|
233
|
+
const prevMatch = getMatch(combinedText);
|
|
234
|
+
if (isTargetNode(prevSibling)) {
|
|
235
|
+
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
|
236
|
+
$replaceWithSimpleText(prevSibling);
|
|
237
|
+
return;
|
|
238
|
+
} else {
|
|
239
|
+
const diff = prevMatch.end - previousText.length;
|
|
240
|
+
if (diff > 0) {
|
|
241
|
+
const concatText = text.slice(0, diff);
|
|
242
|
+
const newTextContent = previousText + concatText;
|
|
243
|
+
prevSibling.select();
|
|
244
|
+
prevSibling.setTextContent(newTextContent);
|
|
245
|
+
if (diff === text.length) {
|
|
246
|
+
node.remove();
|
|
247
|
+
} else {
|
|
248
|
+
const remainingText = text.slice(diff);
|
|
249
|
+
node.setTextContent(remainingText);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let prevMatchLengthToSkip = 0;
|
|
259
|
+
// eslint-disable-next-line no-constant-condition
|
|
260
|
+
while (true) {
|
|
261
|
+
match = getMatch(text);
|
|
262
|
+
let nextText = match === null ? '' : text.slice(match.end);
|
|
263
|
+
text = nextText;
|
|
264
|
+
if (nextText === '') {
|
|
265
|
+
const nextSibling = currentNode.getNextSibling();
|
|
266
|
+
if (lexical.$isTextNode(nextSibling)) {
|
|
267
|
+
nextText = currentNode.getTextContent() + nextSibling.getTextContent();
|
|
268
|
+
const nextMatch = getMatch(nextText);
|
|
269
|
+
if (nextMatch === null) {
|
|
270
|
+
if (isTargetNode(nextSibling)) {
|
|
271
|
+
$replaceWithSimpleText(nextSibling);
|
|
272
|
+
} else {
|
|
273
|
+
nextSibling.markDirty();
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
} else if (nextMatch.start !== 0) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (match === null) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (match.start === 0 && lexical.$isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
285
|
+
prevMatchLengthToSkip += match.end;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
let nodeToReplace;
|
|
289
|
+
if (match.start === 0) {
|
|
290
|
+
[nodeToReplace, currentNode] = currentNode.splitText(match.end);
|
|
291
|
+
} else {
|
|
292
|
+
[, nodeToReplace, currentNode] = currentNode.splitText(match.start + prevMatchLengthToSkip, match.end + prevMatchLengthToSkip);
|
|
293
|
+
}
|
|
294
|
+
if (!(nodeToReplace !== undefined)) {
|
|
295
|
+
formatDevErrorMessage(`${'nodeToReplace'} should not be undefined. You may want to check splitOffsets passed to the splitText.`);
|
|
296
|
+
}
|
|
297
|
+
const replacementNode = createNode(nodeToReplace);
|
|
298
|
+
replacementNode.setFormat(nodeToReplace.getFormat());
|
|
299
|
+
nodeToReplace.replace(replacementNode);
|
|
300
|
+
if (currentNode == null) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
prevMatchLengthToSkip = 0;
|
|
304
|
+
prevSibling = replacementNode;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const $reverseNodeTransform = node => {
|
|
308
|
+
const text = node.getTextContent();
|
|
309
|
+
const match = getMatch(text);
|
|
310
|
+
if (match === null || match.start !== 0) {
|
|
311
|
+
$replaceWithSimpleText(node);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (text.length > match.end) {
|
|
315
|
+
// This will split out the rest of the text as simple text
|
|
316
|
+
node.splitText(match.end);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const prevSibling = node.getPreviousSibling();
|
|
320
|
+
if (lexical.$isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
321
|
+
$replaceWithSimpleText(prevSibling);
|
|
322
|
+
$replaceWithSimpleText(node);
|
|
323
|
+
}
|
|
324
|
+
const nextSibling = node.getNextSibling();
|
|
325
|
+
if (lexical.$isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
|
326
|
+
$replaceWithSimpleText(nextSibling);
|
|
327
|
+
|
|
328
|
+
// This may have already been converted in the previous block
|
|
329
|
+
if (isTargetNode(node)) {
|
|
330
|
+
$replaceWithSimpleText(node);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const removePlainTextTransform = editor.registerNodeTransform(lexical.TextNode, $textNodeTransform);
|
|
335
|
+
const removeReverseNodeTransform = editor.registerNodeTransform(targetNode, $reverseNodeTransform);
|
|
336
|
+
return [removePlainTextTransform, removeReverseNodeTransform];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
exports.$canShowPlaceholder = $canShowPlaceholder;
|
|
340
|
+
exports.$canShowPlaceholderCurry = $canShowPlaceholderCurry;
|
|
341
|
+
exports.$findTextIntersectionFromCharacters = $findTextIntersectionFromCharacters;
|
|
342
|
+
exports.$isRootTextContentEmpty = $isRootTextContentEmpty;
|
|
343
|
+
exports.$isRootTextContentEmptyCurry = $isRootTextContentEmptyCurry;
|
|
344
|
+
exports.$rootTextContent = $rootTextContent;
|
|
345
|
+
exports.registerLexicalTextEntity = registerLexicalTextEntity;
|
|
@@ -0,0 +1,337 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { $getRoot, $isDecoratorNode, $isElementNode, $isParagraphNode, $isTextNode, TextNode, $createTextNode } from '@ekz/lexical';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the MIT license found in the
|
|
15
|
+
* LICENSE file in the root directory of this source tree.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the root's text content.
|
|
21
|
+
* @returns The root's text content.
|
|
22
|
+
*/
|
|
23
|
+
function $rootTextContent() {
|
|
24
|
+
const root = $getRoot();
|
|
25
|
+
return root.getTextContent();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
30
|
+
*
|
|
31
|
+
* This source code is licensed under the MIT license found in the
|
|
32
|
+
* LICENSE file in the root directory of this source tree.
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Determines if the root has any text content and can trim any whitespace if it does.
|
|
38
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
39
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
40
|
+
* @returns true if text content is empty, false if there is text or isEditorComposing is true.
|
|
41
|
+
*/
|
|
42
|
+
function $isRootTextContentEmpty(isEditorComposing, trim = true) {
|
|
43
|
+
if (isEditorComposing) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
let text = $rootTextContent();
|
|
47
|
+
if (trim) {
|
|
48
|
+
text = text.trim();
|
|
49
|
+
}
|
|
50
|
+
return text === '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns a function that executes {@link $isRootTextContentEmpty}
|
|
55
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
56
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
57
|
+
* @returns A function that executes $isRootTextContentEmpty based on arguments.
|
|
58
|
+
*/
|
|
59
|
+
function $isRootTextContentEmptyCurry(isEditorComposing, trim) {
|
|
60
|
+
return () => $isRootTextContentEmpty(isEditorComposing, trim);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
65
|
+
*
|
|
66
|
+
* This source code is licensed under the MIT license found in the
|
|
67
|
+
* LICENSE file in the root directory of this source tree.
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Determines if the input should show the placeholder. If anything is in
|
|
73
|
+
* in the root the placeholder should not be shown.
|
|
74
|
+
* @param isComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
75
|
+
* @returns true if the input should show the placeholder, false otherwise.
|
|
76
|
+
*/
|
|
77
|
+
function $canShowPlaceholder(isComposing) {
|
|
78
|
+
if (!$isRootTextContentEmpty(isComposing, false)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const root = $getRoot();
|
|
82
|
+
const children = root.getChildren();
|
|
83
|
+
const childrenLength = children.length;
|
|
84
|
+
if (childrenLength > 1) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
88
|
+
const topBlock = children[i];
|
|
89
|
+
if ($isDecoratorNode(topBlock)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if ($isElementNode(topBlock)) {
|
|
93
|
+
if (!$isParagraphNode(topBlock)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (topBlock.__indent !== 0) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const topBlockChildren = topBlock.getChildren();
|
|
100
|
+
const topBlockChildrenLength = topBlockChildren.length;
|
|
101
|
+
for (let s = 0; s < topBlockChildrenLength; s++) {
|
|
102
|
+
const child = topBlockChildren[i];
|
|
103
|
+
if (!$isTextNode(child)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Returns a function that executes {@link $canShowPlaceholder}
|
|
114
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
115
|
+
* @returns A function that executes $canShowPlaceholder with arguments.
|
|
116
|
+
*/
|
|
117
|
+
function $canShowPlaceholderCurry(isEditorComposing) {
|
|
118
|
+
return () => $canShowPlaceholder(isEditorComposing);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
123
|
+
*
|
|
124
|
+
* This source code is licensed under the MIT license found in the
|
|
125
|
+
* LICENSE file in the root directory of this source tree.
|
|
126
|
+
*
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Finds a TextNode with a size larger than targetCharacters and returns
|
|
131
|
+
* the node along with the remaining length of the text.
|
|
132
|
+
* @param root - The RootNode.
|
|
133
|
+
* @param targetCharacters - The number of characters whose TextNode must be larger than.
|
|
134
|
+
* @returns The TextNode and the intersections offset, or null if no TextNode is found.
|
|
135
|
+
*/
|
|
136
|
+
function $findTextIntersectionFromCharacters(root, targetCharacters) {
|
|
137
|
+
let node = root.getFirstChild();
|
|
138
|
+
let currentCharacters = 0;
|
|
139
|
+
mainLoop: while (node !== null) {
|
|
140
|
+
if ($isElementNode(node)) {
|
|
141
|
+
const child = node.getFirstChild();
|
|
142
|
+
if (child !== null) {
|
|
143
|
+
node = child;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
} else if ($isTextNode(node)) {
|
|
147
|
+
const characters = node.getTextContentSize();
|
|
148
|
+
if (currentCharacters + characters > targetCharacters) {
|
|
149
|
+
return {
|
|
150
|
+
node,
|
|
151
|
+
offset: targetCharacters - currentCharacters
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
currentCharacters += characters;
|
|
155
|
+
}
|
|
156
|
+
const sibling = node.getNextSibling();
|
|
157
|
+
if (sibling !== null) {
|
|
158
|
+
node = sibling;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
let parent = node.getParent();
|
|
162
|
+
while (parent !== null) {
|
|
163
|
+
const parentSibling = parent.getNextSibling();
|
|
164
|
+
if (parentSibling !== null) {
|
|
165
|
+
node = parentSibling;
|
|
166
|
+
continue mainLoop;
|
|
167
|
+
}
|
|
168
|
+
parent = parent.getParent();
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
177
|
+
*
|
|
178
|
+
* This source code is licensed under the MIT license found in the
|
|
179
|
+
* LICENSE file in the root directory of this source tree.
|
|
180
|
+
*
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
// Do not require this module directly! Use normal `invariant` calls.
|
|
184
|
+
|
|
185
|
+
function formatDevErrorMessage(message) {
|
|
186
|
+
throw new Error(message);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Returns a tuple that can be rested (...) into mergeRegister to clean up
|
|
191
|
+
* node transforms listeners that transforms text into another node, eg. a HashtagNode.
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* useEffect(() => {
|
|
195
|
+
return mergeRegister(
|
|
196
|
+
...registerLexicalTextEntity(editor, getMatch, targetNode, createNode),
|
|
197
|
+
);
|
|
198
|
+
}, [createNode, editor, getMatch, targetNode]);
|
|
199
|
+
* ```
|
|
200
|
+
* Where targetNode is the type of node containing the text you want to transform (like a text input),
|
|
201
|
+
* then getMatch uses a regex to find a matching text and creates the proper node to include the matching text.
|
|
202
|
+
* @param editor - The lexical editor.
|
|
203
|
+
* @param getMatch - Finds a matching string that satisfies a regex expression.
|
|
204
|
+
* @param targetNode - The node type that contains text to match with. eg. HashtagNode
|
|
205
|
+
* @param createNode - A function that creates a new node to contain the matched text. eg createHashtagNode
|
|
206
|
+
* @returns An array containing the plain text and reverse node transform listeners.
|
|
207
|
+
*/
|
|
208
|
+
function registerLexicalTextEntity(editor, getMatch, targetNode, createNode) {
|
|
209
|
+
const isTargetNode = node => {
|
|
210
|
+
return node instanceof targetNode;
|
|
211
|
+
};
|
|
212
|
+
const $replaceWithSimpleText = node => {
|
|
213
|
+
const textNode = $createTextNode(node.getTextContent());
|
|
214
|
+
textNode.setFormat(node.getFormat());
|
|
215
|
+
node.replace(textNode);
|
|
216
|
+
};
|
|
217
|
+
const getMode = node => {
|
|
218
|
+
return node.getLatest().__mode;
|
|
219
|
+
};
|
|
220
|
+
const $textNodeTransform = node => {
|
|
221
|
+
if (!node.isSimpleText()) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
let prevSibling = node.getPreviousSibling();
|
|
225
|
+
let text = node.getTextContent();
|
|
226
|
+
let currentNode = node;
|
|
227
|
+
let match;
|
|
228
|
+
if ($isTextNode(prevSibling)) {
|
|
229
|
+
const previousText = prevSibling.getTextContent();
|
|
230
|
+
const combinedText = previousText + text;
|
|
231
|
+
const prevMatch = getMatch(combinedText);
|
|
232
|
+
if (isTargetNode(prevSibling)) {
|
|
233
|
+
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
|
234
|
+
$replaceWithSimpleText(prevSibling);
|
|
235
|
+
return;
|
|
236
|
+
} else {
|
|
237
|
+
const diff = prevMatch.end - previousText.length;
|
|
238
|
+
if (diff > 0) {
|
|
239
|
+
const concatText = text.slice(0, diff);
|
|
240
|
+
const newTextContent = previousText + concatText;
|
|
241
|
+
prevSibling.select();
|
|
242
|
+
prevSibling.setTextContent(newTextContent);
|
|
243
|
+
if (diff === text.length) {
|
|
244
|
+
node.remove();
|
|
245
|
+
} else {
|
|
246
|
+
const remainingText = text.slice(diff);
|
|
247
|
+
node.setTextContent(remainingText);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
let prevMatchLengthToSkip = 0;
|
|
257
|
+
// eslint-disable-next-line no-constant-condition
|
|
258
|
+
while (true) {
|
|
259
|
+
match = getMatch(text);
|
|
260
|
+
let nextText = match === null ? '' : text.slice(match.end);
|
|
261
|
+
text = nextText;
|
|
262
|
+
if (nextText === '') {
|
|
263
|
+
const nextSibling = currentNode.getNextSibling();
|
|
264
|
+
if ($isTextNode(nextSibling)) {
|
|
265
|
+
nextText = currentNode.getTextContent() + nextSibling.getTextContent();
|
|
266
|
+
const nextMatch = getMatch(nextText);
|
|
267
|
+
if (nextMatch === null) {
|
|
268
|
+
if (isTargetNode(nextSibling)) {
|
|
269
|
+
$replaceWithSimpleText(nextSibling);
|
|
270
|
+
} else {
|
|
271
|
+
nextSibling.markDirty();
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
} else if (nextMatch.start !== 0) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (match === null) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
283
|
+
prevMatchLengthToSkip += match.end;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
let nodeToReplace;
|
|
287
|
+
if (match.start === 0) {
|
|
288
|
+
[nodeToReplace, currentNode] = currentNode.splitText(match.end);
|
|
289
|
+
} else {
|
|
290
|
+
[, nodeToReplace, currentNode] = currentNode.splitText(match.start + prevMatchLengthToSkip, match.end + prevMatchLengthToSkip);
|
|
291
|
+
}
|
|
292
|
+
if (!(nodeToReplace !== undefined)) {
|
|
293
|
+
formatDevErrorMessage(`${'nodeToReplace'} should not be undefined. You may want to check splitOffsets passed to the splitText.`);
|
|
294
|
+
}
|
|
295
|
+
const replacementNode = createNode(nodeToReplace);
|
|
296
|
+
replacementNode.setFormat(nodeToReplace.getFormat());
|
|
297
|
+
nodeToReplace.replace(replacementNode);
|
|
298
|
+
if (currentNode == null) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
prevMatchLengthToSkip = 0;
|
|
302
|
+
prevSibling = replacementNode;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const $reverseNodeTransform = node => {
|
|
306
|
+
const text = node.getTextContent();
|
|
307
|
+
const match = getMatch(text);
|
|
308
|
+
if (match === null || match.start !== 0) {
|
|
309
|
+
$replaceWithSimpleText(node);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (text.length > match.end) {
|
|
313
|
+
// This will split out the rest of the text as simple text
|
|
314
|
+
node.splitText(match.end);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const prevSibling = node.getPreviousSibling();
|
|
318
|
+
if ($isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
319
|
+
$replaceWithSimpleText(prevSibling);
|
|
320
|
+
$replaceWithSimpleText(node);
|
|
321
|
+
}
|
|
322
|
+
const nextSibling = node.getNextSibling();
|
|
323
|
+
if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
|
324
|
+
$replaceWithSimpleText(nextSibling);
|
|
325
|
+
|
|
326
|
+
// This may have already been converted in the previous block
|
|
327
|
+
if (isTargetNode(node)) {
|
|
328
|
+
$replaceWithSimpleText(node);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const removePlainTextTransform = editor.registerNodeTransform(TextNode, $textNodeTransform);
|
|
333
|
+
const removeReverseNodeTransform = editor.registerNodeTransform(targetNode, $reverseNodeTransform);
|
|
334
|
+
return [removePlainTextTransform, removeReverseNodeTransform];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export { $canShowPlaceholder, $canShowPlaceholderCurry, $findTextIntersectionFromCharacters, $isRootTextContentEmpty, $isRootTextContentEmptyCurry, $rootTextContent, registerLexicalTextEntity };
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict'
|
|
10
|
+
const EkzLexicalText = process.env.NODE_ENV !== 'production' ? require('./EkzLexicalText.dev.js') : require('./EkzLexicalText.prod.js');
|
|
11
|
+
module.exports = EkzLexicalText;
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as modDev from './EkzLexicalText.dev.mjs';
|
|
10
|
+
import * as modProd from './EkzLexicalText.prod.mjs';
|
|
11
|
+
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
|
|
12
|
+
export const $canShowPlaceholder = mod.$canShowPlaceholder;
|
|
13
|
+
export const $canShowPlaceholderCurry = mod.$canShowPlaceholderCurry;
|
|
14
|
+
export const $findTextIntersectionFromCharacters = mod.$findTextIntersectionFromCharacters;
|
|
15
|
+
export const $isRootTextContentEmpty = mod.$isRootTextContentEmpty;
|
|
16
|
+
export const $isRootTextContentEmptyCurry = mod.$isRootTextContentEmptyCurry;
|
|
17
|
+
export const $rootTextContent = mod.$rootTextContent;
|
|
18
|
+
export const registerLexicalTextEntity = mod.registerLexicalTextEntity;
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
const mod = await (process.env.NODE_ENV !== 'production' ? import('./EkzLexicalText.dev.mjs') : import('./EkzLexicalText.prod.mjs'));
|
|
10
|
+
export const $canShowPlaceholder = mod.$canShowPlaceholder;
|
|
11
|
+
export const $canShowPlaceholderCurry = mod.$canShowPlaceholderCurry;
|
|
12
|
+
export const $findTextIntersectionFromCharacters = mod.$findTextIntersectionFromCharacters;
|
|
13
|
+
export const $isRootTextContentEmpty = mod.$isRootTextContentEmpty;
|
|
14
|
+
export const $isRootTextContentEmptyCurry = mod.$isRootTextContentEmptyCurry;
|
|
15
|
+
export const $rootTextContent = mod.$rootTextContent;
|
|
16
|
+
export const registerLexicalTextEntity = mod.registerLexicalTextEntity;
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";var t=require("@ekz/lexical");function e(){return t.$getRoot().getTextContent()}function n(t,n=!0){if(t)return!1;let r=e();return n&&(r=r.trim()),""===r}function r(e){if(!n(e,!1))return!1;const r=t.$getRoot().getChildren(),o=r.length;if(o>1)return!1;for(let e=0;e<o;e++){const n=r[e];if(t.$isDecoratorNode(n))return!1;if(t.$isElementNode(n)){if(!t.$isParagraphNode(n))return!1;if(0!==n.__indent)return!1;const r=n.getChildren(),o=r.length;for(let n=0;n<o;n++){const n=r[e];if(!t.$isTextNode(n))return!1}}}return!0}function o(t,...e){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",t);for(const t of e)r.append("v",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}exports.$canShowPlaceholder=r,exports.$canShowPlaceholderCurry=function(t){return()=>r(t)},exports.$findTextIntersectionFromCharacters=function(e,n){let r=e.getFirstChild(),o=0;t:for(;null!==r;){if(t.$isElementNode(r)){const t=r.getFirstChild();if(null!==t){r=t;continue}}else if(t.$isTextNode(r)){const t=r.getTextContentSize();if(o+t>n)return{node:r,offset:n-o};o+=t}const e=r.getNextSibling();if(null!==e){r=e;continue}let i=r.getParent();for(;null!==i;){const t=i.getNextSibling();if(null!==t){r=t;continue t}i=i.getParent()}break}return null},exports.$isRootTextContentEmpty=n,exports.$isRootTextContentEmptyCurry=function(t,e){return()=>n(t,e)},exports.$rootTextContent=e,exports.registerLexicalTextEntity=function(e,n,r,i){const s=t=>t instanceof r,l=e=>{const n=t.$createTextNode(e.getTextContent());n.setFormat(e.getFormat()),e.replace(n)};return[e.registerNodeTransform(t.TextNode,e=>{if(!e.isSimpleText())return;let r,u=e.getPreviousSibling(),c=e.getTextContent(),f=e;if(t.$isTextNode(u)){const t=u.getTextContent(),r=n(t+c);if(s(u)){if(null===r||0!==(t=>t.getLatest().__mode)(u))return void l(u);{const n=r.end-t.length;if(n>0){const r=t+c.slice(0,n);if(u.select(),u.setTextContent(r),n===c.length)e.remove();else{const t=c.slice(n);e.setTextContent(t)}return}}}else if(null===r||r.start<t.length)return}let d=0;for(;;){r=n(c);let e,a=null===r?"":c.slice(r.end);if(c=a,""===a){const e=f.getNextSibling();if(t.$isTextNode(e)){a=f.getTextContent()+e.getTextContent();const t=n(a);if(null===t)return void(s(e)?l(e):e.markDirty());if(0!==t.start)return}}if(null===r)return;if(0===r.start&&t.$isTextNode(u)&&u.isTextEntity()){d+=r.end;continue}0===r.start?[e,f]=f.splitText(r.end):[,e,f]=f.splitText(r.start+d,r.end+d),void 0===e&&o(165,"nodeToReplace");const x=i(e);if(x.setFormat(e.getFormat()),e.replace(x),null==f)return;d=0,u=x}}),e.registerNodeTransform(r,e=>{const r=e.getTextContent(),o=n(r);if(null===o||0!==o.start)return void l(e);if(r.length>o.end)return void e.splitText(o.end);const i=e.getPreviousSibling();t.$isTextNode(i)&&i.isTextEntity()&&(l(i),l(e));const u=e.getNextSibling();t.$isTextNode(u)&&u.isTextEntity()&&(l(u),s(e)&&l(e))})]};
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
import{$getRoot as t,$isDecoratorNode as e,$isElementNode as n,$isParagraphNode as r,$isTextNode as i,TextNode as o,$createTextNode as l}from"@ekz/lexical";function s(){return t().getTextContent()}function f(t,e=!0){if(t)return!1;let n=s();return e&&(n=n.trim()),""===n}function u(t,e){return()=>f(t,e)}function c(o){if(!f(o,!1))return!1;const l=t().getChildren(),s=l.length;if(s>1)return!1;for(let t=0;t<s;t++){const o=l[t];if(e(o))return!1;if(n(o)){if(!r(o))return!1;if(0!==o.__indent)return!1;const e=o.getChildren(),n=e.length;for(let r=0;r<n;r++){const n=e[t];if(!i(n))return!1}}}return!0}function g(t){return()=>c(t)}function a(t,e){let r=t.getFirstChild(),o=0;t:for(;null!==r;){if(n(r)){const t=r.getFirstChild();if(null!==t){r=t;continue}}else if(i(r)){const t=r.getTextContentSize();if(o+t>e)return{node:r,offset:e-o};o+=t}const t=r.getNextSibling();if(null!==t){r=t;continue}let l=r.getParent();for(;null!==l;){const t=l.getNextSibling();if(null!==t){r=t;continue t}l=l.getParent()}break}return null}function d(t,...e){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",t);for(const t of e)r.append("v",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function x(t,e,n,r){const s=t=>t instanceof n,f=t=>{const e=l(t.getTextContent());e.setFormat(t.getFormat()),t.replace(e)};return[t.registerNodeTransform(o,t=>{if(!t.isSimpleText())return;let n,o=t.getPreviousSibling(),l=t.getTextContent(),u=t;if(i(o)){const n=o.getTextContent(),r=e(n+l);if(s(o)){if(null===r||0!==(t=>t.getLatest().__mode)(o))return void f(o);{const e=r.end-n.length;if(e>0){const r=n+l.slice(0,e);if(o.select(),o.setTextContent(r),e===l.length)t.remove();else{const n=l.slice(e);t.setTextContent(n)}return}}}else if(null===r||r.start<n.length)return}let c=0;for(;;){n=e(l);let t,g=null===n?"":l.slice(n.end);if(l=g,""===g){const t=u.getNextSibling();if(i(t)){g=u.getTextContent()+t.getTextContent();const n=e(g);if(null===n)return void(s(t)?f(t):t.markDirty());if(0!==n.start)return}}if(null===n)return;if(0===n.start&&i(o)&&o.isTextEntity()){c+=n.end;continue}0===n.start?[t,u]=u.splitText(n.end):[,t,u]=u.splitText(n.start+c,n.end+c),void 0===t&&d(165,"nodeToReplace");const a=r(t);if(a.setFormat(t.getFormat()),t.replace(a),null==u)return;c=0,o=a}}),t.registerNodeTransform(n,t=>{const n=t.getTextContent(),r=e(n);if(null===r||0!==r.start)return void f(t);if(n.length>r.end)return void t.splitText(r.end);const o=t.getPreviousSibling();i(o)&&o.isTextEntity()&&(f(o),f(t));const l=t.getNextSibling();i(l)&&l.isTextEntity()&&(f(l),s(t)&&f(t))})]}export{c as $canShowPlaceholder,g as $canShowPlaceholderCurry,a as $findTextIntersectionFromCharacters,f as $isRootTextContentEmpty,u as $isRootTextContentEmptyCurry,s as $rootTextContent,x as registerLexicalTextEntity};
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
* @flow strict
|
|
8
|
+
*/
|
|
9
|
+
import type {ElementNode, LexicalEditor, RootNode, TextNode} from '@ekz/lexical';
|
|
10
|
+
export type TextNodeWithOffset = {
|
|
11
|
+
node: TextNode,
|
|
12
|
+
offset: number,
|
|
13
|
+
};
|
|
14
|
+
declare export function $findTextIntersectionFromCharacters(
|
|
15
|
+
root: RootNode,
|
|
16
|
+
targetCharacters: number,
|
|
17
|
+
): null | {
|
|
18
|
+
node: TextNode,
|
|
19
|
+
offset: number,
|
|
20
|
+
};
|
|
21
|
+
declare export function $isRootTextContentEmpty(
|
|
22
|
+
isEditorComposing: boolean,
|
|
23
|
+
trim?: boolean,
|
|
24
|
+
): boolean;
|
|
25
|
+
declare export function $isRootTextContentEmptyCurry(
|
|
26
|
+
isEditorComposing: boolean,
|
|
27
|
+
trim?: boolean,
|
|
28
|
+
): () => boolean;
|
|
29
|
+
declare export function $rootTextContent(): string;
|
|
30
|
+
declare export function $canShowPlaceholder(isComposing: boolean): boolean;
|
|
31
|
+
declare export function $canShowPlaceholderCurry(
|
|
32
|
+
isEditorComposing: boolean,
|
|
33
|
+
): () => boolean;
|
|
34
|
+
export type EntityMatch = {
|
|
35
|
+
end: number,
|
|
36
|
+
start: number,
|
|
37
|
+
};
|
|
38
|
+
declare export function registerLexicalTextEntity<N: TextNode>(
|
|
39
|
+
editor: LexicalEditor,
|
|
40
|
+
getMatch: (text: string) => null | EntityMatch,
|
|
41
|
+
targetNode: Class<N>,
|
|
42
|
+
createNode: (textNode: TextNode) => N,
|
|
43
|
+
): Array<() => void>;
|
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if the input should show the placeholder. If anything is in
|
|
3
|
+
* in the root the placeholder should not be shown.
|
|
4
|
+
* @param isComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
5
|
+
* @returns true if the input should show the placeholder, false otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export declare function $canShowPlaceholder(isComposing: boolean): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a function that executes {@link $canShowPlaceholder}
|
|
10
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
11
|
+
* @returns A function that executes $canShowPlaceholder with arguments.
|
|
12
|
+
*/
|
|
13
|
+
export declare function $canShowPlaceholderCurry(isEditorComposing: boolean): () => boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
*/
|
|
8
|
+
import { RootNode, TextNode } from '@ekz/lexical';
|
|
9
|
+
/**
|
|
10
|
+
* Finds a TextNode with a size larger than targetCharacters and returns
|
|
11
|
+
* the node along with the remaining length of the text.
|
|
12
|
+
* @param root - The RootNode.
|
|
13
|
+
* @param targetCharacters - The number of characters whose TextNode must be larger than.
|
|
14
|
+
* @returns The TextNode and the intersections offset, or null if no TextNode is found.
|
|
15
|
+
*/
|
|
16
|
+
export declare function $findTextIntersectionFromCharacters(root: RootNode, targetCharacters: number): null | {
|
|
17
|
+
node: TextNode;
|
|
18
|
+
offset: number;
|
|
19
|
+
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
*/
|
|
8
|
+
import { TextNode } from '@ekz/lexical';
|
|
9
|
+
export type TextNodeWithOffset = {
|
|
10
|
+
node: TextNode;
|
|
11
|
+
offset: number;
|
|
12
|
+
};
|
|
13
|
+
export { $canShowPlaceholder, $canShowPlaceholderCurry, } from './canShowPlaceholder';
|
|
14
|
+
export { $findTextIntersectionFromCharacters } from './findTextIntersectionFromCharacters';
|
|
15
|
+
export { $isRootTextContentEmpty, $isRootTextContentEmptyCurry, } from './isRootTextContentEmpty';
|
|
16
|
+
export type { EntityMatch } from './registerLexicalTextEntity';
|
|
17
|
+
export { registerLexicalTextEntity } from './registerLexicalTextEntity';
|
|
18
|
+
export { $rootTextContent } from './rootTextContent';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if the root has any text content and can trim any whitespace if it does.
|
|
3
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
4
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
5
|
+
* @returns true if text content is empty, false if there is text or isEditorComposing is true.
|
|
6
|
+
*/
|
|
7
|
+
export declare function $isRootTextContentEmpty(isEditorComposing: boolean, trim?: boolean): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a function that executes {@link $isRootTextContentEmpty}
|
|
10
|
+
* @param isEditorComposing - Is the editor in composition mode due to an active Input Method Editor?
|
|
11
|
+
* @param trim - Should the root text have its whitespaced trimmed? Defaults to true.
|
|
12
|
+
* @returns A function that executes $isRootTextContentEmpty based on arguments.
|
|
13
|
+
*/
|
|
14
|
+
export declare function $isRootTextContentEmptyCurry(isEditorComposing: boolean, trim?: boolean): () => boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekz/lexical-text",
|
|
3
|
+
"description": "This package contains utilities and helpers for handling Lexical text.",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"lexical",
|
|
6
|
+
"editor",
|
|
7
|
+
"rich-text",
|
|
8
|
+
"list",
|
|
9
|
+
"text"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"version": "0.40.0",
|
|
13
|
+
"main": "LexicalText.js",
|
|
14
|
+
"types": "index.d.ts",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/facebook/lexical.git",
|
|
18
|
+
"directory": "packages/lexical-text"
|
|
19
|
+
},
|
|
20
|
+
"module": "LexicalText.mjs",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./index.d.ts",
|
|
26
|
+
"development": "./LexicalText.dev.mjs",
|
|
27
|
+
"production": "./LexicalText.prod.mjs",
|
|
28
|
+
"node": "./LexicalText.node.mjs",
|
|
29
|
+
"default": "./LexicalText.mjs"
|
|
30
|
+
},
|
|
31
|
+
"require": {
|
|
32
|
+
"types": "./index.d.ts",
|
|
33
|
+
"development": "./LexicalText.dev.js",
|
|
34
|
+
"production": "./LexicalText.prod.js",
|
|
35
|
+
"default": "./LexicalText.js"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ekz/lexical": "0.40.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
*/
|
|
8
|
+
import { Klass, LexicalEditor, TextNode } from '@ekz/lexical';
|
|
9
|
+
export type EntityMatch = {
|
|
10
|
+
end: number;
|
|
11
|
+
start: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Returns a tuple that can be rested (...) into mergeRegister to clean up
|
|
15
|
+
* node transforms listeners that transforms text into another node, eg. a HashtagNode.
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* useEffect(() => {
|
|
19
|
+
return mergeRegister(
|
|
20
|
+
...registerLexicalTextEntity(editor, getMatch, targetNode, createNode),
|
|
21
|
+
);
|
|
22
|
+
}, [createNode, editor, getMatch, targetNode]);
|
|
23
|
+
* ```
|
|
24
|
+
* Where targetNode is the type of node containing the text you want to transform (like a text input),
|
|
25
|
+
* then getMatch uses a regex to find a matching text and creates the proper node to include the matching text.
|
|
26
|
+
* @param editor - The lexical editor.
|
|
27
|
+
* @param getMatch - Finds a matching string that satisfies a regex expression.
|
|
28
|
+
* @param targetNode - The node type that contains text to match with. eg. HashtagNode
|
|
29
|
+
* @param createNode - A function that creates a new node to contain the matched text. eg createHashtagNode
|
|
30
|
+
* @returns An array containing the plain text and reverse node transform listeners.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerLexicalTextEntity<T extends TextNode>(editor: LexicalEditor, getMatch: (text: string) => null | EntityMatch, targetNode: Klass<T>, createNode: (textNode: TextNode) => T): Array<() => void>;
|