@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.
@@ -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 {operations, history} = editor
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
- let merge =
156
- currentUndoStepId === previousUndoStepId || isNormalizingNode(editor)
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
- if (step && merge) {
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
- while (undos.length > UNDO_STEP_LIMIT) {
185
- undos.shift()
186
- }
153
+ apply(op)
187
154
 
188
- if (shouldClear(op)) {
189
- history.redos = []
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)