@portabletext/editor 1.10.2 → 1.11.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/README.md +21 -27
- package/lib/index.d.mts +531 -206
- package/lib/index.d.ts +531 -206
- package/lib/index.esm.js +415 -367
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +458 -410
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +415 -367
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -10
- package/src/editor/Editable.tsx +1 -1
- package/src/editor/PortableTextEditor.tsx +122 -95
- package/src/editor/behavior/behavior.types.ts +9 -0
- package/src/editor/components/Synchronizer.tsx +13 -68
- package/src/editor/create-slate-editor.tsx +2 -14
- package/src/editor/editor-event-listener.tsx +24 -0
- package/src/editor/editor-machine.ts +34 -4
- package/src/editor/editor-provider.tsx +81 -0
- package/src/editor/hooks/useSyncValue.ts +2 -3
- package/src/editor/plugins/with-plugins.ts +0 -27
- package/src/editor/use-editor.ts +89 -20
- package/src/index.ts +12 -4
- package/src/types/editor.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@portabletext/toolkit": "^2.0.16",
|
|
61
|
-
"@sanity/block-tools": "^3.64.
|
|
61
|
+
"@sanity/block-tools": "^3.64.2",
|
|
62
62
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
63
|
-
"@sanity/pkg-utils": "^6.11.
|
|
64
|
-
"@sanity/schema": "^3.64.
|
|
65
|
-
"@sanity/types": "^3.64.
|
|
63
|
+
"@sanity/pkg-utils": "^6.11.12",
|
|
64
|
+
"@sanity/schema": "^3.64.2",
|
|
65
|
+
"@sanity/types": "^3.64.2",
|
|
66
66
|
"@testing-library/jest-dom": "^6.6.3",
|
|
67
67
|
"@testing-library/react": "^16.0.1",
|
|
68
68
|
"@types/debug": "^4.1.5",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"@types/lodash.startcase": "^4.4.9",
|
|
71
71
|
"@types/react": "^18.3.12",
|
|
72
72
|
"@types/react-dom": "^18.3.1",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
74
|
-
"@typescript-eslint/parser": "^8.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
74
|
+
"@typescript-eslint/parser": "^8.15.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.3.3",
|
|
76
76
|
"@vitest/browser": "^2.1.5",
|
|
77
77
|
"babel-plugin-react-compiler": "19.0.0-beta-0dec889-20241115",
|
|
@@ -90,9 +90,9 @@
|
|
|
90
90
|
"@sanity/gherkin-driver": "^0.0.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"@sanity/block-tools": "^3.64.
|
|
94
|
-
"@sanity/schema": "^3.64.
|
|
95
|
-
"@sanity/types": "^3.64.
|
|
93
|
+
"@sanity/block-tools": "^3.64.2",
|
|
94
|
+
"@sanity/schema": "^3.64.2",
|
|
95
|
+
"@sanity/types": "^3.64.2",
|
|
96
96
|
"react": "^16.9 || ^17 || ^18",
|
|
97
97
|
"rxjs": "^7.8.1",
|
|
98
98
|
"styled-components": "^6.1.13"
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -160,7 +160,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
160
160
|
|
|
161
161
|
const editorActor = useContext(EditorActorContext)
|
|
162
162
|
const readOnly = useSelector(editorActor, (s) => s.context.readOnly)
|
|
163
|
-
const
|
|
163
|
+
const schemaTypes = useSelector(editorActor, (s) => s.context.schema)
|
|
164
164
|
const slateEditor = useSlate()
|
|
165
165
|
|
|
166
166
|
const blockTypeName = schemaTypes.block.name
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from 'react'
|
|
15
15
|
import {Subject} from 'rxjs'
|
|
16
16
|
import {Slate} from 'slate-react'
|
|
17
|
-
import {
|
|
17
|
+
import {useEffectEvent} from 'use-effect-event'
|
|
18
18
|
import type {
|
|
19
19
|
EditableAPI,
|
|
20
20
|
EditableAPIDeleteOptions,
|
|
@@ -28,17 +28,13 @@ import {debugWithName} from '../utils/debug'
|
|
|
28
28
|
import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
|
|
29
29
|
import {compileType} from '../utils/schema'
|
|
30
30
|
import {Synchronizer} from './components/Synchronizer'
|
|
31
|
-
import {createSlateEditor, type SlateEditor} from './create-slate-editor'
|
|
32
31
|
import {EditorActorContext} from './editor-actor-context'
|
|
33
|
-
import
|
|
32
|
+
import type {EditorActor} from './editor-machine'
|
|
34
33
|
import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
35
34
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
36
35
|
import {defaultKeyGenerator} from './key-generator'
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
type AddedAnnotationPaths,
|
|
40
|
-
} from './plugins/createWithEditableAPI'
|
|
41
|
-
import type {Editor} from './use-editor'
|
|
36
|
+
import type {AddedAnnotationPaths} from './plugins/createWithEditableAPI'
|
|
37
|
+
import {createEditor, type Editor} from './use-editor'
|
|
42
38
|
|
|
43
39
|
const debug = debugWithName('component:PortableTextEditor')
|
|
44
40
|
|
|
@@ -124,71 +120,35 @@ export class PortableTextEditor extends Component<
|
|
|
124
120
|
*/
|
|
125
121
|
public schemaTypes: PortableTextMemberSchemaTypes
|
|
126
122
|
/**
|
|
123
|
+
* The editor instance
|
|
124
|
+
*/
|
|
125
|
+
private editor: Editor
|
|
126
|
+
/*
|
|
127
127
|
* The editor API (currently implemented with Slate).
|
|
128
128
|
*/
|
|
129
|
-
private editable
|
|
130
|
-
private editorActor: EditorActor
|
|
131
|
-
private slateEditor: SlateEditor
|
|
129
|
+
private editable: EditableAPI
|
|
132
130
|
|
|
133
131
|
constructor(props: PortableTextEditorProps) {
|
|
134
132
|
super(props)
|
|
135
133
|
|
|
136
134
|
if (props.editor) {
|
|
137
|
-
|
|
138
|
-
this.editorActor = editor._internal.editorActor
|
|
139
|
-
this.slateEditor = editor._internal.slateEditor
|
|
140
|
-
this.editorActor.start()
|
|
141
|
-
this.schemaTypes = this.editorActor.getSnapshot().context.schema
|
|
135
|
+
this.editor = props.editor as Editor
|
|
142
136
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
154
|
-
props.schemaType.hasOwnProperty('jsonType')
|
|
155
|
-
? props.schemaType
|
|
156
|
-
: compileType(props.schemaType),
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
this.editorActor = createActor(editorMachine, {
|
|
160
|
-
input: {
|
|
161
|
-
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
162
|
-
schema: this.schemaTypes,
|
|
163
|
-
value: props.value,
|
|
164
|
-
},
|
|
165
|
-
})
|
|
166
|
-
this.editorActor.start()
|
|
167
|
-
|
|
168
|
-
this.slateEditor = createSlateEditor({
|
|
169
|
-
editorActor: this.editorActor,
|
|
137
|
+
this.editor = createEditor({
|
|
138
|
+
keyGenerator: props.keyGenerator ?? defaultKeyGenerator,
|
|
139
|
+
schema: props.schemaType,
|
|
140
|
+
initialValue: props.value,
|
|
141
|
+
maxBlocks:
|
|
142
|
+
props.maxBlocks === undefined
|
|
143
|
+
? undefined
|
|
144
|
+
: Number.parseInt(props.maxBlocks.toString(), 10),
|
|
145
|
+
readOnly: props.readOnly,
|
|
170
146
|
})
|
|
171
|
-
|
|
172
|
-
if (props.readOnly) {
|
|
173
|
-
this.editorActor.send({
|
|
174
|
-
type: 'toggle readOnly',
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (props.maxBlocks) {
|
|
179
|
-
this.editorActor.send({
|
|
180
|
-
type: 'update maxBlocks',
|
|
181
|
-
maxBlocks:
|
|
182
|
-
props.maxBlocks === undefined
|
|
183
|
-
? undefined
|
|
184
|
-
: Number.parseInt(props.maxBlocks.toString(), 10),
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
147
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.editorActor
|
|
191
|
-
|
|
148
|
+
|
|
149
|
+
this.schemaTypes =
|
|
150
|
+
this.editor._internal.editorActor.getSnapshot().context.schema
|
|
151
|
+
this.editable = this.editor.editable
|
|
192
152
|
}
|
|
193
153
|
|
|
194
154
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
@@ -204,7 +164,7 @@ export class PortableTextEditor extends Component<
|
|
|
204
164
|
: compileType(this.props.schemaType),
|
|
205
165
|
)
|
|
206
166
|
|
|
207
|
-
this.editorActor.send({
|
|
167
|
+
this.editor._internal.editorActor.send({
|
|
208
168
|
type: 'update schema',
|
|
209
169
|
schema: this.schemaTypes,
|
|
210
170
|
})
|
|
@@ -212,13 +172,13 @@ export class PortableTextEditor extends Component<
|
|
|
212
172
|
|
|
213
173
|
if (!this.props.editor && !prevProps.editor) {
|
|
214
174
|
if (this.props.readOnly !== prevProps.readOnly) {
|
|
215
|
-
this.editorActor.send({
|
|
175
|
+
this.editor._internal.editorActor.send({
|
|
216
176
|
type: 'toggle readOnly',
|
|
217
177
|
})
|
|
218
178
|
}
|
|
219
179
|
|
|
220
180
|
if (this.props.maxBlocks !== prevProps.maxBlocks) {
|
|
221
|
-
this.editorActor.send({
|
|
181
|
+
this.editor._internal.editorActor.send({
|
|
222
182
|
type: 'update maxBlocks',
|
|
223
183
|
maxBlocks:
|
|
224
184
|
this.props.maxBlocks === undefined
|
|
@@ -228,7 +188,7 @@ export class PortableTextEditor extends Component<
|
|
|
228
188
|
}
|
|
229
189
|
|
|
230
190
|
if (this.props.value !== prevProps.value) {
|
|
231
|
-
this.editorActor.send({
|
|
191
|
+
this.editor._internal.editorActor.send({
|
|
232
192
|
type: 'update value',
|
|
233
193
|
value: this.props.value,
|
|
234
194
|
})
|
|
@@ -244,15 +204,7 @@ export class PortableTextEditor extends Component<
|
|
|
244
204
|
}
|
|
245
205
|
|
|
246
206
|
public setEditable = (editable: EditableAPI) => {
|
|
247
|
-
this.editable = {...this.editable, ...editable}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private getValue = () => {
|
|
251
|
-
if (this.editable) {
|
|
252
|
-
return this.editable.getValue()
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return undefined
|
|
207
|
+
this.editor.editable = {...this.editor.editable, ...editable}
|
|
256
208
|
}
|
|
257
209
|
|
|
258
210
|
render() {
|
|
@@ -264,33 +216,38 @@ export class PortableTextEditor extends Component<
|
|
|
264
216
|
<>
|
|
265
217
|
{legacyPatches ? (
|
|
266
218
|
<RoutePatchesObservableToEditorActor
|
|
267
|
-
editorActor={this.editorActor}
|
|
219
|
+
editorActor={this.editor._internal.editorActor}
|
|
268
220
|
patches$={legacyPatches}
|
|
269
221
|
/>
|
|
270
222
|
) : null}
|
|
271
|
-
<
|
|
223
|
+
<RouteEventsToChanges
|
|
224
|
+
editorActor={this.editor._internal.editorActor}
|
|
225
|
+
onChange={(change) => {
|
|
226
|
+
if (!this.props.editor) {
|
|
227
|
+
this.props.onChange(change)
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* For backwards compatibility, we relay all changes to the
|
|
231
|
+
* `change$` Subject as well.
|
|
232
|
+
*/
|
|
233
|
+
this.change$.next(change)
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
<Synchronizer
|
|
237
|
+
editorActor={this.editor._internal.editorActor}
|
|
238
|
+
getValue={this.editor.editable.getValue}
|
|
239
|
+
portableTextEditor={this}
|
|
240
|
+
slateEditor={this.editor._internal.slateEditor.instance}
|
|
241
|
+
/>
|
|
242
|
+
<EditorActorContext.Provider value={this.editor._internal.editorActor}>
|
|
272
243
|
<Slate
|
|
273
|
-
editor={this.slateEditor.instance}
|
|
274
|
-
initialValue={this.slateEditor.initialValue}
|
|
244
|
+
editor={this.editor._internal.slateEditor.instance}
|
|
245
|
+
initialValue={this.editor._internal.slateEditor.initialValue}
|
|
275
246
|
>
|
|
276
247
|
<PortableTextEditorContext.Provider value={this}>
|
|
277
248
|
<PortableTextEditorSelectionProvider
|
|
278
|
-
editorActor={this.editorActor}
|
|
249
|
+
editorActor={this.editor._internal.editorActor}
|
|
279
250
|
>
|
|
280
|
-
<Synchronizer
|
|
281
|
-
editorActor={this.editorActor}
|
|
282
|
-
getValue={this.getValue}
|
|
283
|
-
onChange={(change) => {
|
|
284
|
-
if (!this.props.editor) {
|
|
285
|
-
this.props.onChange(change)
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* For backwards compatibility, we relay all changes to the
|
|
289
|
-
* `change$` Subject as well.
|
|
290
|
-
*/
|
|
291
|
-
this.change$.next(change)
|
|
292
|
-
}}
|
|
293
|
-
/>
|
|
294
251
|
{this.props.children}
|
|
295
252
|
</PortableTextEditorSelectionProvider>
|
|
296
253
|
</PortableTextEditorContext.Provider>
|
|
@@ -468,3 +425,73 @@ function RoutePatchesObservableToEditorActor(props: {
|
|
|
468
425
|
|
|
469
426
|
return null
|
|
470
427
|
}
|
|
428
|
+
|
|
429
|
+
export function RouteEventsToChanges(props: {
|
|
430
|
+
editorActor: EditorActor
|
|
431
|
+
onChange: (change: EditorChange) => void
|
|
432
|
+
}) {
|
|
433
|
+
// We want to ensure that _when_ `props.onChange` is called, it uses the current value.
|
|
434
|
+
// But we don't want to have the `useEffect` run setup + teardown + setup every time the prop might change, as that's unnecessary.
|
|
435
|
+
// So we use our own polyfill that lets us use an upcoming React hook that solves this exact problem.
|
|
436
|
+
// https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
|
|
437
|
+
const handleChange = useEffectEvent((change: EditorChange) =>
|
|
438
|
+
props.onChange(change),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
debug('Subscribing to editor changes')
|
|
443
|
+
const sub = props.editorActor.on('*', (event) => {
|
|
444
|
+
switch (event.type) {
|
|
445
|
+
case 'patch':
|
|
446
|
+
handleChange(event)
|
|
447
|
+
break
|
|
448
|
+
case 'loading': {
|
|
449
|
+
handleChange({type: 'loading', isLoading: true})
|
|
450
|
+
break
|
|
451
|
+
}
|
|
452
|
+
case 'done loading': {
|
|
453
|
+
handleChange({type: 'loading', isLoading: false})
|
|
454
|
+
break
|
|
455
|
+
}
|
|
456
|
+
case 'focused': {
|
|
457
|
+
handleChange({type: 'focus', event: event.event})
|
|
458
|
+
break
|
|
459
|
+
}
|
|
460
|
+
case 'value changed': {
|
|
461
|
+
handleChange({type: 'value', value: event.value})
|
|
462
|
+
break
|
|
463
|
+
}
|
|
464
|
+
case 'invalid value': {
|
|
465
|
+
handleChange({
|
|
466
|
+
type: 'invalidValue',
|
|
467
|
+
resolution: event.resolution,
|
|
468
|
+
value: event.value,
|
|
469
|
+
})
|
|
470
|
+
break
|
|
471
|
+
}
|
|
472
|
+
case 'error': {
|
|
473
|
+
handleChange({
|
|
474
|
+
...event,
|
|
475
|
+
level: 'warning',
|
|
476
|
+
})
|
|
477
|
+
break
|
|
478
|
+
}
|
|
479
|
+
case 'annotation.add':
|
|
480
|
+
case 'annotation.remove':
|
|
481
|
+
case 'annotation.toggle':
|
|
482
|
+
case 'focus':
|
|
483
|
+
case 'patches':
|
|
484
|
+
case 'readOnly toggled':
|
|
485
|
+
break
|
|
486
|
+
default:
|
|
487
|
+
handleChange(event)
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
return () => {
|
|
491
|
+
debug('Unsubscribing to changes')
|
|
492
|
+
sub.unsubscribe()
|
|
493
|
+
}
|
|
494
|
+
}, [props.editorActor, handleChange])
|
|
495
|
+
|
|
496
|
+
return null
|
|
497
|
+
}
|
|
@@ -212,3 +212,12 @@ export type PickFromUnion<
|
|
|
212
212
|
TTagKey extends keyof TUnion,
|
|
213
213
|
TPickedTags extends TUnion[TTagKey],
|
|
214
214
|
> = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @alpha
|
|
218
|
+
*/
|
|
219
|
+
export type OmitFromUnion<
|
|
220
|
+
TUnion,
|
|
221
|
+
TTagKey extends keyof TUnion,
|
|
222
|
+
TOmittedTags extends TUnion[TTagKey],
|
|
223
|
+
> = TUnion extends Record<TTagKey, TOmittedTags> ? never : TUnion
|
|
@@ -4,14 +4,12 @@ import {useSelector} from '@xstate/react'
|
|
|
4
4
|
import {throttle} from 'lodash'
|
|
5
5
|
import {useCallback, useEffect, useRef} from 'react'
|
|
6
6
|
import {Editor} from 'slate'
|
|
7
|
-
import {
|
|
8
|
-
import {useEffectEvent} from 'use-effect-event'
|
|
9
|
-
import type {EditorChange} from '../../types/editor'
|
|
7
|
+
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
10
8
|
import {debugWithName} from '../../utils/debug'
|
|
11
9
|
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
12
10
|
import type {EditorActor} from '../editor-machine'
|
|
13
|
-
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
14
11
|
import {useSyncValue} from '../hooks/useSyncValue'
|
|
12
|
+
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
15
13
|
|
|
16
14
|
const debug = debugWithName('component:PortableTextEditor:Synchronizer')
|
|
17
15
|
const debugVerbose = debug.enabled && false
|
|
@@ -26,7 +24,8 @@ const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
|
26
24
|
export interface SynchronizerProps {
|
|
27
25
|
editorActor: EditorActor
|
|
28
26
|
getValue: () => Array<PortableTextBlock> | undefined
|
|
29
|
-
|
|
27
|
+
portableTextEditor: PortableTextEditor
|
|
28
|
+
slateEditor: PortableTextSlateEditor
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -34,20 +33,18 @@ export interface SynchronizerProps {
|
|
|
34
33
|
* @internal
|
|
35
34
|
*/
|
|
36
35
|
export function Synchronizer(props: SynchronizerProps) {
|
|
37
|
-
const portableTextEditor = usePortableTextEditor()
|
|
38
36
|
const readOnly = useSelector(props.editorActor, (s) => s.context.readOnly)
|
|
39
37
|
const value = useSelector(props.editorActor, (s) => s.context.value)
|
|
40
|
-
const {editorActor, getValue,
|
|
38
|
+
const {editorActor, getValue, portableTextEditor, slateEditor} = props
|
|
41
39
|
const pendingPatches = useRef<Patch[]>([])
|
|
42
40
|
|
|
43
41
|
const syncValue = useSyncValue({
|
|
44
42
|
editorActor,
|
|
45
43
|
portableTextEditor,
|
|
46
44
|
readOnly,
|
|
45
|
+
slateEditor,
|
|
47
46
|
})
|
|
48
47
|
|
|
49
|
-
const slateEditor = useSlate()
|
|
50
|
-
|
|
51
48
|
useEffect(() => {
|
|
52
49
|
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, false)
|
|
53
50
|
}, [slateEditor])
|
|
@@ -76,14 +73,6 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
76
73
|
}
|
|
77
74
|
}, [onFlushPendingPatches])
|
|
78
75
|
|
|
79
|
-
// We want to ensure that _when_ `props.onChange` is called, it uses the current value.
|
|
80
|
-
// But we don't want to have the `useEffect` run setup + teardown + setup every time the prop might change, as that's unnecessary.
|
|
81
|
-
// So we use our own polyfill that lets us use an upcoming React hook that solves this exact problem.
|
|
82
|
-
// https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
|
|
83
|
-
const handleChange = useEffectEvent((change: EditorChange) =>
|
|
84
|
-
onChange(change),
|
|
85
|
-
)
|
|
86
|
-
|
|
87
76
|
// Subscribe to, and handle changes from the editor
|
|
88
77
|
useEffect(() => {
|
|
89
78
|
const onFlushPendingPatchesThrottled = throttle(
|
|
@@ -104,61 +93,17 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
104
93
|
},
|
|
105
94
|
)
|
|
106
95
|
|
|
107
|
-
debug('Subscribing to
|
|
108
|
-
const sub = editorActor.on('
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
pendingPatches.current.push(event.patch)
|
|
113
|
-
onFlushPendingPatchesThrottled()
|
|
114
|
-
handleChange(event)
|
|
115
|
-
break
|
|
116
|
-
case 'loading': {
|
|
117
|
-
handleChange({type: 'loading', isLoading: true})
|
|
118
|
-
break
|
|
119
|
-
}
|
|
120
|
-
case 'done loading': {
|
|
121
|
-
handleChange({type: 'loading', isLoading: false})
|
|
122
|
-
break
|
|
123
|
-
}
|
|
124
|
-
case 'focused': {
|
|
125
|
-
handleChange({type: 'focus', event: event.event})
|
|
126
|
-
break
|
|
127
|
-
}
|
|
128
|
-
case 'value changed': {
|
|
129
|
-
handleChange({type: 'value', value: event.value})
|
|
130
|
-
break
|
|
131
|
-
}
|
|
132
|
-
case 'invalid value': {
|
|
133
|
-
handleChange({
|
|
134
|
-
type: 'invalidValue',
|
|
135
|
-
resolution: event.resolution,
|
|
136
|
-
value: event.value,
|
|
137
|
-
})
|
|
138
|
-
break
|
|
139
|
-
}
|
|
140
|
-
case 'error': {
|
|
141
|
-
handleChange({
|
|
142
|
-
...event,
|
|
143
|
-
level: 'warning',
|
|
144
|
-
})
|
|
145
|
-
break
|
|
146
|
-
}
|
|
147
|
-
case 'annotation.add':
|
|
148
|
-
case 'annotation.remove':
|
|
149
|
-
case 'annotation.toggle':
|
|
150
|
-
case 'focus':
|
|
151
|
-
case 'patches':
|
|
152
|
-
break
|
|
153
|
-
default:
|
|
154
|
-
handleChange(event)
|
|
155
|
-
}
|
|
96
|
+
debug('Subscribing to patch events')
|
|
97
|
+
const sub = editorActor.on('patch', (event) => {
|
|
98
|
+
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, true)
|
|
99
|
+
pendingPatches.current.push(event.patch)
|
|
100
|
+
onFlushPendingPatchesThrottled()
|
|
156
101
|
})
|
|
157
102
|
return () => {
|
|
158
|
-
debug('Unsubscribing to
|
|
103
|
+
debug('Unsubscribing to patch events')
|
|
159
104
|
sub.unsubscribe()
|
|
160
105
|
}
|
|
161
|
-
}, [editorActor,
|
|
106
|
+
}, [editorActor, onFlushPendingPatches, slateEditor])
|
|
162
107
|
|
|
163
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.
|
|
164
109
|
// This will cause the editor to not be able to signal a validation error and offer invalid value resolution of the initial value.
|
|
@@ -32,8 +32,8 @@ export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
|
|
|
32
32
|
|
|
33
33
|
debug('Creating new Slate editor instance', config.editorActor.id)
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const unsubscriptions: Array<() => void> = []
|
|
36
|
+
const subscriptions: Array<() => () => void> = []
|
|
37
37
|
|
|
38
38
|
const instance = withPlugins(withReact(createEditor()), {
|
|
39
39
|
editorActor: config.editorActor,
|
|
@@ -47,18 +47,6 @@ export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
|
|
|
47
47
|
unsubscriptions.push(subscription())
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
config.editorActor.subscribe((snapshot) => {
|
|
51
|
-
if (snapshot.status !== 'active') {
|
|
52
|
-
debug('Destroying Slate editor')
|
|
53
|
-
instance.destroy()
|
|
54
|
-
for (const unsubscribe of unsubscriptions) {
|
|
55
|
-
unsubscribe()
|
|
56
|
-
}
|
|
57
|
-
subscriptions = []
|
|
58
|
-
unsubscriptions = []
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
50
|
const initialValue = [instance.pteCreateTextBlock({decorators: []})]
|
|
63
51
|
|
|
64
52
|
const slateEditor: SlateEditor = {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {useEffect} from 'react'
|
|
2
|
+
import {useEffectEvent} from 'use-effect-event'
|
|
3
|
+
import type {EditorEmittedEvent} from './editor-machine'
|
|
4
|
+
import {useEditorContext} from './editor-provider'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @alpha
|
|
8
|
+
*/
|
|
9
|
+
export function EditorEventListener(props: {
|
|
10
|
+
on: (event: EditorEmittedEvent) => void
|
|
11
|
+
}) {
|
|
12
|
+
const editor = useEditorContext()
|
|
13
|
+
const on = useEffectEvent(props.on)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const subscription = editor.on('*', on)
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
subscription.unsubscribe()
|
|
20
|
+
}
|
|
21
|
+
}, [editor, on])
|
|
22
|
+
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
BehaviorActionIntend,
|
|
28
28
|
BehaviorContext,
|
|
29
29
|
BehaviorEvent,
|
|
30
|
+
OmitFromUnion,
|
|
30
31
|
PickFromUnion,
|
|
31
32
|
} from './behavior/behavior.types'
|
|
32
33
|
|
|
@@ -95,7 +96,27 @@ export type InternalEditorEvent =
|
|
|
95
96
|
type: 'update maxBlocks'
|
|
96
97
|
maxBlocks: number | undefined
|
|
97
98
|
}
|
|
98
|
-
| InternalEditorEmittedEvent
|
|
99
|
+
| OmitFromUnion<InternalEditorEmittedEvent, 'type', 'readOnly toggled'>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @alpha
|
|
103
|
+
*/
|
|
104
|
+
export type EditorEmittedEvent = PickFromUnion<
|
|
105
|
+
InternalEditorEmittedEvent,
|
|
106
|
+
'type',
|
|
107
|
+
| 'blur'
|
|
108
|
+
| 'done loading'
|
|
109
|
+
| 'error'
|
|
110
|
+
| 'focus'
|
|
111
|
+
| 'invalid value'
|
|
112
|
+
| 'loading'
|
|
113
|
+
| 'mutation'
|
|
114
|
+
| 'patch'
|
|
115
|
+
| 'readOnly toggled'
|
|
116
|
+
| 'ready'
|
|
117
|
+
| 'selection'
|
|
118
|
+
| 'value changed'
|
|
119
|
+
>
|
|
99
120
|
|
|
100
121
|
/**
|
|
101
122
|
* @internal
|
|
@@ -129,6 +150,7 @@ export type InternalEditorEmittedEvent =
|
|
|
129
150
|
| {type: 'focused'; event: FocusEvent<HTMLDivElement, Element>}
|
|
130
151
|
| {type: 'loading'}
|
|
131
152
|
| {type: 'done loading'}
|
|
153
|
+
| {type: 'readOnly toggled'; readOnly: boolean}
|
|
132
154
|
| PickFromUnion<
|
|
133
155
|
BehaviorEvent,
|
|
134
156
|
'type',
|
|
@@ -154,6 +176,8 @@ export const editorMachine = setup({
|
|
|
154
176
|
input: {} as {
|
|
155
177
|
behaviors?: Array<Behavior>
|
|
156
178
|
keyGenerator: () => string
|
|
179
|
+
maxBlocks?: number
|
|
180
|
+
readOnly?: boolean
|
|
157
181
|
schema: PortableTextMemberSchemaTypes
|
|
158
182
|
value?: Array<PortableTextBlock>
|
|
159
183
|
},
|
|
@@ -296,8 +320,8 @@ export const editorMachine = setup({
|
|
|
296
320
|
keyGenerator: input.keyGenerator,
|
|
297
321
|
pendingEvents: [],
|
|
298
322
|
schema: input.schema,
|
|
299
|
-
readOnly: false,
|
|
300
|
-
maxBlocks:
|
|
323
|
+
readOnly: input.readOnly ?? false,
|
|
324
|
+
maxBlocks: input.maxBlocks,
|
|
301
325
|
value: input.value,
|
|
302
326
|
}),
|
|
303
327
|
on: {
|
|
@@ -332,7 +356,13 @@ export const editorMachine = setup({
|
|
|
332
356
|
'update schema': {actions: 'assign schema'},
|
|
333
357
|
'update value': {actions: assign({value: ({event}) => event.value})},
|
|
334
358
|
'toggle readOnly': {
|
|
335
|
-
actions:
|
|
359
|
+
actions: [
|
|
360
|
+
assign({readOnly: ({context}) => !context.readOnly}),
|
|
361
|
+
emit(({context}) => ({
|
|
362
|
+
type: 'readOnly toggled',
|
|
363
|
+
readOnly: context.readOnly,
|
|
364
|
+
})),
|
|
365
|
+
],
|
|
336
366
|
},
|
|
337
367
|
'update maxBlocks': {
|
|
338
368
|
actions: assign({maxBlocks: ({event}) => event.maxBlocks}),
|