@portabletext/editor 2.14.3 → 2.15.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/lib/_chunks-cjs/{selector.is-active-style.cjs → selector.is-at-the-start-of-block.cjs} +90 -94
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -0
- package/lib/_chunks-cjs/use-editor.cjs.map +1 -1
- package/lib/_chunks-dts/behavior.types.action.d.cts +4 -0
- package/lib/_chunks-dts/behavior.types.action.d.ts +4 -0
- package/lib/_chunks-es/{selector.is-active-style.js → selector.is-at-the-start-of-block.js} +90 -94
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -0
- package/lib/_chunks-es/use-editor.js.map +1 -1
- package/lib/index.cjs +505 -363
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +426 -284
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.ts +3 -3
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.cjs +33 -33
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.js +2 -2
- package/lib/utils/index.d.cts +2 -2
- package/package.json +8 -8
- package/src/editor/editor-dom.ts +99 -0
- package/src/editor/plugins/createWithUndoRedo.ts +78 -61
- package/src/editor/sync-machine.ts +410 -311
- package/src/internal-utils/operation-to-patches.ts +39 -0
- package/lib/_chunks-cjs/selector.is-active-style.cjs.map +0 -1
- package/lib/_chunks-es/selector.is-active-style.js.map +0 -1
package/src/editor/editor-dom.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {Editor} from 'slate'
|
|
|
2
2
|
import {DOMEditor} from 'slate-dom'
|
|
3
3
|
import type {BehaviorEvent} from '../behaviors/behavior.types.event'
|
|
4
4
|
import {toSlateRange} from '../internal-utils/to-slate-range'
|
|
5
|
+
import {getSelectionEndBlock, getSelectionStartBlock} from '../selectors'
|
|
5
6
|
import type {PickFromUnion} from '../type-utils'
|
|
6
7
|
import type {PortableTextSlateEditor} from '../types/editor'
|
|
7
8
|
import type {EditorSnapshot} from './editor-snapshot'
|
|
@@ -9,6 +10,10 @@ import type {EditorSnapshot} from './editor-snapshot'
|
|
|
9
10
|
export type EditorDom = {
|
|
10
11
|
getBlockNodes: (snapshot: EditorSnapshot) => Array<Node>
|
|
11
12
|
getChildNodes: (snapshot: EditorSnapshot) => Array<Node>
|
|
13
|
+
getEditorElement: () => Element | undefined
|
|
14
|
+
getSelectionRect: (snapshot: EditorSnapshot) => DOMRect | null
|
|
15
|
+
getStartBlockElement: (snapshot: EditorSnapshot) => Element | null
|
|
16
|
+
getEndBlockElement: (snapshot: EditorSnapshot) => Element | null
|
|
12
17
|
/**
|
|
13
18
|
* Let the Editor set the drag ghost. This is to be sure that it will get
|
|
14
19
|
* properly removed again when the drag ends.
|
|
@@ -33,6 +38,11 @@ export function createEditorDom(
|
|
|
33
38
|
return {
|
|
34
39
|
getBlockNodes: (snapshot) => getBlockNodes(slateEditor, snapshot),
|
|
35
40
|
getChildNodes: (snapshot) => getChildNodes(slateEditor, snapshot),
|
|
41
|
+
getEditorElement: () => getEditorElement(slateEditor),
|
|
42
|
+
getSelectionRect: (snapshot) => getSelectionRect(snapshot),
|
|
43
|
+
getStartBlockElement: (snapshot) =>
|
|
44
|
+
getStartBlockElement(slateEditor, snapshot),
|
|
45
|
+
getEndBlockElement: (snapshot) => getEndBlockElement(slateEditor, snapshot),
|
|
36
46
|
setDragGhost: ({event, ghost}) => setDragGhost({sendBack, event, ghost}),
|
|
37
47
|
}
|
|
38
48
|
}
|
|
@@ -99,6 +109,95 @@ function getChildNodes(
|
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
111
|
|
|
112
|
+
function getEditorElement(slateEditor: PortableTextSlateEditor) {
|
|
113
|
+
try {
|
|
114
|
+
return DOMEditor.toDOMNode(slateEditor, slateEditor)
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getSelectionRect(snapshot: EditorSnapshot) {
|
|
121
|
+
if (!snapshot.context.selection) {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const selection = window.getSelection()
|
|
127
|
+
|
|
128
|
+
if (!selection) {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const range = selection.getRangeAt(0)
|
|
133
|
+
return range.getBoundingClientRect()
|
|
134
|
+
} catch {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getStartBlockElement(
|
|
140
|
+
slateEditor: PortableTextSlateEditor,
|
|
141
|
+
snapshot: EditorSnapshot,
|
|
142
|
+
) {
|
|
143
|
+
const startBlock = getSelectionStartBlock(snapshot)
|
|
144
|
+
|
|
145
|
+
if (!startBlock) {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const startBlockNode = getBlockNodes(slateEditor, {
|
|
150
|
+
...snapshot,
|
|
151
|
+
context: {
|
|
152
|
+
...snapshot.context,
|
|
153
|
+
selection: {
|
|
154
|
+
anchor: {
|
|
155
|
+
path: startBlock.path,
|
|
156
|
+
offset: 0,
|
|
157
|
+
},
|
|
158
|
+
focus: {
|
|
159
|
+
path: startBlock.path,
|
|
160
|
+
offset: 0,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
})?.at(0)
|
|
165
|
+
|
|
166
|
+
return startBlockNode && startBlockNode instanceof Element
|
|
167
|
+
? startBlockNode
|
|
168
|
+
: null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getEndBlockElement(
|
|
172
|
+
slateEditor: PortableTextSlateEditor,
|
|
173
|
+
snapshot: EditorSnapshot,
|
|
174
|
+
) {
|
|
175
|
+
const endBlock = getSelectionEndBlock(snapshot)
|
|
176
|
+
|
|
177
|
+
if (!endBlock) {
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const endBlockNode = getBlockNodes(slateEditor, {
|
|
182
|
+
...snapshot,
|
|
183
|
+
context: {
|
|
184
|
+
...snapshot.context,
|
|
185
|
+
selection: {
|
|
186
|
+
anchor: {
|
|
187
|
+
path: endBlock.path,
|
|
188
|
+
offset: 0,
|
|
189
|
+
},
|
|
190
|
+
focus: {
|
|
191
|
+
path: endBlock.path,
|
|
192
|
+
offset: 0,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
})?.at(0)
|
|
197
|
+
|
|
198
|
+
return endBlockNode && endBlockNode instanceof Element ? endBlockNode : null
|
|
199
|
+
}
|
|
200
|
+
|
|
102
201
|
function setDragGhost({
|
|
103
202
|
sendBack,
|
|
104
203
|
event,
|
|
@@ -142,52 +142,89 @@ export function createWithUndoRedo(
|
|
|
142
142
|
return
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
const
|
|
146
|
-
const {undos} = history
|
|
147
|
-
const step = undos[undos.length - 1]
|
|
148
|
-
const lastOp =
|
|
149
|
-
step && step.operations && step.operations[step.operations.length - 1]
|
|
150
|
-
const overwrite = shouldOverwrite(op, lastOp)
|
|
151
|
-
const save = isSaving(editor)
|
|
152
|
-
|
|
145
|
+
const savingUndoSteps = isSaving(editor)
|
|
153
146
|
const currentUndoStepId = getCurrentUndoStepId(editor)
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (save) {
|
|
159
|
-
if (!step) {
|
|
160
|
-
merge = false
|
|
161
|
-
} else if (operations.length === 0) {
|
|
162
|
-
merge =
|
|
163
|
-
currentUndoStepId === undefined && previousUndoStepId === undefined
|
|
164
|
-
? shouldMerge(op, lastOp) || overwrite
|
|
165
|
-
: merge
|
|
166
|
-
}
|
|
148
|
+
if (!savingUndoSteps) {
|
|
149
|
+
// If we are bypassing saving undo steps, then we can just move along.
|
|
167
150
|
|
|
168
|
-
|
|
169
|
-
step.operations.push(op)
|
|
170
|
-
} else {
|
|
171
|
-
const newStep = {
|
|
172
|
-
operations: [
|
|
173
|
-
...(editor.selection === null
|
|
174
|
-
? []
|
|
175
|
-
: [createSelectOperation(editor)]),
|
|
176
|
-
op,
|
|
177
|
-
],
|
|
178
|
-
timestamp: new Date(),
|
|
179
|
-
}
|
|
180
|
-
undos.push(newStep)
|
|
181
|
-
debug('Created new undo step', step)
|
|
182
|
-
}
|
|
151
|
+
previousUndoStepId = currentUndoStepId
|
|
183
152
|
|
|
184
|
-
|
|
185
|
-
undos.shift()
|
|
186
|
-
}
|
|
153
|
+
apply(op)
|
|
187
154
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (op.type !== 'set_selection') {
|
|
159
|
+
// Clear the repo steps if any actual changes are made
|
|
160
|
+
editor.history.redos = []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const step = editor.history.undos.at(editor.history.undos.length - 1)
|
|
164
|
+
|
|
165
|
+
if (!step) {
|
|
166
|
+
// If the undo stack is empty, then we can just create a new step and
|
|
167
|
+
// move along.
|
|
168
|
+
|
|
169
|
+
editor.history.undos.push({
|
|
170
|
+
operations: [
|
|
171
|
+
...(editor.selection === null
|
|
172
|
+
? []
|
|
173
|
+
: [createSelectOperation(editor)]),
|
|
174
|
+
op,
|
|
175
|
+
],
|
|
176
|
+
timestamp: new Date(),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
apply(op)
|
|
180
|
+
|
|
181
|
+
previousUndoStepId = currentUndoStepId
|
|
182
|
+
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const selectingWithoutUndoStepId =
|
|
187
|
+
op.type === 'set_selection' &&
|
|
188
|
+
currentUndoStepId === undefined &&
|
|
189
|
+
previousUndoStepId !== undefined
|
|
190
|
+
const selectingWithDifferentUndoStepId =
|
|
191
|
+
op.type === 'set_selection' &&
|
|
192
|
+
currentUndoStepId !== undefined &&
|
|
193
|
+
previousUndoStepId !== undefined &&
|
|
194
|
+
previousUndoStepId !== currentUndoStepId
|
|
195
|
+
|
|
196
|
+
const lastOp = step.operations.at(-1)
|
|
197
|
+
const mergeOpIntoPreviousStep =
|
|
198
|
+
editor.operations.length > 0
|
|
199
|
+
? currentUndoStepId === previousUndoStepId ||
|
|
200
|
+
isNormalizingNode(editor)
|
|
201
|
+
: selectingWithoutUndoStepId ||
|
|
202
|
+
selectingWithDifferentUndoStepId ||
|
|
203
|
+
(currentUndoStepId === undefined &&
|
|
204
|
+
previousUndoStepId === undefined)
|
|
205
|
+
? shouldMerge(op, lastOp) ||
|
|
206
|
+
(lastOp?.type === 'set_selection' && op.type === 'set_selection')
|
|
207
|
+
: currentUndoStepId === previousUndoStepId ||
|
|
208
|
+
isNormalizingNode(editor)
|
|
209
|
+
|
|
210
|
+
if (mergeOpIntoPreviousStep) {
|
|
211
|
+
step.operations.push(op)
|
|
212
|
+
} else {
|
|
213
|
+
editor.history.undos.push({
|
|
214
|
+
operations: [
|
|
215
|
+
...(editor.selection === null
|
|
216
|
+
? []
|
|
217
|
+
: [createSelectOperation(editor)]),
|
|
218
|
+
op,
|
|
219
|
+
],
|
|
220
|
+
timestamp: new Date(),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Make sure we don't exceed the maximum number of undo steps we want
|
|
225
|
+
// to store.
|
|
226
|
+
while (editor.history.undos.length > UNDO_STEP_LIMIT) {
|
|
227
|
+
editor.history.undos.shift()
|
|
191
228
|
}
|
|
192
229
|
|
|
193
230
|
previousUndoStepId = currentUndoStepId
|
|
@@ -195,7 +232,6 @@ export function createWithUndoRedo(
|
|
|
195
232
|
apply(op)
|
|
196
233
|
}
|
|
197
234
|
|
|
198
|
-
// Plugin return
|
|
199
235
|
return editor
|
|
200
236
|
}
|
|
201
237
|
}
|
|
@@ -563,25 +599,6 @@ const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
|
|
563
599
|
return false
|
|
564
600
|
}
|
|
565
601
|
|
|
566
|
-
const shouldOverwrite = (
|
|
567
|
-
op: Operation,
|
|
568
|
-
prev: Operation | undefined,
|
|
569
|
-
): boolean => {
|
|
570
|
-
if (prev && op.type === 'set_selection' && prev.type === 'set_selection') {
|
|
571
|
-
return true
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return false
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const shouldClear = (op: Operation): boolean => {
|
|
578
|
-
if (op.type === 'set_selection') {
|
|
579
|
-
return false
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return true
|
|
583
|
-
}
|
|
584
|
-
|
|
585
602
|
export function withoutSaving(editor: Editor, fn: () => void): void {
|
|
586
603
|
const prev = isSaving(editor)
|
|
587
604
|
SAVING.set(editor, false)
|