@portabletext/editor 2.14.0 → 2.14.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -1,5 +1,6 @@
1
1
  import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
2
2
  import {getLastBlock} from '../selectors/selector.get-last-block'
3
+ import {isSelectionCollapsed} from '../utils'
3
4
  import {getBlockEndPoint} from '../utils/util.get-block-end-point'
4
5
  import {getBlockStartPoint} from '../utils/util.get-block-start-point'
5
6
  import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
@@ -472,4 +473,22 @@ export const abstractInsertBehaviors = [
472
473
  ],
473
474
  ],
474
475
  }),
476
+ defineBehavior({
477
+ on: 'insert.text',
478
+ guard: ({snapshot}) => {
479
+ const selection = snapshot.context.selection
480
+
481
+ if (!selection || isSelectionCollapsed(selection)) {
482
+ return false
483
+ }
484
+
485
+ return {selection}
486
+ },
487
+ actions: [
488
+ ({event}, {selection}) => [
489
+ raise({type: 'delete', at: selection}),
490
+ raise(event),
491
+ ],
492
+ ],
493
+ }),
475
494
  ]
@@ -34,6 +34,7 @@ import type {
34
34
  MutationEvent,
35
35
  PatchEvent,
36
36
  } from './relay-machine'
37
+ import {isNormalizingNode} from './with-normalizing-node'
37
38
 
38
39
  export * from 'xstate/guards'
39
40
 
@@ -93,16 +94,10 @@ export type InternalEditorEvent =
93
94
  type: 'focus'
94
95
  editor: PortableTextSlateEditor
95
96
  }
96
- | {
97
- type: 'normalizing'
98
- }
99
97
  | {
100
98
  type: 'update selection'
101
99
  selection: EditorSelection
102
100
  }
103
- | {
104
- type: 'done normalizing'
105
- }
106
101
  | {
107
102
  type: 'done syncing value'
108
103
  }
@@ -128,6 +123,7 @@ export type InternalEditorEvent =
128
123
  }
129
124
  | {type: 'dragend'}
130
125
  | {type: 'drop'}
126
+ | {type: 'add slate editor'; editor: PortableTextSlateEditor}
131
127
 
