@lexical/react 0.1.7 → 0.1.8
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_useLexical.dev.js +84 -0
- package/DEPRECATED_useLexical.js +9 -2
- package/DEPRECATED_useLexical.prod.js +8 -0
- package/DEPRECATED_useLexicalAutoFormatter.dev.js +642 -0
- package/DEPRECATED_useLexicalAutoFormatter.js +9 -11
- package/DEPRECATED_useLexicalAutoFormatter.prod.js +22 -0
- package/DEPRECATED_useLexicalCanShowPlaceholder.dev.js +136 -0
- package/DEPRECATED_useLexicalCanShowPlaceholder.js +9 -3
- package/DEPRECATED_useLexicalCanShowPlaceholder.prod.js +8 -0
- package/DEPRECATED_useLexicalCharacterLimit.dev.js +294 -0
- package/DEPRECATED_useLexicalCharacterLimit.js +9 -8
- package/DEPRECATED_useLexicalCharacterLimit.prod.js +14 -0
- package/DEPRECATED_useLexicalDecorators.dev.js +82 -0
- package/DEPRECATED_useLexicalDecorators.js +9 -2
- package/DEPRECATED_useLexicalDecorators.prod.js +8 -0
- package/DEPRECATED_useLexicalEditor.dev.js +52 -0
- package/DEPRECATED_useLexicalEditor.js +9 -1
- package/DEPRECATED_useLexicalEditor.prod.js +7 -0
- package/DEPRECATED_useLexicalEditorEvents.dev.js +96 -0
- package/DEPRECATED_useLexicalEditorEvents.js +9 -2
- package/DEPRECATED_useLexicalEditorEvents.prod.js +8 -0
- package/DEPRECATED_useLexicalHistory.dev.js +339 -0
- package/DEPRECATED_useLexicalHistory.js +9 -7
- package/DEPRECATED_useLexicalHistory.prod.js +13 -0
- package/DEPRECATED_useLexicalList.dev.js +64 -0
- package/DEPRECATED_useLexicalList.js +9 -1
- package/DEPRECATED_useLexicalList.prod.js +7 -0
- package/DEPRECATED_useLexicalPlainText.dev.js +755 -0
- package/DEPRECATED_useLexicalPlainText.js +9 -16
- package/DEPRECATED_useLexicalPlainText.prod.js +22 -0
- package/DEPRECATED_useLexicalRichText.dev.js +1326 -0
- package/DEPRECATED_useLexicalRichText.js +9 -30
- package/DEPRECATED_useLexicalRichText.prod.js +35 -0
- package/LexicalAutoFormatterPlugin.dev.js +645 -0
- package/LexicalAutoFormatterPlugin.js +9 -12
- package/LexicalAutoFormatterPlugin.prod.js +23 -0
- package/LexicalAutoLinkPlugin.dev.js +227 -0
- package/LexicalAutoLinkPlugin.js +9 -6
- package/LexicalAutoLinkPlugin.prod.js +12 -0
- package/LexicalBootstrapPlugin.dev.js +124 -0
- package/LexicalBootstrapPlugin.js +9 -0
- package/LexicalBootstrapPlugin.prod.js +8 -0
- package/LexicalCharacterLimitPlugin.dev.js +352 -0
- package/LexicalCharacterLimitPlugin.js +9 -8
- package/LexicalCharacterLimitPlugin.prod.js +14 -0
- package/LexicalCollaborationPlugin.dev.js +235 -0
- package/LexicalCollaborationPlugin.js +9 -8
- package/LexicalCollaborationPlugin.prod.js +14 -0
- package/LexicalComposer.dev.js +76 -0
- package/LexicalComposer.js +9 -3
- package/LexicalComposer.prod.js +9 -0
- package/LexicalComposerContext.dev.js +53 -0
- package/LexicalComposerContext.js +9 -1
- package/LexicalComposerContext.prod.js +7 -0
- package/LexicalContentEditable.dev.js +71 -0
- package/LexicalContentEditable.js +9 -3
- package/LexicalContentEditable.prod.js +9 -0
- package/LexicalHashtagPlugin.dev.js +152 -0
- package/LexicalHashtagPlugin.js +9 -4
- package/LexicalHashtagPlugin.prod.js +10 -0
- package/LexicalHistoryPlugin.dev.js +344 -0
- package/LexicalHistoryPlugin.js +9 -7
- package/LexicalHistoryPlugin.prod.js +13 -0
- package/LexicalHorizontalRulePlugin.dev.js +51 -0
- package/LexicalHorizontalRulePlugin.js +9 -1
- package/LexicalHorizontalRulePlugin.prod.js +7 -0
- package/LexicalLinkPlugin.dev.js +137 -0
- package/LexicalLinkPlugin.js +9 -3
- package/LexicalLinkPlugin.prod.js +9 -0
- package/LexicalListPlugin.dev.js +67 -0
- package/LexicalListPlugin.js +9 -2
- package/LexicalListPlugin.prod.js +8 -0
- package/LexicalNestedComposer.dev.js +60 -0
- package/LexicalNestedComposer.js +9 -2
- package/LexicalNestedComposer.prod.js +8 -0
- package/LexicalOnChangePlugin.dev.js +57 -0
- package/LexicalOnChangePlugin.js +9 -1
- package/LexicalOnChangePlugin.prod.js +7 -0
- package/LexicalPlainTextPlugin.dev.js +565 -0
- package/LexicalPlainTextPlugin.js +9 -13
- package/LexicalPlainTextPlugin.prod.js +17 -0
- package/LexicalRichTextPlugin.dev.js +1136 -0
- package/LexicalRichTextPlugin.js +9 -27
- package/LexicalRichTextPlugin.prod.js +31 -0
- package/LexicalTablePlugin.dev.js +95 -0
- package/LexicalTablePlugin.js +9 -3
- package/LexicalTablePlugin.prod.js +9 -0
- package/LexicalTreeView.dev.js +340 -0
- package/LexicalTreeView.js +9 -13
- package/LexicalTreeView.prod.js +19 -0
- package/README.md +1 -0
- package/package.json +4 -4
- package/useLexicalDecoratorMap.dev.js +83 -0
- package/useLexicalDecoratorMap.js +9 -2
- package/useLexicalDecoratorMap.prod.js +8 -0
- package/useLexicalIsTextContentEmpty.dev.js +84 -0
- package/useLexicalIsTextContentEmpty.js +9 -2
- package/useLexicalIsTextContentEmpty.prod.js +8 -0
- package/withSubscriptions.dev.js +23 -0
- package/withSubscriptions.js +9 -1
- package/withSubscriptions.prod.js +7 -0
|
@@ -0,0 +1,352 @@
|
|
|
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 React = require('react');
|
|
11
|
+
var lexical = require('lexical');
|
|
12
|
+
var withSubscriptions = require('@lexical/react/withSubscriptions');
|
|
13
|
+
var OverflowNode = require('lexical/OverflowNode');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
17
|
+
*
|
|
18
|
+
* This source code is licensed under the MIT license found in the
|
|
19
|
+
* LICENSE file in the root directory of this source tree.
|
|
20
|
+
*
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
function $dfs__DEPRECATED(startingNode, nextNode) {
|
|
24
|
+
let node = startingNode;
|
|
25
|
+
nextNode(node);
|
|
26
|
+
|
|
27
|
+
while (node !== null) {
|
|
28
|
+
if (lexical.$isElementNode(node) && node.getChildrenSize() > 0) {
|
|
29
|
+
node = node.getFirstChild();
|
|
30
|
+
} else {
|
|
31
|
+
// Find immediate sibling or nearest parent sibling
|
|
32
|
+
let sibling = null;
|
|
33
|
+
|
|
34
|
+
while (sibling === null && node !== null) {
|
|
35
|
+
sibling = node.getNextSibling();
|
|
36
|
+
|
|
37
|
+
if (sibling === null) {
|
|
38
|
+
node = node.getParent();
|
|
39
|
+
} else {
|
|
40
|
+
node = sibling;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (node !== null) {
|
|
46
|
+
node = nextNode(node);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
53
|
+
*
|
|
54
|
+
* This source code is licensed under the MIT license found in the
|
|
55
|
+
* LICENSE file in the root directory of this source tree.
|
|
56
|
+
*
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
59
|
+
function $textContent() {
|
|
60
|
+
const root = lexical.$getRoot();
|
|
61
|
+
return root.getTextContent();
|
|
62
|
+
}
|
|
63
|
+
const $textContentCurry = $textContent;
|
|
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
|
+
function useCharacterLimit(editor, maxCharacters, optional = Object.freeze({})) {
|
|
74
|
+
const {
|
|
75
|
+
strlen = input => input.length,
|
|
76
|
+
// UTF-16
|
|
77
|
+
remainingCharacters = characters => {}
|
|
78
|
+
} = optional;
|
|
79
|
+
React.useEffect(() => {
|
|
80
|
+
if (!editor.hasNodes([OverflowNode.OverflowNode])) {
|
|
81
|
+
{
|
|
82
|
+
throw Error(`useCharacterLimit: OverflowNode not registered on editor`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, [editor]);
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
let text = editor.getEditorState().read($textContentCurry);
|
|
88
|
+
let lastComputedTextLength = 0;
|
|
89
|
+
return withSubscriptions(editor.addListener('textcontent', currentText => {
|
|
90
|
+
text = currentText;
|
|
91
|
+
}), editor.addListener('update', ({
|
|
92
|
+
dirtyLeaves
|
|
93
|
+
}) => {
|
|
94
|
+
const isComposing = editor.isComposing();
|
|
95
|
+
const hasDirtyLeaves = dirtyLeaves.size > 0;
|
|
96
|
+
|
|
97
|
+
if (isComposing || !hasDirtyLeaves) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const textLength = strlen(text);
|
|
102
|
+
const textLengthAboveThreshold = textLength > maxCharacters || lastComputedTextLength !== null && lastComputedTextLength > maxCharacters;
|
|
103
|
+
const diff = maxCharacters - textLength;
|
|
104
|
+
remainingCharacters(diff);
|
|
105
|
+
|
|
106
|
+
if (lastComputedTextLength === null || textLengthAboveThreshold) {
|
|
107
|
+
const offset = findOffset(text, maxCharacters, strlen);
|
|
108
|
+
editor.update(() => {
|
|
109
|
+
lexical.$log('CharacterLimit');
|
|
110
|
+
$wrapOverflowedNodes(offset);
|
|
111
|
+
}, {
|
|
112
|
+
tag: 'without-history'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lastComputedTextLength = textLength;
|
|
117
|
+
}));
|
|
118
|
+
}, [editor, maxCharacters, remainingCharacters, strlen]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function findOffset(text, maxCharacters, strlen) {
|
|
122
|
+
const Segmenter = Intl.Segmenter;
|
|
123
|
+
let offsetUtf16 = 0;
|
|
124
|
+
let offset = 0;
|
|
125
|
+
|
|
126
|
+
if (typeof Segmenter === 'function') {
|
|
127
|
+
const segmenter = new Segmenter();
|
|
128
|
+
const graphemes = segmenter.segment(text); // eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
|
129
|
+
|
|
130
|
+
for (const {
|
|
131
|
+
segment: grapheme
|
|
132
|
+
} of graphemes) {
|
|
133
|
+
const nextOffset = offset + strlen(grapheme);
|
|
134
|
+
|
|
135
|
+
if (nextOffset > maxCharacters) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
offset = nextOffset;
|
|
140
|
+
offsetUtf16 += grapheme.length;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
const codepoints = Array.from(text);
|
|
144
|
+
const codepointsLength = codepoints.length;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < codepointsLength; i++) {
|
|
147
|
+
const codepoint = codepoints[i];
|
|
148
|
+
const nextOffset = offset + strlen(codepoint);
|
|
149
|
+
|
|
150
|
+
if (nextOffset > maxCharacters) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
offset = nextOffset;
|
|
155
|
+
offsetUtf16 += codepoint.length;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return offsetUtf16;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function $wrapOverflowedNodes(offset) {
|
|
163
|
+
const root = lexical.$getRoot();
|
|
164
|
+
let accumulatedLength = 0;
|
|
165
|
+
let previousNode = root;
|
|
166
|
+
$dfs__DEPRECATED(root, node => {
|
|
167
|
+
if (OverflowNode.$isOverflowNode(node)) {
|
|
168
|
+
const previousLength = accumulatedLength;
|
|
169
|
+
const nextLength = accumulatedLength + node.getTextContentSize();
|
|
170
|
+
|
|
171
|
+
if (nextLength <= offset) {
|
|
172
|
+
const parent = node.getParent();
|
|
173
|
+
const previousSibling = node.getPreviousSibling();
|
|
174
|
+
const nextSibling = node.getNextSibling();
|
|
175
|
+
$unwrapNode(node);
|
|
176
|
+
const selection = lexical.$getSelection(); // Restore selection when the overflow children are removed
|
|
177
|
+
|
|
178
|
+
if (selection !== null && (!selection.anchor.getNode().isAttached() || !selection.focus.getNode().isAttached())) {
|
|
179
|
+
if (lexical.$isTextNode(previousSibling)) {
|
|
180
|
+
previousSibling.select();
|
|
181
|
+
} else if (lexical.$isTextNode(nextSibling)) {
|
|
182
|
+
nextSibling.select();
|
|
183
|
+
} else if (parent !== null) {
|
|
184
|
+
parent.select();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return previousNode;
|
|
189
|
+
} else if (previousLength < offset) {
|
|
190
|
+
const descendant = node.getFirstDescendant();
|
|
191
|
+
const descendantLength = descendant !== null ? descendant.getTextContentSize() : 0;
|
|
192
|
+
const previousPlusDescendantLength = previousLength + descendantLength; // For simple text we can redimension the overflow into a smaller and more accurate
|
|
193
|
+
// container
|
|
194
|
+
|
|
195
|
+
const firstDescendantIsSimpleText = lexical.$isTextNode(descendant) && descendant.isSimpleText();
|
|
196
|
+
const firstDescendantDoesNotOverflow = previousPlusDescendantLength <= offset;
|
|
197
|
+
|
|
198
|
+
if (firstDescendantIsSimpleText || firstDescendantDoesNotOverflow) {
|
|
199
|
+
$unwrapNode(node);
|
|
200
|
+
return previousNode;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else if (lexical.$isLeafNode(node)) {
|
|
204
|
+
const previousAccumulatedLength = accumulatedLength;
|
|
205
|
+
accumulatedLength += node.getTextContentSize();
|
|
206
|
+
|
|
207
|
+
if (accumulatedLength > offset && !OverflowNode.$isOverflowNode(node.getParent())) {
|
|
208
|
+
const previousSelection = lexical.$getSelection();
|
|
209
|
+
let overflowNode; // For simple text we can improve the limit accuracy by splitting the TextNode
|
|
210
|
+
// on the split point
|
|
211
|
+
|
|
212
|
+
if (previousAccumulatedLength < offset && lexical.$isTextNode(node) && node.isSimpleText()) {
|
|
213
|
+
const [, overflowedText] = node.splitText(offset - previousAccumulatedLength);
|
|
214
|
+
overflowNode = $wrapNode(overflowedText);
|
|
215
|
+
} else {
|
|
216
|
+
overflowNode = $wrapNode(node);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (previousSelection !== null) {
|
|
220
|
+
lexical.$setSelection(previousSelection);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
mergePrevious(overflowNode);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
previousNode = node;
|
|
228
|
+
return node;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function $wrapNode(node) {
|
|
233
|
+
const overflowNode = OverflowNode.$createOverflowNode();
|
|
234
|
+
node.insertBefore(overflowNode);
|
|
235
|
+
overflowNode.append(node);
|
|
236
|
+
return overflowNode;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function $unwrapNode(node) {
|
|
240
|
+
const children = node.getChildren();
|
|
241
|
+
const childrenLength = children.length;
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
244
|
+
node.insertBefore(children[i]);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
node.remove();
|
|
248
|
+
return childrenLength > 0 ? children[childrenLength - 1] : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function mergePrevious(overflowNode) {
|
|
252
|
+
const previousNode = overflowNode.getPreviousSibling();
|
|
253
|
+
|
|
254
|
+
if (!OverflowNode.$isOverflowNode(previousNode)) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const firstChild = overflowNode.getFirstChild();
|
|
259
|
+
const previousNodeChildren = previousNode.getChildren();
|
|
260
|
+
const previousNodeChildrenLength = previousNodeChildren.length;
|
|
261
|
+
|
|
262
|
+
if (firstChild === null) {
|
|
263
|
+
overflowNode.append(...previousNodeChildren);
|
|
264
|
+
} else {
|
|
265
|
+
for (let i = 0; i < previousNodeChildrenLength; i++) {
|
|
266
|
+
firstChild.insertBefore(previousNodeChildren[i]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const selection = lexical.$getSelection();
|
|
271
|
+
|
|
272
|
+
if (selection !== null) {
|
|
273
|
+
const anchor = selection.anchor;
|
|
274
|
+
const anchorNode = anchor.getNode();
|
|
275
|
+
const focus = selection.focus;
|
|
276
|
+
const focusNode = anchor.getNode();
|
|
277
|
+
|
|
278
|
+
if (anchorNode.is(previousNode)) {
|
|
279
|
+
anchor.set(overflowNode.getKey(), anchor.offset, 'element');
|
|
280
|
+
} else if (anchorNode.is(overflowNode)) {
|
|
281
|
+
anchor.set(overflowNode.getKey(), previousNodeChildrenLength + anchor.offset, 'element');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (focusNode.is(previousNode)) {
|
|
285
|
+
focus.set(overflowNode.getKey(), focus.offset, 'element');
|
|
286
|
+
} else if (focusNode.is(overflowNode)) {
|
|
287
|
+
focus.set(overflowNode.getKey(), previousNodeChildrenLength + focus.offset, 'element');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
previousNode.remove();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
296
|
+
*
|
|
297
|
+
* This source code is licensed under the MIT license found in the
|
|
298
|
+
* LICENSE file in the root directory of this source tree.
|
|
299
|
+
*
|
|
300
|
+
*
|
|
301
|
+
*/
|
|
302
|
+
const CHARACTER_LIMIT = 5;
|
|
303
|
+
let textEncoderInstance = null;
|
|
304
|
+
|
|
305
|
+
function textEncoder() {
|
|
306
|
+
if (window.TextEncoder === undefined) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (textEncoderInstance === null) {
|
|
311
|
+
textEncoderInstance = new window.TextEncoder();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return textEncoderInstance;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function utf8Length(text) {
|
|
318
|
+
const currentTextEncoder = textEncoder();
|
|
319
|
+
|
|
320
|
+
if (currentTextEncoder === null) {
|
|
321
|
+
// http://stackoverflow.com/a/5515960/210370
|
|
322
|
+
const m = encodeURIComponent(text).match(/%[89ABab]/g);
|
|
323
|
+
return text.length + (m ? m.length : 0);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return currentTextEncoder.encode(text).length;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function CharacterLimitPlugin({
|
|
330
|
+
charset = 'UTF-16'
|
|
331
|
+
}) {
|
|
332
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
333
|
+
const [remainingCharacters, setRemainingCharacters] = React.useState(0);
|
|
334
|
+
const characterLimitProps = React.useMemo(() => ({
|
|
335
|
+
remainingCharacters: setRemainingCharacters,
|
|
336
|
+
strlen: text => {
|
|
337
|
+
if (charset === 'UTF-8') {
|
|
338
|
+
return utf8Length(text);
|
|
339
|
+
} else if (charset === 'UTF-16') {
|
|
340
|
+
return text.length;
|
|
341
|
+
} else {
|
|
342
|
+
throw new Error('Unrecognized charset');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}), [charset]);
|
|
346
|
+
useCharacterLimit(editor, CHARACTER_LIMIT, characterLimitProps);
|
|
347
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
348
|
+
className: `characters-limit ${remainingCharacters < 0 ? 'characters-limit-exceeded' : ''}`
|
|
349
|
+
}, remainingCharacters);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = CharacterLimitPlugin;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
const LexicalCharacterLimitPlugin = process.env.NODE_ENV === 'development' ? require('./LexicalCharacterLimitPlugin.dev.js') : require('./LexicalCharacterLimitPlugin.prod.js')
|
|
9
|
+
module.exports = LexicalCharacterLimitPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
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';var l=require("@lexical/react/LexicalComposerContext"),p=require("react"),q=require("lexical"),r=require("@lexical/react/withSubscriptions"),t=require("lexical/OverflowNode");function u(a,e){for(e(a);null!==a;){if(q.$isElementNode(a)&&0<a.getChildrenSize())a=a.getFirstChild();else{let f=null;for(;null===f&&null!==a;)f=a.getNextSibling(),a=null===f?a.getParent():f}null!==a&&(a=e(a))}}function w(){return q.$getRoot().getTextContent()}
|
|
8
|
+
function x(a,e,f=Object.freeze({})){const {strlen:h=c=>c.length,remainingCharacters:d=()=>{}}=f;p.useEffect(()=>{if(!a.hasNodes([t.OverflowNode]))throw Error("useCharacterLimit: OverflowNode not registered on editor");},[a]);p.useEffect(()=>{let c=a.getEditorState().read(w),b=0;return r(a.addListener("textcontent",g=>{c=g}),a.addListener("update",({dirtyLeaves:g})=>{var k=a.isComposing();g=0<g.size;if(!k&&g){k=h(c);g=k>e||null!==b&&b>e;d(e-k);if(null===b||g){const n=y(c,e,h);a.update(()=>{q.$log("CharacterLimit");
|
|
9
|
+
z(n)},{tag:"without-history"})}b=k}}))},[a,e,d,h])}function y(a,e,f){var h=Intl.Segmenter;let d=0;var c=0;if("function"===typeof h){a=(new h).segment(a);for(var {segment:b}of a){c+=f(b);if(c>e)break;d+=b.length}}else for(b=Array.from(a),a=b.length,h=0;h<a;h++){const g=b[h];c+=f(g);if(c>e)break;d+=g.length}return d}
|
|
10
|
+
function z(a){const e=q.$getRoot();let f=0,h=e;u(e,d=>{if(t.$isOverflowNode(d)){var c=f;if(f+d.getTextContentSize()<=a){var b=d.getParent();c=d.getPreviousSibling();var g=d.getNextSibling();A(d);d=q.$getSelection();null===d||d.anchor.getNode().isAttached()&&d.focus.getNode().isAttached()||(q.$isTextNode(c)?c.select():q.$isTextNode(g)?g.select():null!==b&&b.select());return h}if(c<a&&(b=d.getFirstDescendant(),g=null!==b?b.getTextContentSize():0,c+=g,b=q.$isTextNode(b)&&b.isSimpleText(),c=c<=a,b||c))return A(d),
|
|
11
|
+
h}else if(q.$isLeafNode(d)&&(c=f,f+=d.getTextContentSize(),f>a&&!t.$isOverflowNode(d.getParent())&&(b=q.$getSelection(),c<a&&q.$isTextNode(d)&&d.isSimpleText()?([,c]=d.splitText(a-c),c=B(c)):c=B(d),null!==b&&q.$setSelection(b),b=c,g=b.getPreviousSibling(),t.$isOverflowNode(g)))){var k=b.getFirstChild(),n=g.getChildren();c=n.length;if(null===k)b.append(...n);else for(var m=0;m<c;m++)k.insertBefore(n[m]);m=q.$getSelection();if(null!==m){k=m.anchor;n=k.getNode();m=m.focus;const v=k.getNode();n.is(g)?
|
|
12
|
+
k.set(b.getKey(),k.offset,"element"):n.is(b)&&k.set(b.getKey(),c+k.offset,"element");v.is(g)?m.set(b.getKey(),m.offset,"element"):v.is(b)&&m.set(b.getKey(),c+m.offset,"element")}g.remove()}return h=d})}function B(a){const e=t.$createOverflowNode();a.insertBefore(e);e.append(a);return e}function A(a){const e=a.getChildren(),f=e.length;for(let h=0;h<f;h++)a.insertBefore(e[h]);a.remove();return 0<f?e[f-1]:null}let C=null;
|
|
13
|
+
module.exports=function({charset:a="UTF-16"}){const [e]=l.useLexicalComposerContext(),[f,h]=p.useState(0),d=p.useMemo(()=>({remainingCharacters:h,strlen:c=>{if("UTF-8"===a){if(void 0===window.TextEncoder)var b=null;else null===C&&(C=new window.TextEncoder),b=C;null===b?(b=encodeURIComponent(c).match(/%[89ABab]/g),c=c.length+(b?b.length:0)):c=b.encode(c).length;return c}if("UTF-16"===a)return c.length;throw Error("Unrecognized charset");}}),[a]);x(e,5,d);return p.createElement("span",{className:`characters-limit ${0>
|
|
14
|
+
f?"characters-limit-exceeded":""}`},f)};
|
|
@@ -0,0 +1,235 @@
|
|
|
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 React = require('react');
|
|
11
|
+
var yjs = require('@lexical/yjs');
|
|
12
|
+
var reactDom = require('react-dom');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
16
|
+
*
|
|
17
|
+
* This source code is licensed under the MIT license found in the
|
|
18
|
+
* LICENSE file in the root directory of this source tree.
|
|
19
|
+
*
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
const EditorPriority = 0;
|
|
23
|
+
const BootstrapPriority = 1;
|
|
24
|
+
function useYjsCollaboration(editor, id, provider, docMap, name, color, shouldBootstrap) {
|
|
25
|
+
const binding = React.useMemo(() => yjs.createBinding(editor, provider, id, docMap), [editor, provider, id, docMap]);
|
|
26
|
+
const connect = React.useCallback(() => {
|
|
27
|
+
provider.connect();
|
|
28
|
+
}, [provider]);
|
|
29
|
+
const disconnect = React.useCallback(() => {
|
|
30
|
+
try {
|
|
31
|
+
provider.disconnect();
|
|
32
|
+
} catch (e) {// Do nothing
|
|
33
|
+
}
|
|
34
|
+
}, [provider]);
|
|
35
|
+
const isInitialized = React.useRef(false);
|
|
36
|
+
React.useLayoutEffect(() => {
|
|
37
|
+
return editor.addListener('command', type => {
|
|
38
|
+
if (type === 'bootstrapEditor') {
|
|
39
|
+
return !isInitialized.current;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
}, BootstrapPriority);
|
|
44
|
+
}, [editor]);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
const {
|
|
47
|
+
root
|
|
48
|
+
} = binding;
|
|
49
|
+
const {
|
|
50
|
+
awareness
|
|
51
|
+
} = provider;
|
|
52
|
+
|
|
53
|
+
const onStatus = ({
|
|
54
|
+
status
|
|
55
|
+
}) => {
|
|
56
|
+
editor.execCommand('connected', status === 'connected');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onSync = isSynced => {
|
|
60
|
+
if (shouldBootstrap && isSynced && root.isEmpty() && root._xmlText._length === 0) {
|
|
61
|
+
isInitialized.current = true;
|
|
62
|
+
editor.execCommand('bootstrapEditor');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const onAwarenessUpdate = () => {
|
|
67
|
+
yjs.syncCursorPositions(binding, provider);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const onYjsTreeChanges = (events, transaction) => {
|
|
71
|
+
if (transaction.origin !== binding) {
|
|
72
|
+
yjs.syncYjsChangesToLexical(binding, provider, events);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
yjs.initLocalState(provider, name, color, document.activeElement === editor.getRootElement());
|
|
77
|
+
provider.on('status', onStatus);
|
|
78
|
+
provider.on('sync', onSync);
|
|
79
|
+
awareness.on('update', onAwarenessUpdate);
|
|
80
|
+
root.getSharedType().observeDeep(onYjsTreeChanges);
|
|
81
|
+
const removeListener = editor.addListener('update', ({
|
|
82
|
+
prevEditorState,
|
|
83
|
+
editorState,
|
|
84
|
+
dirtyLeaves,
|
|
85
|
+
dirtyElements,
|
|
86
|
+
normalizedNodes,
|
|
87
|
+
tags
|
|
88
|
+
}) => {
|
|
89
|
+
yjs.syncLexicalUpdateToYjs(binding, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags);
|
|
90
|
+
});
|
|
91
|
+
connect();
|
|
92
|
+
return () => {
|
|
93
|
+
disconnect();
|
|
94
|
+
provider.off('sync', onSync);
|
|
95
|
+
provider.off('status', onStatus);
|
|
96
|
+
awareness.off('update', onAwarenessUpdate);
|
|
97
|
+
root.getSharedType().unobserveDeep(onYjsTreeChanges);
|
|
98
|
+
removeListener();
|
|
99
|
+
};
|
|
100
|
+
}, [binding, color, connect, disconnect, editor, name, provider, shouldBootstrap]);
|
|
101
|
+
const cursorsContainer = React.useMemo(() => {
|
|
102
|
+
const ref = element => {
|
|
103
|
+
binding.cursorsContainer = element;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return /*#__PURE__*/reactDom.createPortal( /*#__PURE__*/React.createElement("div", {
|
|
107
|
+
ref: ref
|
|
108
|
+
}), document.body);
|
|
109
|
+
}, [binding]);
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
return editor.addListener('command', (type, payload) => {
|
|
112
|
+
if (type === 'toggleConnect') {
|
|
113
|
+
if (connect !== undefined && disconnect !== undefined) {
|
|
114
|
+
const shouldConnect = payload;
|
|
115
|
+
|
|
116
|
+
if (shouldConnect) {
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.log('Collaboration connected!');
|
|
119
|
+
connect();
|
|
120
|
+
} else {
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.log('Collaboration disconnected!');
|
|
123
|
+
disconnect();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}, EditorPriority);
|
|
130
|
+
}, [connect, disconnect, editor]);
|
|
131
|
+
return [cursorsContainer, binding];
|
|
132
|
+
}
|
|
133
|
+
function useYjsFocusTracking(editor, provider) {
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
const onBlur = () => {
|
|
136
|
+
yjs.setLocalStateFocus(provider, false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const onFocus = () => {
|
|
140
|
+
yjs.setLocalStateFocus(provider, true);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return editor.addListener('root', (rootElement, prevRootElement) => {
|
|
144
|
+
// Clear our old listener if the root element changes
|
|
145
|
+
if (prevRootElement !== null) {
|
|
146
|
+
prevRootElement.removeEventListener('blur', onBlur);
|
|
147
|
+
prevRootElement.removeEventListener('focus', onFocus);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (rootElement !== null) {
|
|
151
|
+
if (document.activeElement === rootElement) {
|
|
152
|
+
onFocus();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
rootElement.addEventListener('blur', onBlur);
|
|
156
|
+
rootElement.addEventListener('focus', onFocus);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}, [editor, provider, provider.awareness]);
|
|
160
|
+
}
|
|
161
|
+
function useYjsHistory(editor, binding) {
|
|
162
|
+
const undoManager = React.useMemo(() => yjs.createUndoManager(binding, binding.root.getSharedType()), [binding]);
|
|
163
|
+
React.useEffect(() => {
|
|
164
|
+
const undo = () => {
|
|
165
|
+
undoManager.undo();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const redo = () => {
|
|
169
|
+
undoManager.redo();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const applyCommand = type => {
|
|
173
|
+
if (type === 'undo') {
|
|
174
|
+
undo();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (type === 'redo') {
|
|
179
|
+
redo();
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return false;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return editor.addListener('command', applyCommand, EditorPriority);
|
|
187
|
+
});
|
|
188
|
+
const clearHistory = React.useCallback(() => {
|
|
189
|
+
undoManager.clear();
|
|
190
|
+
}, [undoManager]);
|
|
191
|
+
return clearHistory;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
196
|
+
*
|
|
197
|
+
* This source code is licensed under the MIT license found in the
|
|
198
|
+
* LICENSE file in the root directory of this source tree.
|
|
199
|
+
*
|
|
200
|
+
*
|
|
201
|
+
*/
|
|
202
|
+
const entries = [['Cat', '255,165,0'], ['Dog', '0,200,55'], ['Rabbit', '160,0,200'], ['Frog', '0,172,200'], ['Fox', '197,200,0'], ['Hedgehog', '31,200,0'], ['Pigeon', '200,0,0'], ['Squirrel', '200,0,148'], ['Bear', '255,235,0'], ['Tiger', '86,255,0'], ['Leopard', '0,255,208'], ['Zebra', '0,243,255'], ['Wolf', '0,102,255'], ['Owl', '147,0,255'], ['Gull', '255,0,153'], ['Squid', '0,220,255']];
|
|
203
|
+
const randomEntry = entries[Math.floor(Math.random() * (entries.length - 1 - 0 + 1) + 0)];
|
|
204
|
+
function CollaborationPlugin({
|
|
205
|
+
id,
|
|
206
|
+
providerFactory,
|
|
207
|
+
shouldBootstrap
|
|
208
|
+
}) {
|
|
209
|
+
const collabContext = useCollaborationContext();
|
|
210
|
+
const {
|
|
211
|
+
yjsDocMap,
|
|
212
|
+
name,
|
|
213
|
+
color
|
|
214
|
+
} = collabContext;
|
|
215
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
216
|
+
const provider = React.useMemo(() => providerFactory(id, yjsDocMap), [id, providerFactory, yjsDocMap]);
|
|
217
|
+
const [cursors, binding] = useYjsCollaboration(editor, id, provider, yjsDocMap, name, color, shouldBootstrap);
|
|
218
|
+
collabContext.clientID = binding.clientID;
|
|
219
|
+
useYjsHistory(editor, binding);
|
|
220
|
+
useYjsFocusTracking(editor, provider);
|
|
221
|
+
return cursors;
|
|
222
|
+
}
|
|
223
|
+
const CollaborationContext = /*#__PURE__*/React.createContext({
|
|
224
|
+
clientID: 0,
|
|
225
|
+
color: randomEntry[1],
|
|
226
|
+
name: randomEntry[0],
|
|
227
|
+
yjsDocMap: new Map()
|
|
228
|
+
});
|
|
229
|
+
function useCollaborationContext() {
|
|
230
|
+
return React.useContext(CollaborationContext);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
exports.CollaborationContext = CollaborationContext;
|
|
234
|
+
exports.CollaborationPlugin = CollaborationPlugin;
|
|
235
|
+
exports.useCollaborationContext = useCollaborationContext;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
const LexicalCollaborationPlugin = process.env.NODE_ENV === 'development' ? require('./LexicalCollaborationPlugin.dev.js') : require('./LexicalCollaborationPlugin.prod.js')
|
|
9
|
+
module.exports = LexicalCollaborationPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
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';var c=require("@lexical/react/LexicalComposerContext"),k=require("react"),z=require("@lexical/yjs"),A=require("react-dom");
|
|
8
|
+
function B(b,d,a,f,g,l,r){const e=k.useMemo(()=>z.createBinding(b,a,d,f),[b,a,d,f]),m=k.useCallback(()=>{a.connect()},[a]),n=k.useCallback(()=>{try{a.disconnect()}catch(h){}},[a]),q=k.useRef(!1);k.useLayoutEffect(()=>b.addListener("command",h=>"bootstrapEditor"===h?!q.current:!1,1),[b]);k.useEffect(()=>{const {root:h}=e,{awareness:t}=a,v=({status:p})=>{b.execCommand("connected","connected"===p)},w=p=>{r&&p&&h.isEmpty()&&0===h._xmlText._length&&(q.current=!0,b.execCommand("bootstrapEditor"))},x=()=>
|
|
9
|
+
{z.syncCursorPositions(e,a)},y=(p,u)=>{u.origin!==e&&z.syncYjsChangesToLexical(e,a,p)};z.initLocalState(a,g,l,document.activeElement===b.getRootElement());a.on("status",v);a.on("sync",w);t.on("update",x);h.getSharedType().observeDeep(y);const K=b.addListener("update",({prevEditorState:p,editorState:u,dirtyLeaves:G,dirtyElements:H,normalizedNodes:I,tags:J})=>{z.syncLexicalUpdateToYjs(e,a,p,u,H,G,I,J)});m();return()=>{n();a.off("sync",w);a.off("status",v);t.off("update",x);h.getSharedType().unobserveDeep(y);
|
|
10
|
+
K()}},[e,l,m,n,b,g,a,r]);const L=k.useMemo(()=>A.createPortal(k.createElement("div",{ref:h=>{e.cursorsContainer=h}}),document.body),[e]);k.useEffect(()=>b.addListener("command",(h,t)=>{"toggleConnect"===h&&void 0!==m&&void 0!==n&&(t?(console.log("Collaboration connected!"),m()):(console.log("Collaboration disconnected!"),n()));return!1},0),[m,n,b]);return[L,e]}
|
|
11
|
+
function C(b,d){k.useEffect(()=>{const a=()=>{z.setLocalStateFocus(d,!1)},f=()=>{z.setLocalStateFocus(d,!0)};return b.addListener("root",(g,l)=>{null!==l&&(l.removeEventListener("blur",a),l.removeEventListener("focus",f));null!==g&&(document.activeElement===g&&z.setLocalStateFocus(d,!0),g.addEventListener("blur",a),g.addEventListener("focus",f))})},[b,d,d.awareness])}
|
|
12
|
+
function D(b,d){const a=k.useMemo(()=>z.createUndoManager(d,d.root.getSharedType()),[d]);k.useEffect(()=>b.addListener("command",f=>"undo"===f?(a.undo(),!0):"redo"===f?(a.redo(),!0):!1,0));return k.useCallback(()=>{a.clear()},[a])}
|
|
13
|
+
const E=[["Cat","255,165,0"],["Dog","0,200,55"],["Rabbit","160,0,200"],["Frog","0,172,200"],["Fox","197,200,0"],["Hedgehog","31,200,0"],["Pigeon","200,0,0"],["Squirrel","200,0,148"],["Bear","255,235,0"],["Tiger","86,255,0"],["Leopard","0,255,208"],["Zebra","0,243,255"],["Wolf","0,102,255"],["Owl","147,0,255"],["Gull","255,0,153"],["Squid","0,220,255"]],F=E[Math.floor(Math.random()*(E.length-1+1))],M=k.createContext({clientID:0,color:F[1],name:F[0],yjsDocMap:new Map});
|
|
14
|
+
function N(){return k.useContext(M)}exports.CollaborationContext=M;exports.CollaborationPlugin=function({id:b,providerFactory:d,shouldBootstrap:a}){const f=N(),{yjsDocMap:g,name:l,color:r}=f,[e]=c.useLexicalComposerContext(),m=k.useMemo(()=>d(b,g),[b,d,g]),[n,q]=B(e,b,m,g,l,r,a);f.clientID=q.clientID;D(e,q);C(e,m);return n};exports.useCollaborationContext=N;
|