@portabletext/editor 1.15.3 → 1.16.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/behavior.core.cjs +25 -25
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs +14 -14
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/{selectors.cjs → selector.is-selection-collapsed.cjs} +8 -8
- package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +1 -0
- package/lib/_chunks-es/behavior.core.js +7 -7
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/selector.get-text-before.js +14 -14
- package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
- package/lib/_chunks-es/{selectors.js → selector.is-selection-collapsed.js} +8 -8
- package/lib/_chunks-es/selector.is-selection-collapsed.js.map +1 -0
- package/lib/behaviors/index.cjs +23 -23
- package/lib/behaviors/index.cjs.map +1 -1
- package/lib/behaviors/index.d.cts +1 -0
- package/lib/behaviors/index.d.ts +1 -0
- package/lib/behaviors/index.js +8 -8
- package/lib/behaviors/index.js.map +1 -1
- package/lib/index.cjs +855 -515
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +4126 -4834
- package/lib/index.d.ts +4126 -4834
- package/lib/index.js +852 -514
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs +166 -16
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +54 -5
- package/lib/selectors/index.d.ts +54 -5
- package/lib/selectors/index.js +154 -3
- package/lib/selectors/index.js.map +1 -1
- package/package.json +11 -11
- package/src/behaviors/behavior.code-editor.ts +5 -9
- package/src/behaviors/behavior.core.block-objects.ts +13 -19
- package/src/behaviors/behavior.core.lists.ts +11 -17
- package/src/behaviors/behavior.links.ts +4 -4
- package/src/behaviors/behavior.markdown.ts +16 -21
- package/src/editor/Editable.tsx +11 -4
- package/src/editor/PortableTextEditor.tsx +2 -3
- package/src/editor/{hooks/useSyncValue.test.tsx → __tests__/sync-value.test.tsx} +42 -23
- package/src/editor/components/Synchronizer.tsx +53 -80
- package/src/editor/editor-machine.ts +129 -80
- package/src/editor/editor-provider.tsx +0 -3
- package/src/editor/editor-selector.ts +5 -0
- package/src/editor/editor-snapshot.ts +1 -0
- package/src/editor/get-active-decorators.ts +20 -0
- package/src/editor/mutation-machine.ts +100 -0
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +21 -15
- package/src/editor/plugins/createWithMaxBlocks.ts +1 -1
- package/src/editor/plugins/createWithPatches.ts +0 -4
- package/src/editor/plugins/createWithPlaceholderBlock.ts +1 -1
- package/src/editor/plugins/createWithPortableTextSelections.ts +4 -1
- package/src/editor/plugins/createWithUndoRedo.ts +3 -3
- package/src/editor/sync-machine.ts +657 -0
- package/src/editor/withSyncRangeDecorations.ts +17 -5
- package/src/selectors/_exports/index.ts +1 -0
- package/src/selectors/index.ts +9 -1
- package/src/selectors/selector.get-active-style.ts +37 -0
- package/src/selectors/selector.get-selected-spans.ts +136 -0
- package/src/selectors/selector.is-active-annotation.ts +49 -0
- package/src/selectors/selector.is-active-decorator.ts +21 -0
- package/src/selectors/selector.is-active-list-item.ts +13 -0
- package/src/selectors/selector.is-active-style.ts +13 -0
- package/src/selectors/selector.is-selection-collapsed.ts +12 -0
- package/src/selectors/selector.is-selection-expanded.ts +9 -0
- package/src/selectors/selectors.ts +0 -11
- package/src/utils/weakMaps.ts +0 -3
- package/src/utils/withChanges.ts +1 -8
- package/lib/_chunks-cjs/selectors.cjs.map +0 -1
- package/lib/_chunks-es/selectors.js.map +0 -1
- package/src/editor/hooks/useSyncValue.ts +0 -426
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {useSelector} from '@xstate/react'
|
|
4
|
-
import {throttle} from 'lodash'
|
|
5
|
-
import {useCallback, useEffect, useRef} from 'react'
|
|
6
|
-
import {Editor} from 'slate'
|
|
1
|
+
import {useActorRef, useSelector} from '@xstate/react'
|
|
2
|
+
import {useEffect} from 'react'
|
|
7
3
|
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
8
4
|
import {debugWithName} from '../../utils/debug'
|
|
9
|
-
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
10
5
|
import type {EditorActor} from '../editor-machine'
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
6
|
+
import {mutationMachine} from '../mutation-machine'
|
|
7
|
+
import {syncMachine} from '../sync-machine'
|
|
13
8
|
|
|
14
9
|
const debug = debugWithName('component:PortableTextEditor:Synchronizer')
|
|
15
|
-
const debugVerbose = debug.enabled && false
|
|
16
|
-
|
|
17
|
-
// The editor will commit changes in a throttled fashion in order
|
|
18
|
-
// not to overload the network and degrade performance while typing.
|
|
19
|
-
const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
20
10
|
|
|
21
11
|
/**
|
|
22
12
|
* @internal
|
|
23
13
|
*/
|
|
24
14
|
export interface SynchronizerProps {
|
|
25
15
|
editorActor: EditorActor
|
|
26
|
-
getValue: () => Array<PortableTextBlock> | undefined
|
|
27
|
-
portableTextEditor: PortableTextEditor
|
|
28
16
|
slateEditor: PortableTextSlateEditor
|
|
29
17
|
}
|
|
30
18
|
|
|
@@ -33,90 +21,75 @@ export interface SynchronizerProps {
|
|
|
33
21
|
* @internal
|
|
34
22
|
*/
|
|
35
23
|
export function Synchronizer(props: SynchronizerProps) {
|
|
36
|
-
const
|
|
37
|
-
const value = useSelector(props.editorActor, (s) => s.context.value)
|
|
38
|
-
const {editorActor, getValue, portableTextEditor, slateEditor} = props
|
|
39
|
-
const pendingPatches = useRef<Patch[]>([])
|
|
24
|
+
const {editorActor, slateEditor} = props
|
|
40
25
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
const value = useSelector(props.editorActor, (s) => s.context.value)
|
|
27
|
+
const readOnly = useSelector(props.editorActor, (s) =>
|
|
28
|
+
s.matches({'edit mode': 'read only'}),
|
|
29
|
+
)
|
|
30
|
+
const syncActorRef = useActorRef(syncMachine, {
|
|
31
|
+
input: {
|
|
32
|
+
keyGenerator: props.editorActor.getSnapshot().context.keyGenerator,
|
|
33
|
+
readOnly: props.editorActor
|
|
34
|
+
.getSnapshot()
|
|
35
|
+
.matches({'edit mode': 'read only'}),
|
|
36
|
+
schema: props.editorActor.getSnapshot().context.schema,
|
|
37
|
+
slateEditor,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
const mutationActorRef = useActorRef(mutationMachine, {
|
|
41
|
+
input: {
|
|
42
|
+
schema: editorActor.getSnapshot().context.schema,
|
|
43
|
+
slateEditor,
|
|
44
|
+
},
|
|
46
45
|
})
|
|
47
46
|
|
|
48
47
|
useEffect(() => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
debug(`Patches:\n${JSON.stringify(pendingPatches.current, null, 2)}`)
|
|
48
|
+
const subscription = mutationActorRef.on('*', (event) => {
|
|
49
|
+
if (event.type === 'has pending patches') {
|
|
50
|
+
syncActorRef.send({type: 'has pending patches'})
|
|
51
|
+
}
|
|
52
|
+
if (event.type === 'mutation') {
|
|
53
|
+
syncActorRef.send({type: 'mutation'})
|
|
54
|
+
editorActor.send(event)
|
|
57
55
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
snapshot,
|
|
63
|
-
})
|
|
64
|
-
pendingPatches.current = []
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
subscription.unsubscribe()
|
|
65
60
|
}
|
|
66
|
-
|
|
67
|
-
}, [editorActor, slateEditor, getValue])
|
|
61
|
+
}, [mutationActorRef, syncActorRef, editorActor])
|
|
68
62
|
|
|
69
|
-
// Flush pending patches immediately on unmount
|
|
70
63
|
useEffect(() => {
|
|
64
|
+
const subscription = syncActorRef.on('*', (event) => {
|
|
65
|
+
props.editorActor.send(event)
|
|
66
|
+
})
|
|
67
|
+
|
|
71
68
|
return () => {
|
|
72
|
-
|
|
69
|
+
subscription.unsubscribe()
|
|
73
70
|
}
|
|
74
|
-
}, [
|
|
71
|
+
}, [props.editorActor, syncActorRef])
|
|
75
72
|
|
|
76
|
-
// Subscribe to, and handle changes from the editor
|
|
77
73
|
useEffect(() => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// If the editor is normalizing (each operation) it means that it's not in the middle of a bigger transform,
|
|
81
|
-
// and we can flush these changes immediately.
|
|
82
|
-
if (Editor.isNormalizing(slateEditor)) {
|
|
83
|
-
onFlushPendingPatches()
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
// If it's in the middle of something, try again.
|
|
87
|
-
onFlushPendingPatchesThrottled()
|
|
88
|
-
},
|
|
89
|
-
FLUSH_PATCHES_THROTTLED_MS,
|
|
90
|
-
{
|
|
91
|
-
leading: false,
|
|
92
|
-
trailing: true,
|
|
93
|
-
},
|
|
94
|
-
)
|
|
74
|
+
syncActorRef.send({type: 'toggle readOnly'})
|
|
75
|
+
}, [syncActorRef, readOnly])
|
|
95
76
|
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
debug('Value from props changed, syncing new value')
|
|
79
|
+
syncActorRef.send({type: 'update value', value})
|
|
80
|
+
}, [syncActorRef, value])
|
|
81
|
+
|
|
82
|
+
// Subscribe to, and handle changes from the editor
|
|
83
|
+
useEffect(() => {
|
|
96
84
|
debug('Subscribing to patch events')
|
|
97
85
|
const sub = editorActor.on('patch', (event) => {
|
|
98
|
-
|
|
99
|
-
pendingPatches.current.push(event.patch)
|
|
100
|
-
onFlushPendingPatchesThrottled()
|
|
86
|
+
mutationActorRef.send(event)
|
|
101
87
|
})
|
|
102
88
|
return () => {
|
|
103
89
|
debug('Unsubscribing to patch events')
|
|
104
90
|
sub.unsubscribe()
|
|
105
91
|
}
|
|
106
|
-
}, [editorActor,
|
|
107
|
-
|
|
108
|
-
// This hook must be set up after setting up the subscription above, or it will not pick up validation errors from the useSyncValue hook.
|
|
109
|
-
// This will cause the editor to not be able to signal a validation error and offer invalid value resolution of the initial value.
|
|
110
|
-
const isInitialValueFromProps = useRef(true)
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
debug('Value from props changed, syncing new value')
|
|
113
|
-
syncValue(value)
|
|
114
|
-
// Signal that we have our first value, and are ready to roll.
|
|
115
|
-
if (isInitialValueFromProps.current) {
|
|
116
|
-
editorActor.send({type: 'ready'})
|
|
117
|
-
isInitialValueFromProps.current = false
|
|
118
|
-
}
|
|
119
|
-
}, [editorActor, syncValue, value])
|
|
92
|
+
}, [editorActor, mutationActorRef, slateEditor])
|
|
120
93
|
|
|
121
94
|
return null
|
|
122
95
|
}
|
|
@@ -31,6 +31,7 @@ import {fromSlateValue} from '../utils/values'
|
|
|
31
31
|
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
32
32
|
import type {EditorSchema} from './define-schema'
|
|
33
33
|
import type {EditorContext} from './editor-snapshot'
|
|
34
|
+
import {getActiveDecorators} from './get-active-decorators'
|
|
34
35
|
|
|
35
36
|
export * from 'xstate/guards'
|
|
36
37
|
|
|
@@ -68,6 +69,7 @@ export type MutationEvent = {
|
|
|
68
69
|
export type InternalEditorEvent =
|
|
69
70
|
| {type: 'normalizing'}
|
|
70
71
|
| {type: 'done normalizing'}
|
|
72
|
+
| {type: 'done syncing'}
|
|
71
73
|
| {
|
|
72
74
|
type: 'behavior event'
|
|
73
75
|
behaviorEvent: SyntheticBehaviorEvent | NativeBehaviorEvent
|
|
@@ -98,7 +100,7 @@ export type InternalEditorEvent =
|
|
|
98
100
|
type: 'update maxBlocks'
|
|
99
101
|
maxBlocks: number | undefined
|
|
100
102
|
}
|
|
101
|
-
| OmitFromUnion<InternalEditorEmittedEvent, 'type', '
|
|
103
|
+
| OmitFromUnion<InternalEditorEmittedEvent, 'type', 'read only' | 'editable'>
|
|
102
104
|
|
|
103
105
|
/**
|
|
104
106
|
* @alpha
|
|
@@ -108,13 +110,14 @@ export type EditorEmittedEvent = PickFromUnion<
|
|
|
108
110
|
'type',
|
|
109
111
|
| 'blurred'
|
|
110
112
|
| 'done loading'
|
|
113
|
+
| 'editable'
|
|
111
114
|
| 'error'
|
|
112
115
|
| 'focused'
|
|
113
116
|
| 'invalid value'
|
|
114
117
|
| 'loading'
|
|
115
118
|
| 'mutation'
|
|
116
119
|
| 'patch'
|
|
117
|
-
| '
|
|
120
|
+
| 'read only'
|
|
118
121
|
| 'ready'
|
|
119
122
|
| 'selection'
|
|
120
123
|
| 'value changed'
|
|
@@ -152,7 +155,8 @@ export type InternalEditorEmittedEvent =
|
|
|
152
155
|
| {type: 'focused'; event: FocusEvent<HTMLDivElement, Element>}
|
|
153
156
|
| {type: 'loading'}
|
|
154
157
|
| {type: 'done loading'}
|
|
155
|
-
| {type: '
|
|
158
|
+
| {type: 'read only'}
|
|
159
|
+
| {type: 'editable'}
|
|
156
160
|
| PickFromUnion<
|
|
157
161
|
SyntheticBehaviorEvent,
|
|
158
162
|
'type',
|
|
@@ -180,7 +184,7 @@ export const editorMachine = setup({
|
|
|
180
184
|
keyGenerator: () => string
|
|
181
185
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
182
186
|
schema: EditorSchema
|
|
183
|
-
|
|
187
|
+
initialReadOnly: boolean
|
|
184
188
|
maxBlocks: number | undefined
|
|
185
189
|
selection: EditorSelection
|
|
186
190
|
value: Array<PortableTextBlock> | undefined
|
|
@@ -217,6 +221,8 @@ export const editorMachine = setup({
|
|
|
217
221
|
assertEvent(event, 'mutation')
|
|
218
222
|
return event
|
|
219
223
|
}),
|
|
224
|
+
'emit read only': emit({type: 'read only'}),
|
|
225
|
+
'emit editable': emit({type: 'editable'}),
|
|
220
226
|
'defer event': assign({
|
|
221
227
|
pendingEvents: ({context, event}) => {
|
|
222
228
|
assertEvent(event, ['patch', 'mutation'])
|
|
@@ -228,6 +234,7 @@ export const editorMachine = setup({
|
|
|
228
234
|
enqueue(emit(event))
|
|
229
235
|
}
|
|
230
236
|
}),
|
|
237
|
+
'emit ready': emit({type: 'ready'}),
|
|
231
238
|
'clear pending events': assign({
|
|
232
239
|
pendingEvents: [],
|
|
233
240
|
}),
|
|
@@ -276,6 +283,10 @@ export const editorMachine = setup({
|
|
|
276
283
|
)
|
|
277
284
|
|
|
278
285
|
const editorContext = {
|
|
286
|
+
activeDecorators: getActiveDecorators({
|
|
287
|
+
schema: context.schema,
|
|
288
|
+
slateEditorInstance: event.editor,
|
|
289
|
+
}),
|
|
279
290
|
keyGenerator: context.keyGenerator,
|
|
280
291
|
schema: context.schema,
|
|
281
292
|
selection,
|
|
@@ -345,48 +356,11 @@ export const editorMachine = setup({
|
|
|
345
356
|
pendingEvents: [],
|
|
346
357
|
schema: input.schema,
|
|
347
358
|
selection: null,
|
|
348
|
-
|
|
359
|
+
initialReadOnly: input.readOnly ?? false,
|
|
349
360
|
maxBlocks: input.maxBlocks,
|
|
350
361
|
value: input.value,
|
|
351
362
|
}),
|
|
352
363
|
on: {
|
|
353
|
-
'annotation.add': {
|
|
354
|
-
actions: emit(({event}) => event),
|
|
355
|
-
guard: ({context}) => !context.readOnly,
|
|
356
|
-
},
|
|
357
|
-
'annotation.remove': {
|
|
358
|
-
actions: emit(({event}) => event),
|
|
359
|
-
guard: ({context}) => !context.readOnly,
|
|
360
|
-
},
|
|
361
|
-
'annotation.toggle': {
|
|
362
|
-
actions: emit(({event}) => event),
|
|
363
|
-
guard: ({context}) => !context.readOnly,
|
|
364
|
-
},
|
|
365
|
-
'blur': {
|
|
366
|
-
actions: emit(({event}) => event),
|
|
367
|
-
guard: ({context}) => !context.readOnly,
|
|
368
|
-
},
|
|
369
|
-
'decorator.*': {
|
|
370
|
-
actions: emit(({event}) => event),
|
|
371
|
-
guard: ({context}) => !context.readOnly,
|
|
372
|
-
},
|
|
373
|
-
'focus': {
|
|
374
|
-
actions: emit(({event}) => event),
|
|
375
|
-
guard: ({context}) => !context.readOnly,
|
|
376
|
-
},
|
|
377
|
-
'insert.*': {
|
|
378
|
-
actions: emit(({event}) => event),
|
|
379
|
-
guard: ({context}) => !context.readOnly,
|
|
380
|
-
},
|
|
381
|
-
'list item.*': {
|
|
382
|
-
actions: emit(({event}) => event),
|
|
383
|
-
guard: ({context}) => !context.readOnly,
|
|
384
|
-
},
|
|
385
|
-
'style.*': {
|
|
386
|
-
actions: emit(({event}) => event),
|
|
387
|
-
guard: ({context}) => !context.readOnly,
|
|
388
|
-
},
|
|
389
|
-
'ready': {actions: emit(({event}) => event)},
|
|
390
364
|
'unset': {actions: emit(({event}) => event)},
|
|
391
365
|
'value changed': {actions: emit(({event}) => event)},
|
|
392
366
|
'invalid value': {actions: emit(({event}) => event)},
|
|
@@ -405,22 +379,9 @@ export const editorMachine = setup({
|
|
|
405
379
|
'update behaviors': {actions: 'assign behaviors'},
|
|
406
380
|
'update schema': {actions: 'assign schema'},
|
|
407
381
|
'update value': {actions: assign({value: ({event}) => event.value})},
|
|
408
|
-
'toggle readOnly': {
|
|
409
|
-
actions: [
|
|
410
|
-
assign({readOnly: ({context}) => !context.readOnly}),
|
|
411
|
-
emit(({context}) => ({
|
|
412
|
-
type: 'readOnly toggled',
|
|
413
|
-
readOnly: context.readOnly,
|
|
414
|
-
})),
|
|
415
|
-
],
|
|
416
|
-
},
|
|
417
382
|
'update maxBlocks': {
|
|
418
383
|
actions: assign({maxBlocks: ({event}) => event.maxBlocks}),
|
|
419
384
|
},
|
|
420
|
-
'behavior event': {
|
|
421
|
-
actions: 'handle behavior event',
|
|
422
|
-
guard: ({context}) => !context.readOnly,
|
|
423
|
-
},
|
|
424
385
|
'behavior action intends': {
|
|
425
386
|
actions: [
|
|
426
387
|
({context, event}) => {
|
|
@@ -455,49 +416,137 @@ export const editorMachine = setup({
|
|
|
455
416
|
],
|
|
456
417
|
},
|
|
457
418
|
},
|
|
458
|
-
|
|
419
|
+
type: 'parallel',
|
|
459
420
|
states: {
|
|
460
|
-
|
|
461
|
-
initial: '
|
|
421
|
+
'edit mode': {
|
|
422
|
+
initial: 'read only',
|
|
462
423
|
states: {
|
|
463
|
-
|
|
424
|
+
'read only': {
|
|
425
|
+
initial: 'determine initial edit mode',
|
|
426
|
+
states: {
|
|
427
|
+
'determine initial edit mode': {
|
|
428
|
+
on: {
|
|
429
|
+
'done syncing': [
|
|
430
|
+
{
|
|
431
|
+
target: '#editor.edit mode.read only.read only',
|
|
432
|
+
guard: ({context}) => context.initialReadOnly,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
target: '#editor.edit mode.editable',
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
'read only': {
|
|
441
|
+
on: {
|
|
442
|
+
'toggle readOnly': {
|
|
443
|
+
target: '#editor.edit mode.editable',
|
|
444
|
+
actions: ['emit editable'],
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
'editable': {
|
|
464
451
|
on: {
|
|
465
|
-
|
|
466
|
-
target: '
|
|
452
|
+
'toggle readOnly': {
|
|
453
|
+
target: '#editor.edit mode.read only.read only',
|
|
454
|
+
actions: ['emit read only'],
|
|
467
455
|
},
|
|
468
|
-
|
|
469
|
-
actions: '
|
|
470
|
-
target: '#editor.dirty',
|
|
456
|
+
'behavior event': {
|
|
457
|
+
actions: 'handle behavior event',
|
|
471
458
|
},
|
|
472
|
-
|
|
473
|
-
actions:
|
|
474
|
-
|
|
459
|
+
'annotation.add': {
|
|
460
|
+
actions: emit(({event}) => event),
|
|
461
|
+
},
|
|
462
|
+
'annotation.remove': {
|
|
463
|
+
actions: emit(({event}) => event),
|
|
464
|
+
},
|
|
465
|
+
'annotation.toggle': {
|
|
466
|
+
actions: emit(({event}) => event),
|
|
467
|
+
},
|
|
468
|
+
'blur': {
|
|
469
|
+
actions: emit(({event}) => event),
|
|
470
|
+
},
|
|
471
|
+
'decorator.*': {
|
|
472
|
+
actions: emit(({event}) => event),
|
|
473
|
+
},
|
|
474
|
+
'focus': {
|
|
475
|
+
actions: emit(({event}) => event),
|
|
476
|
+
},
|
|
477
|
+
'insert.*': {
|
|
478
|
+
actions: emit(({event}) => event),
|
|
479
|
+
},
|
|
480
|
+
'list item.*': {
|
|
481
|
+
actions: emit(({event}) => event),
|
|
482
|
+
},
|
|
483
|
+
'style.*': {
|
|
484
|
+
actions: emit(({event}) => event),
|
|
475
485
|
},
|
|
476
486
|
},
|
|
477
487
|
},
|
|
478
|
-
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
'setup': {
|
|
491
|
+
initial: 'setting up',
|
|
492
|
+
states: {
|
|
493
|
+
'setting up': {
|
|
494
|
+
exit: ['emit ready'],
|
|
479
495
|
on: {
|
|
480
|
-
'done normalizing': {
|
|
481
|
-
target: 'idle',
|
|
482
|
-
},
|
|
483
496
|
'patch': {
|
|
484
497
|
actions: 'defer event',
|
|
485
498
|
},
|
|
486
499
|
'mutation': {
|
|
487
500
|
actions: 'defer event',
|
|
488
501
|
},
|
|
502
|
+
'done syncing': {
|
|
503
|
+
target: 'pristine',
|
|
504
|
+
},
|
|
489
505
|
},
|
|
490
506
|
},
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
507
|
+
'pristine': {
|
|
508
|
+
initial: 'idle',
|
|
509
|
+
states: {
|
|
510
|
+
idle: {
|
|
511
|
+
on: {
|
|
512
|
+
normalizing: {
|
|
513
|
+
target: 'normalizing',
|
|
514
|
+
},
|
|
515
|
+
patch: {
|
|
516
|
+
actions: 'defer event',
|
|
517
|
+
target: '#editor.setup.dirty',
|
|
518
|
+
},
|
|
519
|
+
mutation: {
|
|
520
|
+
actions: 'defer event',
|
|
521
|
+
target: '#editor.setup.dirty',
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
normalizing: {
|
|
526
|
+
on: {
|
|
527
|
+
'done normalizing': {
|
|
528
|
+
target: 'idle',
|
|
529
|
+
},
|
|
530
|
+
'patch': {
|
|
531
|
+
actions: 'defer event',
|
|
532
|
+
},
|
|
533
|
+
'mutation': {
|
|
534
|
+
actions: 'defer event',
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
498
539
|
},
|
|
499
|
-
|
|
500
|
-
|
|
540
|
+
'dirty': {
|
|
541
|
+
entry: ['emit pending events', 'clear pending events'],
|
|
542
|
+
on: {
|
|
543
|
+
patch: {
|
|
544
|
+
actions: 'emit patch event',
|
|
545
|
+
},
|
|
546
|
+
mutation: {
|
|
547
|
+
actions: 'emit mutation event',
|
|
548
|
+
},
|
|
549
|
+
},
|
|
501
550
|
},
|
|
502
551
|
},
|
|
503
552
|
},
|
|
@@ -28,7 +28,6 @@ export function EditorProvider(props: EditorProviderProps) {
|
|
|
28
28
|
const editor = useCreateEditor(props.initialConfig)
|
|
29
29
|
const editorActor = editor._internal.editorActor
|
|
30
30
|
const slateEditor = editor._internal.slateEditor
|
|
31
|
-
const editable = editor._internal.editable
|
|
32
31
|
const portableTextEditor = useMemo(
|
|
33
32
|
() =>
|
|
34
33
|
new PortableTextEditor({
|
|
@@ -47,8 +46,6 @@ export function EditorProvider(props: EditorProviderProps) {
|
|
|
47
46
|
/>
|
|
48
47
|
<Synchronizer
|
|
49
48
|
editorActor={editorActor}
|
|
50
|
-
getValue={editable.getValue}
|
|
51
|
-
portableTextEditor={portableTextEditor}
|
|
52
49
|
slateEditor={slateEditor.instance}
|
|
53
50
|
/>
|
|
54
51
|
<EditorActorContext.Provider value={editorActor}>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {useSelector} from '@xstate/react'
|
|
2
2
|
import type {Editor} from './create-editor'
|
|
3
3
|
import type {EditorSnapshot} from './editor-snapshot'
|
|
4
|
+
import {getActiveDecorators} from './get-active-decorators'
|
|
4
5
|
import {getValue} from './get-value'
|
|
5
6
|
|
|
6
7
|
function defaultCompare<T>(a: T, b: T) {
|
|
@@ -24,6 +25,10 @@ export function useEditorSelector<TSelected>(
|
|
|
24
25
|
editor._internal.editorActor,
|
|
25
26
|
(snapshot) => {
|
|
26
27
|
const context = {
|
|
28
|
+
activeDecorators: getActiveDecorators({
|
|
29
|
+
schema: snapshot.context.schema,
|
|
30
|
+
slateEditorInstance: editor._internal.slateEditor.instance,
|
|
31
|
+
}),
|
|
27
32
|
keyGenerator: snapshot.context.keyGenerator,
|
|
28
33
|
schema: snapshot.context.schema,
|
|
29
34
|
selection: snapshot.context.selection,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {Editor} from 'slate'
|
|
2
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
3
|
+
import type {EditorSchema} from './define-schema'
|
|
4
|
+
|
|
5
|
+
export function getActiveDecorators({
|
|
6
|
+
schema,
|
|
7
|
+
slateEditorInstance,
|
|
8
|
+
}: {
|
|
9
|
+
schema: EditorSchema
|
|
10
|
+
slateEditorInstance: PortableTextSlateEditor
|
|
11
|
+
}) {
|
|
12
|
+
const decorators = schema.decorators.map((decorator) => decorator.value)
|
|
13
|
+
|
|
14
|
+
const marks =
|
|
15
|
+
{
|
|
16
|
+
...(Editor.marks(slateEditorInstance) ?? {}),
|
|
17
|
+
}.marks ?? []
|
|
18
|
+
|
|
19
|
+
return marks.filter((mark) => decorators.includes(mark))
|
|
20
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {Patch} from '@portabletext/patches'
|
|
2
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
3
|
+
import {Editor} from 'slate'
|
|
4
|
+
import {assign, emit, setup} from 'xstate'
|
|
5
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
6
|
+
import {fromSlateValue} from '../utils/values'
|
|
7
|
+
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
8
|
+
import type {EditorSchema} from './define-schema'
|
|
9
|
+
|
|
10
|
+
const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Makes sure editor mutation events are debounced
|
|
14
|
+
*/
|
|
15
|
+
export const mutationMachine = setup({
|
|
16
|
+
types: {
|
|
17
|
+
context: {} as {
|
|
18
|
+
pendingPatches: Array<Patch>
|
|
19
|
+
schema: EditorSchema
|
|
20
|
+
slateEditor: PortableTextSlateEditor
|
|
21
|
+
},
|
|
22
|
+
events: {} as {type: 'patch'; patch: Patch},
|
|
23
|
+
input: {} as {
|
|
24
|
+
schema: EditorSchema
|
|
25
|
+
slateEditor: PortableTextSlateEditor
|
|
26
|
+
},
|
|
27
|
+
emitted: {} as
|
|
28
|
+
| {
|
|
29
|
+
type: 'has pending patches'
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: 'mutation'
|
|
33
|
+
patches: Array<Patch>
|
|
34
|
+
snapshot: Array<PortableTextBlock> | undefined
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
actions: {
|
|
38
|
+
'emit has pending patches': emit({type: 'has pending patches'}),
|
|
39
|
+
'emit mutation': emit(({context}) => ({
|
|
40
|
+
type: 'mutation' as const,
|
|
41
|
+
patches: context.pendingPatches,
|
|
42
|
+
snapshot: fromSlateValue(
|
|
43
|
+
context.slateEditor.children,
|
|
44
|
+
context.schema.block.name,
|
|
45
|
+
KEY_TO_VALUE_ELEMENT.get(context.slateEditor),
|
|
46
|
+
),
|
|
47
|
+
})),
|
|
48
|
+
'clear pending patches': assign({
|
|
49
|
+
pendingPatches: [],
|
|
50
|
+
}),
|
|
51
|
+
'defer patch': assign({
|
|
52
|
+
pendingPatches: ({context, event}) => [
|
|
53
|
+
...context.pendingPatches,
|
|
54
|
+
event.patch,
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
guards: {
|
|
59
|
+
'slate is normalizing': ({context}) =>
|
|
60
|
+
Editor.isNormalizing(context.slateEditor),
|
|
61
|
+
},
|
|
62
|
+
}).createMachine({
|
|
63
|
+
id: 'mutation',
|
|
64
|
+
context: ({input}) => ({
|
|
65
|
+
pendingPatches: [],
|
|
66
|
+
schema: input.schema,
|
|
67
|
+
slateEditor: input.slateEditor,
|
|
68
|
+
}),
|
|
69
|
+
initial: 'idle',
|
|
70
|
+
states: {
|
|
71
|
+
'idle': {
|
|
72
|
+
on: {
|
|
73
|
+
patch: {
|
|
74
|
+
actions: ['defer patch', 'emit has pending patches'],
|
|
75
|
+
target: 'has pending patches',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
'has pending patches': {
|
|
80
|
+
after: {
|
|
81
|
+
[FLUSH_PATCHES_THROTTLED_MS]: [
|
|
82
|
+
{
|
|
83
|
+
guard: 'slate is normalizing',
|
|
84
|
+
target: 'idle',
|
|
85
|
+
actions: ['emit mutation', 'clear pending patches'],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
reenter: true,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
on: {
|
|
93
|
+
patch: {
|
|
94
|
+
actions: ['defer patch'],
|
|
95
|
+
reenter: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
})
|