132
128
  /**
133
129
  * @internal
@@ -235,6 +231,13 @@ export const editorMachine = setup({
235
231
  return new Set([...context.behaviors])
236
232
  },
237
233
  }),
234
+ 'add slate editor to context': assign({
235
+ slateEditor: ({context, event}) => {
236
+ return event.type === 'add slate editor'
237
+ ? event.editor
238
+ : context.slateEditor
239
+ },
240
+ }),
238
241
  'emit patch event': emit(({event}) => {
239
242
  assertEvent(event, 'internal.patch')
240
243
  return event
@@ -363,6 +366,13 @@ export const editorMachine = setup({
363
366
 
364
367
  return context.slateEditor.operations.length > 0
365
368
  },
369
+ 'slate is normalizing node': ({context}) => {
370
+ if (!context.slateEditor) {
371
+ return false
372
+ }
373
+
374
+ return isNormalizingNode(context.slateEditor)
375
+ },
366
376
  },
367
377
  }).createMachine({
368
378
  id: 'editor',
@@ -386,6 +396,7 @@ export const editorMachine = setup({
386
396
  'update maxBlocks': {
387
397
  actions: assign({maxBlocks: ({event}) => event.maxBlocks}),
388
398
  },
399
+ 'add slate editor': {actions: 'add slate editor to context'},
389
400
  'update selection': {
390
401
  actions: [
391
402
  assign({selection: ({event}) => event.selection}),
@@ -694,44 +705,26 @@ export const editorMachine = setup({
694
705
  },
695
706
  ],
696
707
  on: {
697
- 'normalizing': {
698
- target: 'normalizing',
699
- },
700
- 'internal.patch': {
701
- actions: 'defer event',
702
- target: '#editor.setup.set up.writing.dirty',
703
- },
704
- 'mutation': {
705
- actions: 'defer event',
706
- target: '#editor.setup.set up.writing.dirty',
707
- },
708
- },
709
- },
710
- normalizing: {
711
- entry: [
712
- () => {
713
- debug(
714
- 'entry: setup->set up->writing->pristine->normalizing',
715
- )
716
- },
717
- ],
718
- exit: [
719
- () => {
720
- debug(
721
- 'exit: setup->set up->writing->pristine->normalizing',
722
- )
723
- },
724
- ],
725
- on: {
726
- 'done normalizing': {
727
- target: 'idle',
728
- },
729
- 'internal.patch': {
730
- actions: 'defer event',
731
- },
732
- 'mutation': {
733
- actions: 'defer event',
734
- },
708
+ 'internal.patch': [
709
+ {
710
+ guard: 'slate is normalizing node',
711
+ actions: 'defer event',
712
+ },
713
+ {
714
+ actions: 'defer event',
715
+ target: '#editor.setup.set up.writing.dirty',
716
+ },
717
+ ],
718
+ 'mutation': [
719
+ {
720
+ guard: 'slate is normalizing node',
721
+ actions: 'defer event',
722
+ },
723
+ {
724
+ actions: 'defer event',
725
+ target: '#editor.setup.set up.writing.dirty',
726
+ },
727
+ ],
735
728
  },
736
729
  },
737
730
  },
@@ -70,6 +70,10 @@ export function EditorProvider(props: EditorProviderProps) {
70
70
  unsubscribers.push(relayActorSubscription.unsubscribe)
71
71
 
72
72
  internalEditor.actors.editorActor.start()
73
+ internalEditor.actors.editorActor.send({
74
+ type: 'add slate editor',
75
+ editor: internalEditor.editor._internal.slateEditor.instance,
76
+ })
73
77
  internalEditor.actors.mutationActor.start()
74
78
  internalEditor.actors.relayActor.start()
75
79
  internalEditor.actors.syncActor.start()
@@ -3,6 +3,7 @@ import {isEqual} from 'lodash'
3
3
  import {Editor, Element, Node, Path, Transforms} from 'slate'
4
4
  import type {PortableTextSlateEditor} from '../../types/editor'
5
5
  import type {EditorActor} from '../editor-machine'
6
+ import {withNormalizeNode} from '../with-normalizing-node'
6
7
  import {isChangingRemotely} from '../withChanges'
7
8
  import {isRedoing, isUndoing} from '../withUndoRedo'
8
9
 
@@ -198,30 +199,33 @@ export function createWithObjectKeys(editorActor: EditorActor) {
198
199
  ) {
199
200
  // Set key on block itself
200
201
  if (!node._key) {
201
- editorActor.send({type: 'normalizing'})
202
- Transforms.setNodes(
203
- editor,
204
- {_key: editorActor.getSnapshot().context.keyGenerator()},
205
- {at: path},
206
- )
207
- editorActor.send({type: 'done normalizing'})
202
+ withNormalizeNode(editor, () => {
203
+ Transforms.setNodes(
204
+ editor,
205
+ {_key: editorActor.getSnapshot().context.keyGenerator()},
206
+ {at: path},
207
+ )
208
+ })
208
209
  return
209
210
  }
210
211
  // Set keys on it's children
211
212
  for (const [child, childPath] of Node.children(editor, path)) {
212
213
  if (!child._key) {
213
- editorActor.send({type: 'normalizing'})
214
- Transforms.setNodes(
215
- editor,
216
- {_key: editorActor.getSnapshot().context.keyGenerator()},
217
- {at: childPath},
218
- )
219
- editorActor.send({type: 'done normalizing'})
214
+ withNormalizeNode(editor, () => {
215
+ Transforms.setNodes(
216
+ editor,
217
+ {_key: editorActor.getSnapshot().context.keyGenerator()},
218
+ {at: childPath},
219
+ )
220
+ })
220
221
  return
221
222
  }
222
223
  }
223
224
  }
224
- normalizeNode(entry)
225
+
226
+ withNormalizeNode(editor, () => {
227
+ normalizeNode(entry)
228
+ })
225
229
  }
226
230
 
227
231
  return editor
@@ -15,6 +15,7 @@ import {getActiveDecorators} from '../../selectors/selector.get-active-decorator
15
15
  import type {PortableTextSlateEditor} from '../../types/editor'
16
16
  import type {EditorActor} from '../editor-machine'
17
17
  import {getEditorSnapshot} from '../editor-selector'
18
+ import {withNormalizeNode} from '../with-normalizing-node'
18
19
  import {isChangingRemotely} from '../withChanges'
19
20
  import {isRedoing, isUndoing} from '../withUndoRedo'
20
21
 
@@ -50,12 +51,12 @@ export function createWithPortableTextMarkModel(
50
51
  JSON.stringify(child, null, 2),
51
52
  JSON.stringify(nextNode, null, 2),
52
53
  )
53
- editorActor.send({type: 'normalizing'})
54
- Transforms.mergeNodes(editor, {
55
- at: [childPath[0], childPath[1] + 1],
56
- voids: true,
54
+ withNormalizeNode(editor, () => {
55
+ Transforms.mergeNodes(editor, {
56
+ at: [childPath[0], childPath[1] + 1],
57
+ voids: true,
58
+ })
57
59
  })
58
- editorActor.send({type: 'done normalizing'})
59
60
  return
60
61
  }
61
62
  }
@@ -66,9 +67,9 @@ export function createWithPortableTextMarkModel(
66
67
  */
