@portabletext/editor 1.49.6 → 1.49.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/lib/behaviors/index.d.cts +24 -272
- package/lib/behaviors/index.d.ts +24 -272
- package/lib/index.cjs +3124 -3164
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +31 -277
- package/lib/index.d.ts +31 -277
- package/lib/index.js +3207 -3247
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +31 -277
- package/lib/plugins/index.d.ts +31 -277
- package/lib/selectors/index.d.cts +24 -272
- package/lib/selectors/index.d.ts +24 -272
- package/lib/utils/index.d.cts +24 -272
- package/lib/utils/index.d.ts +24 -272
- package/package.json +1 -1
- package/src/editor/PortableTextEditor.tsx +12 -101
- package/src/editor/create-editor.ts +132 -2
- package/src/editor/editor-machine.ts +2 -9
- package/src/editor/editor-provider.tsx +1 -6
- package/src/editor/route-events-to-changes.tsx +81 -0
- package/src/editor/sync-machine.ts +48 -0
- package/src/editor.ts +7 -1
- package/src/internal-utils/text-selection.ts +3 -1
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +0 -364
- package/src/editor/components/Synchronizer.tsx +0 -134
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from 'react'
|
|
15
15
|
import {Subject} from 'rxjs'
|
|
16
16
|
import {Slate} from 'slate-react'
|
|
17
|
-
import {useEffectEvent} from 'use-effect-event'
|
|
18
17
|
import {createActor} from 'xstate'
|
|
19
18
|
import {createCoreConverters} from '../converters/converters.core'
|
|
20
19
|
import {debugWithName} from '../internal-utils/debug'
|
|
@@ -29,7 +28,6 @@ import type {
|
|
|
29
28
|
PatchObservable,
|
|
30
29
|
PortableTextMemberSchemaTypes,
|
|
31
30
|
} from '../types/editor'
|
|
32
|
-
import {Synchronizer} from './components/Synchronizer'
|
|
33
31
|
import {createInternalEditor, type InternalEditor} from './create-editor'
|
|
34
32
|
import {EditorActorContext} from './editor-actor-context'
|
|
35
33
|
import {editorMachine, type EditorActor} from './editor-machine'
|
|
@@ -38,6 +36,7 @@ import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
|
38
36
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
39
37
|
import {defaultKeyGenerator} from './key-generator'
|
|
40
38
|
import {createLegacySchema} from './legacy-schema'
|
|
39
|
+
import {eventToChange} from './route-events-to-changes'
|
|
41
40
|
|
|
42
41
|
const debug = debugWithName('component:PortableTextEditor')
|
|
43
42
|
|
|
@@ -164,6 +163,16 @@ export class PortableTextEditor extends Component<
|
|
|
164
163
|
})
|
|
165
164
|
editorActor.start()
|
|
166
165
|
|
|
166
|
+
editorActor.on('*', (event) => {
|
|
167
|
+
const change = eventToChange(event)
|
|
168
|
+
|
|
169
|
+
if (change) {
|
|
170
|
+
props.onChange(change)
|
|
171
|
+
|
|
172
|
+
this.change$.next(change)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
167
176
|
this.editor = createInternalEditor(editorActor)
|
|
168
177
|
this.schemaTypes = legacySchema
|
|
169
178
|
}
|
|
@@ -209,7 +218,7 @@ export class PortableTextEditor extends Component<
|
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
if (this.props.value !== prevProps.value) {
|
|
212
|
-
this.editor.
|
|
221
|
+
this.editor.send({
|
|
213
222
|
type: 'update value',
|
|
214
223
|
value: this.props.value,
|
|
215
224
|
})
|
|
@@ -244,23 +253,6 @@ export class PortableTextEditor extends Component<
|
|
|
244
253
|
patches$={legacyPatches}
|
|
245
254
|
/>
|
|
246
255
|
) : null}
|
|
247
|
-
<RouteEventsToChanges
|
|
248
|
-
editorActor={this.editor._internal.editorActor}
|
|
249
|
-
onChange={(change) => {
|
|
250
|
-
if (!this.props.editor) {
|
|
251
|
-
this.props.onChange(change)
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* For backwards compatibility, we relay all changes to the
|
|
255
|
-
* `change$` Subject as well.
|
|
256
|
-
*/
|
|
257
|
-
this.change$.next(change)
|
|
258
|
-
}}
|
|
259
|
-
/>
|
|
260
|
-
<Synchronizer
|
|
261
|
-
editorActor={this.editor._internal.editorActor}
|
|
262
|
-
slateEditor={this.editor._internal.slateEditor.instance}
|
|
263
|
-
/>
|
|
264
256
|
<EditorActorContext.Provider value={this.editor._internal.editorActor}>
|
|
265
257
|
<Slate
|
|
266
258
|
editor={this.editor._internal.slateEditor.instance}
|
|
@@ -775,84 +767,3 @@ function RoutePatchesObservableToEditorActor(props: {
|
|
|
775
767
|
|
|
776
768
|
return null
|
|
777
769
|
}
|
|
778
|
-
|
|
779
|
-
export function RouteEventsToChanges(props: {
|
|
780
|
-
editorActor: EditorActor
|
|
781
|
-
onChange: (change: EditorChange) => void
|
|
782
|
-
}) {
|
|
783
|
-
// We want to ensure that _when_ `props.onChange` is called, it uses the current value.
|
|
784
|
-
// But we don't want to have the `useEffect` run setup + teardown + setup every time the prop might change, as that's unnecessary.
|
|
785
|
-
// So we use our own polyfill that lets us use an upcoming React hook that solves this exact problem.
|
|
786
|
-
// https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
|
|
787
|
-
const handleChange = useEffectEvent((change: EditorChange) =>
|
|
788
|
-
props.onChange(change),
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
useEffect(() => {
|
|
792
|
-
debug('Subscribing to editor changes')
|
|
793
|
-
const sub = props.editorActor.on('*', (event) => {
|
|
794
|
-
switch (event.type) {
|
|
795
|
-
case 'blurred': {
|
|
796
|
-
handleChange({type: 'blur', event: event.event})
|
|
797
|
-
break
|
|
798
|
-
}
|
|
799
|
-
case 'patch':
|
|
800
|
-
handleChange(event)
|
|
801
|
-
break
|
|
802
|
-
case 'loading': {
|
|
803
|
-
handleChange({type: 'loading', isLoading: true})
|
|
804
|
-
break
|
|
805
|
-
}
|
|
806
|
-
case 'done loading': {
|
|
807
|
-
handleChange({type: 'loading', isLoading: false})
|
|
808
|
-
break
|
|
809
|
-
}
|
|
810
|
-
case 'focused': {
|
|
811
|
-
handleChange({type: 'focus', event: event.event})
|
|
812
|
-
break
|
|
813
|
-
}
|
|
814
|
-
case 'value changed': {
|
|
815
|
-
handleChange({type: 'value', value: event.value})
|
|
816
|
-
break
|
|
817
|
-
}
|
|
818
|
-
case 'invalid value': {
|
|
819
|
-
handleChange({
|
|
820
|
-
type: 'invalidValue',
|
|
821
|
-
resolution: event.resolution,
|
|
822
|
-
value: event.value,
|
|
823
|
-
})
|
|
824
|
-
break
|
|
825
|
-
}
|
|
826
|
-
case 'error': {
|
|
827
|
-
handleChange({
|
|
828
|
-
...event,
|
|
829
|
-
level: 'warning',
|
|
830
|
-
})
|
|
831
|
-
break
|
|
832
|
-
}
|
|
833
|
-
case 'mutation': {
|
|
834
|
-
handleChange(event)
|
|
835
|
-
break
|
|
836
|
-
}
|
|
837
|
-
case 'ready': {
|
|
838
|
-
handleChange(event)
|
|
839
|
-
break
|
|
840
|
-
}
|
|
841
|
-
case 'selection': {
|
|
842
|
-
handleChange(event)
|
|
843
|
-
break
|
|
844
|
-
}
|
|
845
|
-
case 'unset': {
|
|
846
|
-
handleChange(event)
|
|
847
|
-
break
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
})
|
|
851
|
-
return () => {
|
|
852
|
-
debug('Unsubscribing to changes')
|
|
853
|
-
sub.unsubscribe()
|
|
854
|
-
}
|
|
855
|
-
}, [props.editorActor])
|
|
856
|
-
|
|
857
|
-
return null
|
|
858
|
-
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import {createActor, type ActorRefFrom} from 'xstate'
|
|
1
2
|
import {createCoreConverters} from '../converters/converters.core'
|
|
2
3
|
import type {Editor, EditorConfig} from '../editor'
|
|
4
|
+
import {debugWithName} from '../internal-utils/debug'
|
|
3
5
|
import {compileType} from '../internal-utils/schema'
|
|
6
|
+
import {fromSlateValue} from '../internal-utils/values'
|
|
7
|
+
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
4
8
|
import {corePriority} from '../priority/priority.core'
|
|
5
9
|
import {createEditorPriority} from '../priority/priority.types'
|
|
6
|
-
import type {EditableAPI} from '../types/editor'
|
|
10
|
+
import type {EditableAPI, PortableTextSlateEditor} from '../types/editor'
|
|
7
11
|
import {createSlateEditor, type SlateEditor} from './create-slate-editor'
|
|
8
12
|
import type {EditorActor} from './editor-machine'
|
|
9
13
|
import {
|
|
@@ -13,7 +17,11 @@ import {
|
|
|
13
17
|
import {getEditorSnapshot} from './editor-selector'
|
|
14
18
|
import {defaultKeyGenerator} from './key-generator'
|
|
15
19
|
import {createLegacySchema} from './legacy-schema'
|
|
20
|
+
import {mutationMachine} from './mutation-machine'
|
|
16
21
|
import {createEditableAPI} from './plugins/createWithEditableAPI'
|
|
22
|
+
import {syncMachine} from './sync-machine'
|
|
23
|
+
|
|
24
|
+
const debug = debugWithName('createInternalEditor')
|
|
17
25
|
|
|
18
26
|
export type InternalEditor = Editor & {
|
|
19
27
|
_internal: {
|
|
@@ -56,6 +64,13 @@ export function editorConfigToMachineInput(config: EditorConfig) {
|
|
|
56
64
|
export function createInternalEditor(editorActor: EditorActor): InternalEditor {
|
|
57
65
|
const slateEditor = createSlateEditor({editorActor})
|
|
58
66
|
const editable = createEditableAPI(slateEditor.instance, editorActor)
|
|
67
|
+
const {mutationActor, syncActor} = createActors({
|
|
68
|
+
editorActor,
|
|
69
|
+
slateEditor: slateEditor.instance,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
mutationActor.start()
|
|
73
|
+
syncActor.start()
|
|
59
74
|
|
|
60
75
|
return {
|
|
61
76
|
getSnapshot: () =>
|
|
@@ -90,10 +105,13 @@ export function createInternalEditor(editorActor: EditorActor): InternalEditor {
|
|
|
90
105
|
},
|
|
91
106
|
send: (event) => {
|
|
92
107
|
switch (event.type) {
|
|
108
|
+
case 'update value':
|
|
109
|
+
syncActor.send(event)
|
|
110
|
+
break
|
|
111
|
+
|
|
93
112
|
case 'update key generator':
|
|
94
113
|
case 'update readOnly':
|
|
95
114
|
case 'patches':
|
|
96
|
-
case 'update value':
|
|
97
115
|
case 'update schema':
|
|
98
116
|
case 'update maxBlocks':
|
|
99
117
|
editorActor.send(event)
|
|
@@ -166,3 +184,115 @@ export function createInternalEditor(editorActor: EditorActor): InternalEditor {
|
|
|
166
184
|
},
|
|
167
185
|
}
|
|
168
186
|
}
|
|
187
|
+
|
|
188
|
+
const actors = new WeakMap<
|
|
189
|
+
EditorActor,
|
|
190
|
+
{
|
|
191
|
+
syncActor: ActorRefFrom<typeof syncMachine>
|
|
192
|
+
mutationActor: ActorRefFrom<typeof mutationMachine>
|
|
193
|
+
}
|
|
194
|
+
>()
|
|
195
|
+
|
|
196
|
+
function createActors(config: {
|
|
197
|
+
editorActor: EditorActor
|
|
198
|
+
slateEditor: PortableTextSlateEditor
|
|
199
|
+
}): {
|
|
200
|
+
syncActor: ActorRefFrom<typeof syncMachine>
|
|
201
|
+
mutationActor: ActorRefFrom<typeof mutationMachine>
|
|
202
|
+
} {
|
|
203
|
+
const existingActor = actors.get(config.editorActor)
|
|
204
|
+
|
|
205
|
+
if (existingActor) {
|
|
206
|
+
debug('Reusing existing actors')
|
|
207
|
+
return existingActor
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
debug('Creating new actors')
|
|
211
|
+
|
|
212
|
+
const mutationActor = createActor(mutationMachine, {
|
|
213
|
+
input: {
|
|
214
|
+
schema: config.editorActor.getSnapshot().context.schema,
|
|
215
|
+
slateEditor: config.slateEditor,
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const syncActor = createActor(syncMachine, {
|
|
220
|
+
input: {
|
|
221
|
+
initialValue: config.editorActor.getSnapshot().context.initialValue,
|
|
222
|
+
keyGenerator: config.editorActor.getSnapshot().context.keyGenerator,
|
|
223
|
+
readOnly: config.editorActor
|
|
224
|
+
.getSnapshot()
|
|
225
|
+
.matches({'edit mode': 'read only'}),
|
|
226
|
+
schema: config.editorActor.getSnapshot().context.schema,
|
|
227
|
+
slateEditor: config.slateEditor,
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
mutationActor.on('*', (event) => {
|
|
232
|
+
if (event.type === 'has pending patches') {
|
|
233
|
+
syncActor.send({type: 'has pending patches'})
|
|
234
|
+
}
|
|
235
|
+
if (event.type === 'mutation') {
|
|
236
|
+
syncActor.send({type: 'mutation'})
|
|
237
|
+
config.editorActor.send({
|
|
238
|
+
type: 'mutation',
|
|
239
|
+
patches: event.patches,
|
|
240
|
+
snapshot: event.snapshot,
|
|
241
|
+
value: event.snapshot,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
syncActor.on('*', (event) => {
|
|
247
|
+
switch (event.type) {
|
|
248
|
+
case 'invalid value':
|
|
249
|
+
config.editorActor.send({
|
|
250
|
+
...event,
|
|
251
|
+
type: 'notify.invalid value',
|
|
252
|
+
})
|
|
253
|
+
break
|
|
254
|
+
case 'value changed':
|
|
255
|
+
config.editorActor.send({
|
|
256
|
+
...event,
|
|
257
|
+
type: 'notify.value changed',
|
|
258
|
+
})
|
|
259
|
+
break
|
|
260
|
+
case 'patch':
|
|
261
|
+
config.editorActor.send({
|
|
262
|
+
...event,
|
|
263
|
+
type: 'internal.patch',
|
|
264
|
+
value: fromSlateValue(
|
|
265
|
+
config.slateEditor.children,
|
|
266
|
+
config.editorActor.getSnapshot().context.schema.block.name,
|
|
267
|
+
KEY_TO_VALUE_ELEMENT.get(config.slateEditor),
|
|
268
|
+
),
|
|
269
|
+
})
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
default:
|
|
273
|
+
config.editorActor.send(event)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
config.editorActor.on('*', (event) => {
|
|
278
|
+
if (event.type === 'read only') {
|
|
279
|
+
syncActor.send({type: 'update readOnly', readOnly: true})
|
|
280
|
+
}
|
|
281
|
+
if (event.type === 'editable') {
|
|
282
|
+
syncActor.send({type: 'update readOnly', readOnly: false})
|
|
283
|
+
}
|
|
284
|
+
if (event.type === 'internal.patch') {
|
|
285
|
+
mutationActor.send({...event, type: 'patch'})
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
actors.set(config.editorActor, {
|
|
290
|
+
syncActor,
|
|
291
|
+
mutationActor,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
syncActor,
|
|
296
|
+
mutationActor,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -71,10 +71,6 @@ export type ExternalEditorEvent =
|
|
|
71
71
|
type: 'update key generator'
|
|
72
72
|
keyGenerator: () => string
|
|
73
73
|
}
|
|
74
|
-
| {
|
|
75
|
-
type: 'update value'
|
|
76
|
-
value: Array<PortableTextBlock> | undefined
|
|
77
|
-
}
|
|
78
74
|
| {
|
|
79
75
|
type: 'update maxBlocks'
|
|
80
76
|
maxBlocks: number | undefined
|
|
@@ -227,7 +223,7 @@ export const editorMachine = setup({
|
|
|
227
223
|
initialReadOnly: boolean
|
|
228
224
|
maxBlocks: number | undefined
|
|
229
225
|
selection: EditorSelection
|
|
230
|
-
|
|
226
|
+
initialValue: Array<PortableTextBlock> | undefined
|
|
231
227
|
internalDrag?: {
|
|
232
228
|
ghost?: HTMLElement
|
|
233
229
|
origin: Pick<EventPosition, 'selection'>
|
|
@@ -402,7 +398,7 @@ export const editorMachine = setup({
|
|
|
402
398
|
selection: null,
|
|
403
399
|
initialReadOnly: input.readOnly ?? false,
|
|
404
400
|
maxBlocks: input.maxBlocks,
|
|
405
|
-
|
|
401
|
+
initialValue: input.initialValue,
|
|
406
402
|
}),
|
|
407
403
|
on: {
|
|
408
404
|
'notify.blurred': {
|
|
@@ -434,9 +430,6 @@ export const editorMachine = setup({
|
|
|
434
430
|
actions: assign({keyGenerator: ({event}) => event.keyGenerator}),
|
|
435
431
|
},
|
|
436
432
|
'update schema': {actions: 'assign schema'},
|
|
437
|
-
'update value': {
|
|
438
|
-
actions: assign({incomingValue: ({event}) => event.value}),
|
|
439
|
-
},
|
|
440
433
|
'update maxBlocks': {
|
|
441
434
|
actions: assign({maxBlocks: ({event}) => event.maxBlocks}),
|
|
442
435
|
},
|
|
@@ -3,7 +3,6 @@ import type React from 'react'
|
|
|
3
3
|
import {useMemo} from 'react'
|
|
4
4
|
import {Slate} from 'slate-react'
|
|
5
5
|
import type {EditorConfig} from '../editor'
|
|
6
|
-
import {Synchronizer} from './components/Synchronizer'
|
|
7
6
|
import {createInternalEditor, editorConfigToMachineInput} from './create-editor'
|
|
8
7
|
import {EditorActorContext} from './editor-actor-context'
|
|
9
8
|
import {EditorContext} from './editor-context'
|
|
@@ -12,9 +11,9 @@ import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
|
12
11
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
13
12
|
import {
|
|
14
13
|
PortableTextEditor,
|
|
15
|
-
RouteEventsToChanges,
|
|
16
14
|
type PortableTextEditorProps,
|
|
17
15
|
} from './PortableTextEditor'
|
|
16
|
+
import {RouteEventsToChanges} from './route-events-to-changes'
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* @public
|
|
@@ -66,10 +65,6 @@ export function EditorProvider(props: EditorProviderProps) {
|
|
|
66
65
|
portableTextEditor.change$.next(change)
|
|
67
66
|
}}
|
|
68
67
|
/>
|
|
69
|
-
<Synchronizer
|
|
70
|
-
editorActor={editorActor}
|
|
71
|
-
slateEditor={internalEditor._internal.slateEditor.instance}
|
|
72
|
-
/>
|
|
73
68
|
<EditorActorContext.Provider value={editorActor}>
|
|
74
69
|
<Slate
|
|
75
70
|
editor={internalEditor._internal.slateEditor.instance}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {useEffect} from 'react'
|
|
2
|
+
import {useEffectEvent} from 'use-effect-event'
|
|
3
|
+
import type {EditorChange} from '../types/editor'
|
|
4
|
+
import type {EditorActor, InternalEditorEmittedEvent} from './editor-machine'
|
|
5
|
+
|
|
6
|
+
export function RouteEventsToChanges(props: {
|
|
7
|
+
editorActor: EditorActor
|
|
8
|
+
onChange: (change: EditorChange) => void
|
|
9
|
+
}) {
|
|
10
|
+
// We want to ensure that _when_ `props.onChange` is called, it uses the current value.
|
|
11
|
+
// But we don't want to have the `useEffect` run setup + teardown + setup every time the prop might change, as that's unnecessary.
|
|
12
|
+
// So we use our own polyfill that lets us use an upcoming React hook that solves this exact problem.
|
|
13
|
+
// https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
|
|
14
|
+
const handleChange = useEffectEvent((change: EditorChange) =>
|
|
15
|
+
props.onChange(change),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const sub = props.editorActor.on('*', (event) => {
|
|
20
|
+
const change = eventToChange(event)
|
|
21
|
+
|
|
22
|
+
if (change) {
|
|
23
|
+
handleChange(change)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
return () => {
|
|
27
|
+
sub.unsubscribe()
|
|
28
|
+
}
|
|
29
|
+
}, [props.editorActor])
|
|
30
|
+
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function eventToChange(
|
|
35
|
+
event: InternalEditorEmittedEvent,
|
|
36
|
+
): EditorChange | undefined {
|
|
37
|
+
switch (event.type) {
|
|
38
|
+
case 'blurred': {
|
|
39
|
+
return {type: 'blur', event: event.event}
|
|
40
|
+
}
|
|
41
|
+
case 'patch':
|
|
42
|
+
return event
|
|
43
|
+
case 'loading': {
|
|
44
|
+
return {type: 'loading', isLoading: true}
|
|
45
|
+
}
|
|
46
|
+
case 'done loading': {
|
|
47
|
+
return {type: 'loading', isLoading: false}
|
|
48
|
+
}
|
|
49
|
+
case 'focused': {
|
|
50
|
+
return {type: 'focus', event: event.event}
|
|
51
|
+
}
|
|
52
|
+
case 'value changed': {
|
|
53
|
+
return {type: 'value', value: event.value}
|
|
54
|
+
}
|
|
55
|
+
case 'invalid value': {
|
|
56
|
+
return {
|
|
57
|
+
type: 'invalidValue',
|
|
58
|
+
resolution: event.resolution,
|
|
59
|
+
value: event.value,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
case 'error': {
|
|
63
|
+
return {
|
|
64
|
+
...event,
|
|
65
|
+
level: 'warning',
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
case 'mutation': {
|
|
69
|
+
return event
|
|
70
|
+
}
|
|
71
|
+
case 'ready': {
|
|
72
|
+
return event
|
|
73
|
+
}
|
|
74
|
+
case 'selection': {
|
|
75
|
+
return event
|
|
76
|
+
}
|
|
77
|
+
case 'unset': {
|
|
78
|
+
return event
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
emit,
|
|
10
10
|
fromCallback,
|
|
11
11
|
not,
|
|
12
|
+
raise,
|
|
12
13
|
setup,
|
|
13
14
|
type AnyEventObject,
|
|
14
15
|
type CallbackLogicFunction,
|
|
@@ -91,6 +92,7 @@ const syncValueLogic = fromCallback(syncValueCallback)
|
|
|
91
92
|
export const syncMachine = setup({
|
|
92
93
|
types: {
|
|
93
94
|
context: {} as {
|
|
95
|
+
initialValue: Array<PortableTextBlock> | undefined
|
|
94
96
|
initialValueSynced: boolean
|
|
95
97
|
isProcessingLocalChanges: boolean
|
|
96
98
|
keyGenerator: () => string
|
|
@@ -101,6 +103,7 @@ export const syncMachine = setup({
|
|
|
101
103
|
previousValue: Array<PortableTextBlock> | undefined
|
|
102
104
|
},
|
|
103
105
|
input: {} as {
|
|
106
|
+
initialValue: Array<PortableTextBlock> | undefined
|
|
104
107
|
keyGenerator: () => string
|
|
105
108
|
schema: EditorSchema
|
|
106
109
|
readOnly: boolean
|
|
@@ -175,6 +178,16 @@ export const syncMachine = setup({
|
|
|
175
178
|
|
|
176
179
|
return isBusy
|
|
177
180
|
},
|
|
181
|
+
'is empty value': ({event}) => {
|
|
182
|
+
return event.type === 'update value' && event.value === undefined
|
|
183
|
+
},
|
|
184
|
+
'is empty array': ({event}) => {
|
|
185
|
+
return (
|
|
186
|
+
event.type === 'update value' &&
|
|
187
|
+
Array.isArray(event.value) &&
|
|
188
|
+
event.value.length === 0
|
|
189
|
+
)
|
|
190
|
+
},
|
|
178
191
|
'is new value': ({context, event}) => {
|
|
179
192
|
return (
|
|
180
193
|
event.type === 'update value' && context.previousValue !== event.value
|
|
@@ -194,6 +207,7 @@ export const syncMachine = setup({
|
|
|
194
207
|
}).createMachine({
|
|
195
208
|
id: 'sync',
|
|
196
209
|
context: ({input}) => ({
|
|
210
|
+
initialValue: input.initialValue,
|
|
197
211
|
initialValueSynced: false,
|
|
198
212
|
isProcessingLocalChanges: false,
|
|
199
213
|
keyGenerator: input.keyGenerator,
|
|
@@ -203,6 +217,11 @@ export const syncMachine = setup({
|
|
|
203
217
|
pendingValue: undefined,
|
|
204
218
|
previousValue: undefined,
|
|
205
219
|
}),
|
|
220
|
+
entry: [
|
|
221
|
+
raise(({context}) => {
|
|
222
|
+
return {type: 'update value', value: context.initialValue}
|
|
223
|
+
}),
|
|
224
|
+
],
|
|
206
225
|
on: {
|
|
207
226
|
'has pending patches': {
|
|
208
227
|
actions: assign({
|
|
@@ -233,6 +252,18 @@ export const syncMachine = setup({
|
|
|
233
252
|
],
|
|
234
253
|
on: {
|
|
235
254
|
'update value': [
|
|
255
|
+
{
|
|
256
|
+
guard: and(['is empty value', not('initial value synced')]),
|
|
257
|
+
actions: ['assign initial value synced', 'emit done syncing value'],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
guard: and(['is empty array', not('initial value synced')]),
|
|
261
|
+
actions: [
|
|
262
|
+
'assign initial value synced',
|
|
263
|
+
emit({type: 'value changed', value: []}),
|
|
264
|
+
'emit done syncing value',
|
|
265
|
+
],
|
|
266
|
+
},
|
|
236
267
|
{
|
|
237
268
|
guard: and(['is busy', 'is new value']),
|
|
238
269
|
target: 'busy',
|
|
@@ -383,6 +414,7 @@ async function updateValue({
|
|
|
383
414
|
streamBlocks: boolean
|
|
384
415
|
value: PortableTextBlock[] | undefined
|
|
385
416
|
}) {
|
|
417
|
+
let doneSyncing = false
|
|
386
418
|
let isChanged = false
|
|
387
419
|
let isValid = true
|
|
388
420
|
|
|
@@ -394,6 +426,10 @@ async function updateValue({
|
|
|
394
426
|
Editor.withoutNormalizing(slateEditor, () => {
|
|
395
427
|
withoutSaving(slateEditor, () => {
|
|
396
428
|
withoutPatching(slateEditor, () => {
|
|
429
|
+
if (doneSyncing) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
397
433
|
if (hadSelection) {
|
|
398
434
|
Transforms.deselect(slateEditor)
|
|
399
435
|
}
|
|
@@ -428,6 +464,11 @@ async function updateValue({
|
|
|
428
464
|
Editor.withoutNormalizing(slateEditor, () => {
|
|
429
465
|
withRemoteChanges(slateEditor, () => {
|
|
430
466
|
withoutPatching(slateEditor, () => {
|
|
467
|
+
if (doneSyncing) {
|
|
468
|
+
resolve()
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
431
472
|
isChanged = removeExtraBlocks({
|
|
432
473
|
slateEditor,
|
|
433
474
|
slateValueFromProps,
|
|
@@ -465,6 +506,10 @@ async function updateValue({
|
|
|
465
506
|
Editor.withoutNormalizing(slateEditor, () => {
|
|
466
507
|
withRemoteChanges(slateEditor, () => {
|
|
467
508
|
withoutPatching(slateEditor, () => {
|
|
509
|
+
if (doneSyncing) {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
468
513
|
isChanged = removeExtraBlocks({
|
|
469
514
|
slateEditor,
|
|
470
515
|
slateValueFromProps,
|
|
@@ -494,6 +539,7 @@ async function updateValue({
|
|
|
494
539
|
|
|
495
540
|
if (!isValid) {
|
|
496
541
|
debug('Invalid value, returning')
|
|
542
|
+
doneSyncing = true
|
|
497
543
|
sendBack({type: 'done syncing', value})
|
|
498
544
|
return
|
|
499
545
|
}
|
|
@@ -509,6 +555,7 @@ async function updateValue({
|
|
|
509
555
|
resolution: null,
|
|
510
556
|
value,
|
|
511
557
|
})
|
|
558
|
+
doneSyncing = true
|
|
512
559
|
sendBack({type: 'done syncing', value})
|
|
513
560
|
return
|
|
514
561
|
}
|
|
@@ -524,6 +571,7 @@ async function updateValue({
|
|
|
524
571
|
debug('Server value and editor value is equal, no need to sync.')
|
|
525
572
|
}
|
|
526
573
|
|
|
574
|
+
doneSyncing = true
|
|
527
575
|
sendBack({type: 'done syncing', value})
|
|
528
576
|
}
|
|
529
577
|
|
package/src/editor.ts
CHANGED
|
@@ -41,7 +41,13 @@ export type EditorConfig = {
|
|
|
41
41
|
/**
|
|
42
42
|
* @public
|
|
43
43
|
*/
|
|
44
|
-
export type EditorEvent =
|
|
44
|
+
export type EditorEvent =
|
|
45
|
+
| ExternalEditorEvent
|
|
46
|
+
| ExternalBehaviorEvent
|
|
47
|
+
| {
|
|
48
|
+
type: 'update value'
|
|
49
|
+
value: Array<PortableTextBlock> | undefined
|
|
50
|
+
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
47
53
|
* @public
|
|
@@ -98,7 +98,9 @@ export function getTextSelection(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (!anchor || !focus) {
|
|
101
|
-
throw new Error(
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Unable to find selection for text "${text}" in value "${JSON.stringify(value)}"`,
|
|
103
|
+
)
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
return {
|