@lexical/history 0.37.1-nightly.20251027.0 → 0.38.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/LexicalHistory.js +3 -3
- package/package.json +4 -4
- package/src/__tests__/unit/LexicalHistory.test.tsx +452 -0
- package/{LexicalHistory.dev.mjs → src/index.ts} +336 -92
- package/LICENSE +0 -21
- package/LexicalHistory.dev.js +0 -363
- package/LexicalHistory.mjs +0 -15
- package/LexicalHistory.node.mjs +0 -13
- package/LexicalHistory.prod.js +0 -9
- package/LexicalHistory.prod.mjs +0 -9
- package/index.d.ts +0 -63
- /package/{LexicalHistory.js.flow → flow/LexicalHistory.js.flow} +0 -0
|
@@ -6,59 +6,126 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { mergeRegister } from '@lexical/utils';
|
|
11
|
-
import { defineExtension, safeCast, configExtension, UNDO_COMMAND, COMMAND_PRIORITY_EDITOR, REDO_COMMAND, CLEAR_EDITOR_COMMAND, CLEAR_HISTORY_COMMAND, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, HISTORIC_TAG, HISTORY_PUSH_TAG, HISTORY_MERGE_TAG, $isRangeSelection, $isTextNode, $isRootNode } from 'lexical';
|
|
9
|
+
import type {EditorState, LexicalEditor, LexicalNode, NodeKey} from 'lexical';
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
import {
|
|
12
|
+
batch,
|
|
13
|
+
effect,
|
|
14
|
+
getPeerDependencyFromEditor,
|
|
15
|
+
namedSignals,
|
|
16
|
+
ReadonlySignal,
|
|
17
|
+
} from '@lexical/extension';
|
|
18
|
+
import {mergeRegister} from '@lexical/utils';
|
|
19
|
+
import {
|
|
20
|
+
$isRangeSelection,
|
|
21
|
+
$isRootNode,
|
|
22
|
+
$isTextNode,
|
|
23
|
+
CAN_REDO_COMMAND,
|
|
24
|
+
CAN_UNDO_COMMAND,
|
|
25
|
+
CLEAR_EDITOR_COMMAND,
|
|
26
|
+
CLEAR_HISTORY_COMMAND,
|
|
27
|
+
COMMAND_PRIORITY_EDITOR,
|
|
28
|
+
configExtension,
|
|
29
|
+
defineExtension,
|
|
30
|
+
HISTORIC_TAG,
|
|
31
|
+
HISTORY_MERGE_TAG,
|
|
32
|
+
HISTORY_PUSH_TAG,
|
|
33
|
+
REDO_COMMAND,
|
|
34
|
+
safeCast,
|
|
35
|
+
UNDO_COMMAND,
|
|
36
|
+
} from 'lexical';
|
|
20
37
|
|
|
38
|
+
type MergeAction = 0 | 1 | 2;
|
|
21
39
|
const HISTORY_MERGE = 0;
|
|
22
40
|
const HISTORY_PUSH = 1;
|
|
23
41
|
const DISCARD_HISTORY_CANDIDATE = 2;
|
|
42
|
+
|
|
43
|
+
type ChangeType = 0 | 1 | 2 | 3 | 4;
|
|
24
44
|
const OTHER = 0;
|
|
25
45
|
const COMPOSING_CHARACTER = 1;
|
|
26
46
|
const INSERT_CHARACTER_AFTER_SELECTION = 2;
|
|
27
47
|
const DELETE_CHARACTER_BEFORE_SELECTION = 3;
|
|
28
48
|
const DELETE_CHARACTER_AFTER_SELECTION = 4;
|
|
29
|
-
|
|
49
|
+
|
|
50
|
+
export type HistoryStateEntry = {
|
|
51
|
+
editor: LexicalEditor;
|
|
52
|
+
editorState: EditorState;
|
|
53
|
+
};
|
|
54
|
+
export type HistoryState = {
|
|
55
|
+
current: null | HistoryStateEntry;
|
|
56
|
+
redoStack: Array<HistoryStateEntry>;
|
|
57
|
+
undoStack: Array<HistoryStateEntry>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type IntentionallyMarkedAsDirtyElement = boolean;
|
|
61
|
+
|
|
62
|
+
function getDirtyNodes(
|
|
63
|
+
editorState: EditorState,
|
|
64
|
+
dirtyLeaves: Set<NodeKey>,
|
|
65
|
+
dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
|
|
66
|
+
): Array<LexicalNode> {
|
|
30
67
|
const nodeMap = editorState._nodeMap;
|
|
31
68
|
const nodes = [];
|
|
69
|
+
|
|
32
70
|
for (const dirtyLeafKey of dirtyLeaves) {
|
|
33
71
|
const dirtyLeaf = nodeMap.get(dirtyLeafKey);
|
|
72
|
+
|
|
34
73
|
if (dirtyLeaf !== undefined) {
|
|
35
74
|
nodes.push(dirtyLeaf);
|
|
36
75
|
}
|
|
37
76
|
}
|
|
77
|
+
|
|
38
78
|
for (const [dirtyElementKey, intentionallyMarkedAsDirty] of dirtyElements) {
|
|
39
79
|
if (!intentionallyMarkedAsDirty) {
|
|
40
80
|
continue;
|
|
41
81
|
}
|
|
82
|
+
|
|
42
83
|
const dirtyElement = nodeMap.get(dirtyElementKey);
|
|
84
|
+
|
|
43
85
|
if (dirtyElement !== undefined && !$isRootNode(dirtyElement)) {
|
|
44
86
|
nodes.push(dirtyElement);
|
|
45
87
|
}
|
|
46
88
|
}
|
|
89
|
+
|
|
47
90
|
return nodes;
|
|
48
91
|
}
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
|
|
93
|
+
function getChangeType(
|
|
94
|
+
prevEditorState: null | EditorState,
|
|
95
|
+
nextEditorState: EditorState,
|
|
96
|
+
dirtyLeavesSet: Set<NodeKey>,
|
|
97
|
+
dirtyElementsSet: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
|
|
98
|
+
isComposing: boolean,
|
|
99
|
+
): ChangeType {
|
|
100
|
+
if (
|
|
101
|
+
prevEditorState === null ||
|
|
102
|
+
(dirtyLeavesSet.size === 0 && dirtyElementsSet.size === 0 && !isComposing)
|
|
103
|
+
) {
|
|
51
104
|
return OTHER;
|
|
52
105
|
}
|
|
106
|
+
|
|
53
107
|
const nextSelection = nextEditorState._selection;
|
|
54
108
|
const prevSelection = prevEditorState._selection;
|
|
109
|
+
|
|
55
110
|
if (isComposing) {
|
|
56
111
|
return COMPOSING_CHARACTER;
|
|
57
112
|
}
|
|
58
|
-
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
!$isRangeSelection(nextSelection) ||
|
|
116
|
+
!$isRangeSelection(prevSelection) ||
|
|
117
|
+
!prevSelection.isCollapsed() ||
|
|
118
|
+
!nextSelection.isCollapsed()
|
|
119
|
+
) {
|
|
59
120
|
return OTHER;
|
|
60
121
|
}
|
|
61
|
-
|
|
122
|
+
|
|
123
|
+
const dirtyNodes = getDirtyNodes(
|
|
124
|
+
nextEditorState,
|
|
125
|
+
dirtyLeavesSet,
|
|
126
|
+
dirtyElementsSet,
|
|
127
|
+
);
|
|
128
|
+
|
|
62
129
|
if (dirtyNodes.length === 0) {
|
|
63
130
|
return OTHER;
|
|
64
131
|
}
|
|
@@ -69,58 +136,123 @@ function getChangeType(prevEditorState, nextEditorState, dirtyLeavesSet, dirtyEl
|
|
|
69
136
|
const nextNodeMap = nextEditorState._nodeMap;
|
|
70
137
|
const nextAnchorNode = nextNodeMap.get(nextSelection.anchor.key);
|
|
71
138
|
const prevAnchorNode = nextNodeMap.get(prevSelection.anchor.key);
|
|
72
|
-
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
nextAnchorNode &&
|
|
142
|
+
prevAnchorNode &&
|
|
143
|
+
!prevEditorState._nodeMap.has(nextAnchorNode.__key) &&
|
|
144
|
+
$isTextNode(nextAnchorNode) &&
|
|
145
|
+
nextAnchorNode.__text.length === 1 &&
|
|
146
|
+
nextSelection.anchor.offset === 1
|
|
147
|
+
) {
|
|
73
148
|
return INSERT_CHARACTER_AFTER_SELECTION;
|
|
74
149
|
}
|
|
150
|
+
|
|
75
151
|
return OTHER;
|
|
76
152
|
}
|
|
153
|
+
|
|
77
154
|
const nextDirtyNode = dirtyNodes[0];
|
|
155
|
+
|
|
78
156
|
const prevDirtyNode = prevEditorState._nodeMap.get(nextDirtyNode.__key);
|
|
79
|
-
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
!$isTextNode(prevDirtyNode) ||
|
|
160
|
+
!$isTextNode(nextDirtyNode) ||
|
|
161
|
+
prevDirtyNode.__mode !== nextDirtyNode.__mode
|
|
162
|
+
) {
|
|
80
163
|
return OTHER;
|
|
81
164
|
}
|
|
165
|
+
|
|
82
166
|
const prevText = prevDirtyNode.__text;
|
|
83
167
|
const nextText = nextDirtyNode.__text;
|
|
168
|
+
|
|
84
169
|
if (prevText === nextText) {
|
|
85
170
|
return OTHER;
|
|
86
171
|
}
|
|
172
|
+
|
|
87
173
|
const nextAnchor = nextSelection.anchor;
|
|
88
174
|
const prevAnchor = prevSelection.anchor;
|
|
175
|
+
|
|
89
176
|
if (nextAnchor.key !== prevAnchor.key || nextAnchor.type !== 'text') {
|
|
90
177
|
return OTHER;
|
|
91
178
|
}
|
|
179
|
+
|
|
92
180
|
const nextAnchorOffset = nextAnchor.offset;
|
|
93
181
|
const prevAnchorOffset = prevAnchor.offset;
|
|
94
182
|
const textDiff = nextText.length - prevText.length;
|
|
183
|
+
|
|
95
184
|
if (textDiff === 1 && prevAnchorOffset === nextAnchorOffset - 1) {
|
|
96
185
|
return INSERT_CHARACTER_AFTER_SELECTION;
|
|
97
186
|
}
|
|
187
|
+
|
|
98
188
|
if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset + 1) {
|
|
99
189
|
return DELETE_CHARACTER_BEFORE_SELECTION;
|
|
100
190
|
}
|
|
191
|
+
|
|
101
192
|
if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset) {
|
|
102
193
|
return DELETE_CHARACTER_AFTER_SELECTION;
|
|
103
194
|
}
|
|
195
|
+
|
|
104
196
|
return OTHER;
|
|
105
197
|
}
|
|
106
|
-
|
|
198
|
+
|
|
199
|
+
function isTextNodeUnchanged(
|
|
200
|
+
key: NodeKey,
|
|
201
|
+
prevEditorState: EditorState,
|
|
202
|
+
nextEditorState: EditorState,
|
|
203
|
+
): boolean {
|
|
107
204
|
const prevNode = prevEditorState._nodeMap.get(key);
|
|
108
205
|
const nextNode = nextEditorState._nodeMap.get(key);
|
|
206
|
+
|
|
109
207
|
const prevSelection = prevEditorState._selection;
|
|
110
208
|
const nextSelection = nextEditorState._selection;
|
|
111
|
-
const isDeletingLine =
|
|
112
|
-
|
|
209
|
+
const isDeletingLine =
|
|
210
|
+
$isRangeSelection(prevSelection) &&
|
|
211
|
+
$isRangeSelection(nextSelection) &&
|
|
212
|
+
prevSelection.anchor.type === 'element' &&
|
|
213
|
+
prevSelection.focus.type === 'element' &&
|
|
214
|
+
nextSelection.anchor.type === 'text' &&
|
|
215
|
+
nextSelection.focus.type === 'text';
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
!isDeletingLine &&
|
|
219
|
+
$isTextNode(prevNode) &&
|
|
220
|
+
$isTextNode(nextNode) &&
|
|
221
|
+
prevNode.__parent === nextNode.__parent
|
|
222
|
+
) {
|
|
113
223
|
// This has the assumption that object key order won't change if the
|
|
114
224
|
// content did not change, which should normally be safe given
|
|
115
225
|
// the manner in which nodes and exportJSON are typically implemented.
|
|
116
|
-
return
|
|
226
|
+
return (
|
|
227
|
+
JSON.stringify(prevEditorState.read(() => prevNode.exportJSON())) ===
|
|
228
|
+
JSON.stringify(nextEditorState.read(() => nextNode.exportJSON()))
|
|
229
|
+
);
|
|
117
230
|
}
|
|
118
231
|
return false;
|
|
119
232
|
}
|
|
120
|
-
|
|
233
|
+
|
|
234
|
+
function createMergeActionGetter(
|
|
235
|
+
editor: LexicalEditor,
|
|
236
|
+
delayOrStore: number | ReadonlySignal<number>,
|
|
237
|
+
): (
|
|
238
|
+
prevEditorState: null | EditorState,
|
|
239
|
+
nextEditorState: EditorState,
|
|
240
|
+
currentHistoryEntry: null | HistoryStateEntry,
|
|
241
|
+
dirtyLeaves: Set<NodeKey>,
|
|
242
|
+
dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
|
|
243
|
+
tags: Set<string>,
|
|
244
|
+
) => MergeAction {
|
|
121
245
|
let prevChangeTime = Date.now();
|
|
122
246
|
let prevChangeType = OTHER;
|
|
123
|
-
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
prevEditorState,
|
|
250
|
+
nextEditorState,
|
|
251
|
+
currentHistoryEntry,
|
|
252
|
+
dirtyLeaves,
|
|
253
|
+
dirtyElements,
|
|
254
|
+
tags,
|
|
255
|
+
) => {
|
|
124
256
|
const changeTime = Date.now();
|
|
125
257
|
|
|
126
258
|
// If applying changes from history stack there's no need
|
|
@@ -130,27 +262,50 @@ function createMergeActionGetter(editor, delayOrStore) {
|
|
|
130
262
|
prevChangeTime = changeTime;
|
|
131
263
|
return DISCARD_HISTORY_CANDIDATE;
|
|
132
264
|
}
|
|
133
|
-
|
|
265
|
+
|
|
266
|
+
const changeType = getChangeType(
|
|
267
|
+
prevEditorState,
|
|
268
|
+
nextEditorState,
|
|
269
|
+
dirtyLeaves,
|
|
270
|
+
dirtyElements,
|
|
271
|
+
editor.isComposing(),
|
|
272
|
+
);
|
|
273
|
+
|
|
134
274
|
const mergeAction = (() => {
|
|
135
|
-
const isSameEditor =
|
|
275
|
+
const isSameEditor =
|
|
276
|
+
currentHistoryEntry === null || currentHistoryEntry.editor === editor;
|
|
136
277
|
const shouldPushHistory = tags.has(HISTORY_PUSH_TAG);
|
|
137
|
-
const shouldMergeHistory =
|
|
278
|
+
const shouldMergeHistory =
|
|
279
|
+
!shouldPushHistory && isSameEditor && tags.has(HISTORY_MERGE_TAG);
|
|
280
|
+
|
|
138
281
|
if (shouldMergeHistory) {
|
|
139
282
|
return HISTORY_MERGE;
|
|
140
283
|
}
|
|
284
|
+
|
|
141
285
|
if (prevEditorState === null) {
|
|
142
286
|
return HISTORY_PUSH;
|
|
143
287
|
}
|
|
288
|
+
|
|
144
289
|
const selection = nextEditorState._selection;
|
|
145
290
|
const hasDirtyNodes = dirtyLeaves.size > 0 || dirtyElements.size > 0;
|
|
291
|
+
|
|
146
292
|
if (!hasDirtyNodes) {
|
|
147
293
|
if (selection !== null) {
|
|
148
294
|
return HISTORY_MERGE;
|
|
149
295
|
}
|
|
296
|
+
|
|
150
297
|
return DISCARD_HISTORY_CANDIDATE;
|
|
151
298
|
}
|
|
152
|
-
|
|
153
|
-
|
|
299
|
+
|
|
300
|
+
const delay =
|
|
301
|
+
typeof delayOrStore === 'number' ? delayOrStore : delayOrStore.peek();
|
|
302
|
+
if (
|
|
303
|
+
shouldPushHistory === false &&
|
|
304
|
+
changeType !== OTHER &&
|
|
305
|
+
changeType === prevChangeType &&
|
|
306
|
+
changeTime < prevChangeTime + delay &&
|
|
307
|
+
isSameEditor
|
|
308
|
+
) {
|
|
154
309
|
return HISTORY_MERGE;
|
|
155
310
|
}
|
|
156
311
|
|
|
@@ -158,61 +313,80 @@ function createMergeActionGetter(editor, delayOrStore) {
|
|
|
158
313
|
// due to some node transform reverting the change.
|
|
159
314
|
if (dirtyLeaves.size === 1) {
|
|
160
315
|
const dirtyLeafKey = Array.from(dirtyLeaves)[0];
|
|
161
|
-
if (
|
|
316
|
+
if (
|
|
317
|
+
isTextNodeUnchanged(dirtyLeafKey, prevEditorState, nextEditorState)
|
|
318
|
+
) {
|
|
162
319
|
return HISTORY_MERGE;
|
|
163
320
|
}
|
|
164
321
|
}
|
|
322
|
+
|
|
165
323
|
return HISTORY_PUSH;
|
|
166
324
|
})();
|
|
325
|
+
|
|
167
326
|
prevChangeTime = changeTime;
|
|
168
327
|
prevChangeType = changeType;
|
|
328
|
+
|
|
169
329
|
return mergeAction;
|
|
170
330
|
};
|
|
171
331
|
}
|
|
172
|
-
|
|
332
|
+
|
|
333
|
+
function redo(editor: LexicalEditor, historyState: HistoryState): void {
|
|
173
334
|
const redoStack = historyState.redoStack;
|
|
174
335
|
const undoStack = historyState.undoStack;
|
|
336
|
+
|
|
175
337
|
if (redoStack.length !== 0) {
|
|
176
338
|
const current = historyState.current;
|
|
339
|
+
|
|
177
340
|
if (current !== null) {
|
|
178
341
|
undoStack.push(current);
|
|
179
342
|
editor.dispatchCommand(CAN_UNDO_COMMAND, true);
|
|
180
343
|
}
|
|
344
|
+
|
|
181
345
|
const historyStateEntry = redoStack.pop();
|
|
346
|
+
|
|
182
347
|
if (redoStack.length === 0) {
|
|
183
348
|
editor.dispatchCommand(CAN_REDO_COMMAND, false);
|
|
184
349
|
}
|
|
350
|
+
|
|
185
351
|
historyState.current = historyStateEntry || null;
|
|
352
|
+
|
|
186
353
|
if (historyStateEntry) {
|
|
187
354
|
historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
|
|
188
|
-
tag: HISTORIC_TAG
|
|
355
|
+
tag: HISTORIC_TAG,
|
|
189
356
|
});
|
|
190
357
|
}
|
|
191
358
|
}
|
|
192
359
|
}
|
|
193
|
-
|
|
360
|
+
|
|
361
|
+
function undo(editor: LexicalEditor, historyState: HistoryState): void {
|
|
194
362
|
const redoStack = historyState.redoStack;
|
|
195
363
|
const undoStack = historyState.undoStack;
|
|
196
364
|
const undoStackLength = undoStack.length;
|
|
365
|
+
|
|
197
366
|
if (undoStackLength !== 0) {
|
|
198
367
|
const current = historyState.current;
|
|
199
368
|
const historyStateEntry = undoStack.pop();
|
|
369
|
+
|
|
200
370
|
if (current !== null) {
|
|
201
371
|
redoStack.push(current);
|
|
202
372
|
editor.dispatchCommand(CAN_REDO_COMMAND, true);
|
|
203
373
|
}
|
|
374
|
+
|
|
204
375
|
if (undoStack.length === 0) {
|
|
205
376
|
editor.dispatchCommand(CAN_UNDO_COMMAND, false);
|
|
206
377
|
}
|
|
378
|
+
|
|
207
379
|
historyState.current = historyStateEntry || null;
|
|
380
|
+
|
|
208
381
|
if (historyStateEntry) {
|
|
209
382
|
historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
|
|
210
|
-
tag: HISTORIC_TAG
|
|
383
|
+
tag: HISTORIC_TAG,
|
|
211
384
|
});
|
|
212
385
|
}
|
|
213
386
|
}
|
|
214
387
|
}
|
|
215
|
-
|
|
388
|
+
|
|
389
|
+
function clearHistory(historyState: HistoryState) {
|
|
216
390
|
historyState.undoStack = [];
|
|
217
391
|
historyState.redoStack = [];
|
|
218
392
|
historyState.current = null;
|
|
@@ -227,31 +401,53 @@ function clearHistory(historyState) {
|
|
|
227
401
|
* instead of merging the current changes with the current stack.
|
|
228
402
|
* @returns The listeners cleanup callback function.
|
|
229
403
|
*/
|
|
230
|
-
function registerHistory(
|
|
404
|
+
export function registerHistory(
|
|
405
|
+
editor: LexicalEditor,
|
|
406
|
+
historyState: HistoryState,
|
|
407
|
+
delay: number | ReadonlySignal<number>,
|
|
408
|
+
): () => void {
|
|
231
409
|
const getMergeAction = createMergeActionGetter(editor, delay);
|
|
410
|
+
|
|
232
411
|
const applyChange = ({
|
|
233
412
|
editorState,
|
|
234
413
|
prevEditorState,
|
|
235
414
|
dirtyLeaves,
|
|
236
415
|
dirtyElements,
|
|
237
|
-
tags
|
|
238
|
-
}
|
|
416
|
+
tags,
|
|
417
|
+
}: {
|
|
418
|
+
editorState: EditorState;
|
|
419
|
+
prevEditorState: EditorState;
|
|
420
|
+
dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>;
|
|
421
|
+
dirtyLeaves: Set<NodeKey>;
|
|
422
|
+
tags: Set<string>;
|
|
423
|
+
}): void => {
|
|
239
424
|
const current = historyState.current;
|
|
240
425
|
const redoStack = historyState.redoStack;
|
|
241
426
|
const undoStack = historyState.undoStack;
|
|
242
427
|
const currentEditorState = current === null ? null : current.editorState;
|
|
428
|
+
|
|
243
429
|
if (current !== null && editorState === currentEditorState) {
|
|
244
430
|
return;
|
|
245
431
|
}
|
|
246
|
-
|
|
432
|
+
|
|
433
|
+
const mergeAction = getMergeAction(
|
|
434
|
+
prevEditorState,
|
|
435
|
+
editorState,
|
|
436
|
+
current,
|
|
437
|
+
dirtyLeaves,
|
|
438
|
+
dirtyElements,
|
|
439
|
+
tags,
|
|
440
|
+
);
|
|
441
|
+
|
|
247
442
|
if (mergeAction === HISTORY_PUSH) {
|
|
248
443
|
if (redoStack.length !== 0) {
|
|
249
444
|
historyState.redoStack = [];
|
|
250
445
|
editor.dispatchCommand(CAN_REDO_COMMAND, false);
|
|
251
446
|
}
|
|
447
|
+
|
|
252
448
|
if (current !== null) {
|
|
253
449
|
undoStack.push({
|
|
254
|
-
...current
|
|
450
|
+
...current,
|
|
255
451
|
});
|
|
256
452
|
editor.dispatchCommand(CAN_UNDO_COMMAND, true);
|
|
257
453
|
}
|
|
@@ -262,24 +458,48 @@ function registerHistory(editor, historyState, delay) {
|
|
|
262
458
|
// Else we merge
|
|
263
459
|
historyState.current = {
|
|
264
460
|
editor,
|
|
265
|
-
editorState
|
|
461
|
+
editorState,
|
|
266
462
|
};
|
|
267
463
|
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
464
|
+
|
|
465
|
+
const unregister = mergeRegister(
|
|
466
|
+
editor.registerCommand(
|
|
467
|
+
UNDO_COMMAND,
|
|
468
|
+
() => {
|
|
469
|
+
undo(editor, historyState);
|
|
470
|
+
return true;
|
|
471
|
+
},
|
|
472
|
+
COMMAND_PRIORITY_EDITOR,
|
|
473
|
+
),
|
|
474
|
+
editor.registerCommand(
|
|
475
|
+
REDO_COMMAND,
|
|
476
|
+
() => {
|
|
477
|
+
redo(editor, historyState);
|
|
478
|
+
return true;
|
|
479
|
+
},
|
|
480
|
+
COMMAND_PRIORITY_EDITOR,
|
|
481
|
+
),
|
|
482
|
+
editor.registerCommand(
|
|
483
|
+
CLEAR_EDITOR_COMMAND,
|
|
484
|
+
() => {
|
|
485
|
+
clearHistory(historyState);
|
|
486
|
+
return false;
|
|
487
|
+
},
|
|
488
|
+
COMMAND_PRIORITY_EDITOR,
|
|
489
|
+
),
|
|
490
|
+
editor.registerCommand(
|
|
491
|
+
CLEAR_HISTORY_COMMAND,
|
|
492
|
+
() => {
|
|
493
|
+
clearHistory(historyState);
|
|
494
|
+
editor.dispatchCommand(CAN_REDO_COMMAND, false);
|
|
495
|
+
editor.dispatchCommand(CAN_UNDO_COMMAND, false);
|
|
496
|
+
return true;
|
|
497
|
+
},
|
|
498
|
+
COMMAND_PRIORITY_EDITOR,
|
|
499
|
+
),
|
|
500
|
+
editor.registerUpdateListener(applyChange),
|
|
501
|
+
);
|
|
502
|
+
|
|
283
503
|
return unregister;
|
|
284
504
|
}
|
|
285
505
|
|
|
@@ -287,41 +507,65 @@ function registerHistory(editor, historyState, delay) {
|
|
|
287
507
|
* Creates an empty history state.
|
|
288
508
|
* @returns - The empty history state, as an object.
|
|
289
509
|
*/
|
|
290
|
-
function createEmptyHistoryState() {
|
|
510
|
+
export function createEmptyHistoryState(): HistoryState {
|
|
291
511
|
return {
|
|
292
512
|
current: null,
|
|
293
513
|
redoStack: [],
|
|
294
|
-
undoStack: []
|
|
514
|
+
undoStack: [],
|
|
295
515
|
};
|
|
296
516
|
}
|
|
517
|
+
|
|
518
|
+
export interface HistoryConfig {
|
|
519
|
+
/**
|
|
520
|
+
* The time (in milliseconds) the editor should delay generating a new history stack,
|
|
521
|
+
* instead of merging the current changes with the current stack. The default is 300ms.
|
|
522
|
+
*/
|
|
523
|
+
delay: number;
|
|
524
|
+
/**
|
|
525
|
+
* The initial history state, the default is {@link createEmptyHistoryState}.
|
|
526
|
+
*/
|
|
527
|
+
createInitialHistoryState: (editor: LexicalEditor) => HistoryState;
|
|
528
|
+
/**
|
|
529
|
+
* Whether history is disabled or not
|
|
530
|
+
*/
|
|
531
|
+
disabled: boolean;
|
|
532
|
+
}
|
|
533
|
+
|
|
297
534
|
/**
|
|
298
535
|
* Registers necessary listeners to manage undo/redo history stack and related
|
|
299
536
|
* editor commands, via the \@lexical/history module.
|
|
300
537
|
*/
|
|
301
538
|
|
|
302
|
-
const HistoryExtension = defineExtension({
|
|
303
|
-
build: (editor, {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
historyState: createInitialHistoryState(editor)
|
|
311
|
-
}),
|
|
312
|
-
config: safeCast({
|
|
539
|
+
export const HistoryExtension = defineExtension({
|
|
540
|
+
build: (editor, {delay, createInitialHistoryState, disabled}) =>
|
|
541
|
+
namedSignals({
|
|
542
|
+
delay,
|
|
543
|
+
disabled,
|
|
544
|
+
historyState: createInitialHistoryState(editor),
|
|
545
|
+
}),
|
|
546
|
+
config: safeCast<HistoryConfig>({
|
|
313
547
|
createInitialHistoryState: createEmptyHistoryState,
|
|
314
548
|
delay: 300,
|
|
315
|
-
disabled: typeof window === 'undefined'
|
|
549
|
+
disabled: typeof window === 'undefined',
|
|
316
550
|
}),
|
|
317
551
|
name: '@lexical/history/History',
|
|
318
552
|
register: (editor, config, state) => {
|
|
319
553
|
const stores = state.getOutput();
|
|
320
|
-
return effect(() =>
|
|
321
|
-
|
|
554
|
+
return effect(() =>
|
|
555
|
+
stores.disabled.value
|
|
556
|
+
? undefined
|
|
557
|
+
: registerHistory(editor, stores.historyState.value, stores.delay),
|
|
558
|
+
);
|
|
559
|
+
},
|
|
322
560
|
});
|
|
323
|
-
|
|
324
|
-
|
|
561
|
+
|
|
562
|
+
function getHistoryPeer(editor: LexicalEditor | null | undefined) {
|
|
563
|
+
return editor
|
|
564
|
+
? getPeerDependencyFromEditor<typeof HistoryExtension>(
|
|
565
|
+
editor,
|
|
566
|
+
HistoryExtension.name,
|
|
567
|
+
)
|
|
568
|
+
: null;
|
|
325
569
|
}
|
|
326
570
|
|
|
327
571
|
/**
|
|
@@ -329,30 +573,30 @@ function getHistoryPeer(editor) {
|
|
|
329
573
|
* editor commands, via the \@lexical/history module, only if the parent editor
|
|
330
574
|
* has a history plugin implementation.
|
|
331
575
|
*/
|
|
332
|
-
const SharedHistoryExtension = defineExtension({
|
|
333
|
-
dependencies: [
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
576
|
+
export const SharedHistoryExtension = defineExtension({
|
|
577
|
+
dependencies: [
|
|
578
|
+
configExtension(HistoryExtension, {
|
|
579
|
+
createInitialHistoryState: () => {
|
|
580
|
+
throw new Error('SharedHistory did not inherit parent history');
|
|
581
|
+
},
|
|
582
|
+
disabled: true,
|
|
583
|
+
}),
|
|
584
|
+
],
|
|
339
585
|
name: '@lexical/history/SharedHistory',
|
|
340
586
|
register(editor, _config, state) {
|
|
341
|
-
const {
|
|
342
|
-
output
|
|
343
|
-
} = state.getDependency(HistoryExtension);
|
|
587
|
+
const {output} = state.getDependency(HistoryExtension);
|
|
344
588
|
const parentPeer = getHistoryPeer(editor._parentEditor);
|
|
345
589
|
if (!parentPeer) {
|
|
346
590
|
return () => {};
|
|
347
591
|
}
|
|
348
592
|
const parentOutput = parentPeer.output;
|
|
349
|
-
return effect(() =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
593
|
+
return effect(() =>
|
|
594
|
+
batch(() => {
|
|
595
|
+
output.delay.value = parentOutput.delay.value;
|
|
596
|
+
output.historyState.value = parentOutput.historyState.value;
|
|
597
|
+
// Note that toggling the parent history will force this to be changed
|
|
598
|
+
output.disabled.value = parentOutput.disabled.value;
|
|
599
|
+
}),
|
|
600
|
+
);
|
|
601
|
+
},
|
|
356
602
|
});
|
|
357
|
-
|
|
358
|
-
export { HistoryExtension, SharedHistoryExtension, createEmptyHistoryState, registerHistory };
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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.
|