67
68
  if (editor.isTextBlock(node) && !Array.isArray(node.markDefs)) {
68
69
  debug('Adding .markDefs to block node')
69
- editorActor.send({type: 'normalizing'})
70
- Transforms.setNodes(editor, {markDefs: []}, {at: path})
71
- editorActor.send({type: 'done normalizing'})
70
+ withNormalizeNode(editor, () => {
71
+ Transforms.setNodes(editor, {markDefs: []}, {at: path})
72
+ })
72
73
  return
73
74
  }
74
75
 
@@ -77,9 +78,9 @@ export function createWithPortableTextMarkModel(
77
78
  */
78
79
  if (editor.isTextSpan(node) && !Array.isArray(node.marks)) {
79
80
  debug('Adding .marks to span node')
80
- editorActor.send({type: 'normalizing'})
81
- Transforms.setNodes(editor, {marks: []}, {at: path})
82
- editorActor.send({type: 'done normalizing'})
81
+ withNormalizeNode(editor, () => {
82
+ Transforms.setNodes(editor, {marks: []}, {at: path})
83
+ })
83
84
  return
84
85
  }
85
86
 
@@ -99,13 +100,17 @@ export function createWithPortableTextMarkModel(
99
100
  if (editor.isTextBlock(block)) {
100
101
  if (node.text === '' && annotations && annotations.length > 0) {
101
102
  debug('Removing annotations from empty span node')
102
- editorActor.send({type: 'normalizing'})
103
- Transforms.setNodes(
104
- editor,
105
- {marks: node.marks?.filter((mark) => decorators.includes(mark))},
106
- {at: path},
107
- )
108
- editorActor.send({type: 'done normalizing'})
103
+ withNormalizeNode(editor, () => {
104
+ Transforms.setNodes(
105
+ editor,
106
+ {
107
+ marks: node.marks?.filter((mark) =>
108
+ decorators.includes(mark),
109
+ ),
110
+ },
111
+ {at: path},
112
+ )
113
+ })
109
114
  return
110
115
  }
111
116
  }
@@ -131,17 +136,17 @@ export function createWithPortableTextMarkModel(
131
136
 
132
137
  if (orphanedAnnotations.length > 0) {
133
138
  debug('Removing orphaned annotations from span node')
134
- editorActor.send({type: 'normalizing'})
135
- Transforms.setNodes(
136
- editor,
137
- {
138
- marks: marks.filter(
139
- (mark) => !orphanedAnnotations.includes(mark),
140
- ),
141
- },
142
- {at: childPath},
143
- )
144
- editorActor.send({type: 'done normalizing'})
139
+ withNormalizeNode(editor, () => {
140
+ Transforms.setNodes(
141
+ editor,
142
+ {
143
+ marks: marks.filter(
144
+ (mark) => !orphanedAnnotations.includes(mark),
145
+ ),
146
+ },
147
+ {at: childPath},
148
+ )
149
+ })
145
150
  return
146
151
  }
147
152
  }
@@ -169,17 +174,17 @@ export function createWithPortableTextMarkModel(
169
174
 
170
175
  if (orphanedAnnotations.length > 0) {
171
176
  debug('Removing orphaned annotations from span node')
172
- editorActor.send({type: 'normalizing'})
173
- Transforms.setNodes(
174
- editor,
175
- {
176
- marks: marks.filter(
177
- (mark) => !orphanedAnnotations.includes(mark),
178
- ),
179
- },
180
- {at: path},
181
- )
182
- editorActor.send({type: 'done normalizing'})
177
+ withNormalizeNode(editor, () => {
178
+ Transforms.setNodes(
179
+ editor,
180
+ {
181
+ marks: marks.filter(
182
+ (mark) => !orphanedAnnotations.includes(mark),
183
+ ),
184
+ },
185
+ {at: path},
186
+ )
187
+ })
183
188
  return
184
189
  }
185
190
  }
@@ -200,9 +205,9 @@ export function createWithPortableTextMarkModel(
200
205
 
201
206
  if (markDefs.length !== newMarkDefs.length) {
202
207
  debug('Removing duplicate markDefs')
203
- editorActor.send({type: 'normalizing'})
204
- Transforms.setNodes(editor, {markDefs: newMarkDefs}, {at: path})
205
- editorActor.send({type: 'done normalizing'})
208
+ withNormalizeNode(editor, () => {
209
+ Transforms.setNodes(editor, {markDefs: newMarkDefs}, {at: path})
210
+ })
206
211
  return
207
212
  }
208
213
  }
@@ -228,20 +233,22 @@ export function createWithPortableTextMarkModel(
228
233
  })
229
234
  if (node.markDefs && !isEqual(newMarkDefs, node.markDefs)) {
230
235
  debug('Removing markDef not in use')
231
- editorActor.send({type: 'normalizing'})
232
- Transforms.setNodes(
233
- editor,
234
- {
235
- markDefs: newMarkDefs,
236
- },
237
- {at: path},
238
- )
239
- editorActor.send({type: 'done normalizing'})
236
+ withNormalizeNode(editor, () => {
237
+ Transforms.setNodes(
238
+ editor,
239
+ {
240
+ markDefs: newMarkDefs,
241
+ },
242
+ {at: path},
243
+ )
244
+ })
240
245
  return
241
246
  }
242
247
  }
243
248
 
244
- normalizeNode(nodeEntry)
249
+ withNormalizeNode(editor, () => {
250
+ normalizeNode(nodeEntry)
251
+ })
245
252
  }
246
253
 
247
254
  editor.apply = (op) => {
@@ -9,6 +9,7 @@ import {debugWithName} from '../../internal-utils/debug'
9
9
  import type {PortableTextSlateEditor} from '../../types/editor'
10
10
  import {isListBlock} from '../../utils/parse-blocks'
11
11
  import type {EditorActor} from '../editor-machine'
12
+ import {withNormalizeNode} from '../with-normalizing-node'
12
13
 
13
14
  const debug = debugWithName('plugin:withSchemaTypes')
14
15
  /**
@@ -87,17 +88,17 @@ export function createWithSchemaTypes({
87
88
  const span = node as PortableTextSpan
88
89
  const key =
89
90
  span._key || editorActor.getSnapshot().context.keyGenerator()
90
- editorActor.send({type: 'normalizing'})
91
- Transforms.setNodes(
92
- editor,
93
- {
94
- ...span,
95
- _type: editorActor.getSnapshot().context.schema.span.name,
96
- _key: key,
97
- },
98
- {at: path},
99
- )
100
- editorActor.send({type: 'done normalizing'})
91
+ withNormalizeNode(editor, () => {
92
+ Transforms.setNodes(
93
+ editor,
94
+ {
95
+ ...span,
96
+ _type: editorActor.getSnapshot().context.schema.span.name,
97
+ _key: key,
98
+ },
99
+ {at: path},
100
+ )
101
+ })
101
102
  return
102
103
  }
103
104
 
@@ -105,13 +106,15 @@ export function createWithSchemaTypes({
105
106
  if (node._key === undefined && (path.length === 1 || path.length === 2)) {
106
107
  debug('Setting missing key on child node without a key')
107
108
  const key = editorActor.getSnapshot().context.keyGenerator()
108
- editorActor.send({type: 'normalizing'})
109
- Transforms.setNodes(editor, {_key: key}, {at: path})
110
- editorActor.send({type: 'done normalizing'})
109
+ withNormalizeNode(editor, () => {
110
+ Transforms.setNodes(editor, {_key: key}, {at: path})
111
+ })
111
112
  return
112
113
  }
113
114
 
114
- normalizeNode(entry)
115
+ withNormalizeNode(editor, () => {
116
+ normalizeNode(entry)
117
+ })
115
118
  }
116
119
  return editor
117
120
  }
@@ -25,6 +25,7 @@ import {fromSlateValue} from '../../internal-utils/values'
25
25
  import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
26
26
  import type {PortableTextSlateEditor} from '../../types/editor'
27
27
  import type {EditorActor} from '../editor-machine'
28
+ import {isNormalizingNode} from '../with-normalizing-node'
28
29
  import {getCurrentUndoStepId} from '../with-undo-step'
29
30
  import {isChangingRemotely} from '../withChanges'
30
31
  import {
@@ -151,7 +152,8 @@ export function createWithUndoRedo(
151
152
 
152
153
  const currentUndoStepId = getCurrentUndoStepId(editor)
153
154
 
154
- let merge = currentUndoStepId === previousUndoStepId
155
+ let merge =
156
+ currentUndoStepId === previousUndoStepId || isNormalizingNode(editor)
155
157
 
156
158
  if (save) {
157
159
  if (!step) {
@@ -0,0 +1,14 @@
1
+ import {Editor} from 'slate'
2
+
3
+ const IS_NORMALIZING_NODE: WeakMap<Editor, boolean | undefined> = new WeakMap()
4
+
5
+ export function withNormalizeNode(editor: Editor, fn: () => void) {
6
+ const prev = IS_NORMALIZING_NODE.get(editor)
7
+ IS_NORMALIZING_NODE.set(editor, true)
8
+ fn()
9
+ IS_NORMALIZING_NODE.set(editor, prev)
10
+ }
11
+
12
+ export function isNormalizingNode(editor: Editor) {
13
+ return IS_NORMALIZING_NODE.get(editor) ?? false
14
+ }