@portabletext/editor 1.49.7 → 1.49.9
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 +5487 -5431
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +35 -277
- package/lib/index.d.ts +35 -277
- package/lib/index.js +5745 -5689
- 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/Editable.tsx +1 -0
- package/src/editor/PortableTextEditor.tsx +73 -127
- package/src/editor/__tests__/PortableTextEditor.test.tsx +1 -0
- package/src/editor/create-editor.ts +159 -5
- package/src/editor/create-slate-editor.tsx +4 -21
- package/src/editor/editor-machine.ts +2 -9
- package/src/editor/editor-provider.tsx +44 -29
- package/src/editor/mutation-machine.ts +3 -0
- package/src/editor/route-events-to-changes.tsx +81 -0
- package/src/editor/sync-machine.ts +34 -0
- package/src/editor.ts +7 -1
- package/src/internal-utils/applyPatch.ts +21 -4
- package/src/internal-utils/stop-actor.ts +43 -0
- package/src/internal-utils/text-selection.ts +3 -1
- package/src/internal-utils/use-constant.ts +15 -0
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +0 -364
- package/src/editor/components/Synchronizer.tsx +0 -134
|
@@ -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,10 +9,12 @@ import {
|
|
|
9
9
|
emit,
|
|
10
10
|
fromCallback,
|
|
11
11
|
not,
|
|
12
|
+
raise,
|
|
12
13
|
setup,
|
|
13
14
|
type AnyEventObject,
|
|
14
15
|
type CallbackLogicFunction,
|
|
15
16
|
} from 'xstate'
|
|
17
|
+
import type {ActorRefFrom} from 'xstate'
|
|
16
18
|
import {debugWithName} from '../internal-utils/debug'
|
|
17
19
|
import {validateValue} from '../internal-utils/validateValue'
|
|
18
20
|
import {toSlateValue, VOID_CHILD_KEY} from '../internal-utils/values'
|
|
@@ -76,6 +78,8 @@ const syncValueCallback: CallbackLogicFunction<
|
|
|
76
78
|
|
|
77
79
|
const syncValueLogic = fromCallback(syncValueCallback)
|
|
78
80
|
|
|
81
|
+
export type SyncActor = ActorRefFrom<typeof syncMachine>
|
|
82
|
+
|
|
79
83
|
/**
|
|
80
84
|
* Sync value with the editor state
|
|
81
85
|
*
|
|
@@ -91,6 +95,7 @@ const syncValueLogic = fromCallback(syncValueCallback)
|
|
|
91
95
|
export const syncMachine = setup({
|
|
92
96
|
types: {
|
|
93
97
|
context: {} as {
|
|
98
|
+
initialValue: Array<PortableTextBlock> | undefined
|
|
94
99
|
initialValueSynced: boolean
|
|
95
100
|
isProcessingLocalChanges: boolean
|
|
96
101
|
keyGenerator: () => string
|
|
@@ -101,6 +106,7 @@ export const syncMachine = setup({
|
|
|
101
106
|
previousValue: Array<PortableTextBlock> | undefined
|
|
102
107
|
},
|
|
103
108
|
input: {} as {
|
|
109
|
+
initialValue: Array<PortableTextBlock> | undefined
|
|
104
110
|
keyGenerator: () => string
|
|
105
111
|
schema: EditorSchema
|
|
106
112
|
readOnly: boolean
|
|
@@ -175,6 +181,16 @@ export const syncMachine = setup({
|
|
|
175
181
|
|
|
176
182
|
return isBusy
|
|
177
183
|
},
|
|
184
|
+
'is empty value': ({event}) => {
|
|
185
|
+
return event.type === 'update value' && event.value === undefined
|
|
186
|
+
},
|
|
187
|
+
'is empty array': ({event}) => {
|
|
188
|
+
return (
|
|
189
|
+
event.type === 'update value' &&
|
|
190
|
+
Array.isArray(event.value) &&
|
|
191
|
+
event.value.length === 0
|
|
192
|
+
)
|
|
193
|
+
},
|
|
178
194
|
'is new value': ({context, event}) => {
|
|
179
195
|
return (
|
|
180
196
|
event.type === 'update value' && context.previousValue !== event.value
|
|
@@ -194,6 +210,7 @@ export const syncMachine = setup({
|
|
|
194
210
|
}).createMachine({
|
|
195
211
|
id: 'sync',
|
|
196
212
|
context: ({input}) => ({
|
|
213
|
+
initialValue: input.initialValue,
|
|
197
214
|
initialValueSynced: false,
|
|
198
215
|
isProcessingLocalChanges: false,
|
|
199
216
|
keyGenerator: input.keyGenerator,
|
|
@@ -203,6 +220,11 @@ export const syncMachine = setup({
|
|
|
203
220
|
pendingValue: undefined,
|
|
204
221
|
previousValue: undefined,
|
|
205
222
|
}),
|
|
223
|
+
entry: [
|
|
224
|
+
raise(({context}) => {
|
|
225
|
+
return {type: 'update value', value: context.initialValue}
|
|
226
|
+
}),
|
|
227
|
+
],
|
|
206
228
|
on: {
|
|
207
229
|
'has pending patches': {
|
|
208
230
|
actions: assign({
|
|
@@ -233,6 +255,18 @@ export const syncMachine = setup({
|
|
|
233
255
|
],
|
|
234
256
|
on: {
|
|
235
257
|
'update value': [
|
|
258
|
+
{
|
|
259
|
+
guard: and(['is empty value', not('initial value synced')]),
|
|
260
|
+
actions: ['assign initial value synced', 'emit done syncing value'],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
guard: and(['is empty array', not('initial value synced')]),
|
|
264
|
+
actions: [
|
|
265
|
+
'assign initial value synced',
|
|
266
|
+
emit({type: 'value changed', value: []}),
|
|
267
|
+
'emit done syncing value',
|
|
268
|
+
],
|
|
269
|
+
},
|
|
236
270
|
{
|
|
237
271
|
guard: and(['is busy', 'is new value']),
|
|
238
272
|
target: 'busy',
|
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
|
|
@@ -24,16 +24,16 @@ import type {
|
|
|
24
24
|
} from '@sanity/types'
|
|
25
25
|
import {
|
|
26
26
|
Element,
|
|
27
|
+
Node,
|
|
27
28
|
Text,
|
|
28
29
|
Transforms,
|
|
29
30
|
type Descendant,
|
|
30
|
-
type Node,
|
|
31
31
|
type Path as SlatePath,
|
|
32
32
|
} from 'slate'
|
|
33
33
|
import type {EditorSchema} from '../editor/editor-schema'
|
|
34
34
|
import type {PortableTextSlateEditor} from '../types/editor'
|
|
35
35
|
import {debugWithName} from './debug'
|
|
36
|
-
import {toSlateValue} from './values'
|
|
36
|
+
import {isEqualToEmptyEditor, toSlateValue} from './values'
|
|
37
37
|
import {KEY_TO_SLATE_ELEMENT} from './weakMaps'
|
|
38
38
|
|
|
39
39
|
const debug = debugWithName('applyPatches')
|
|
@@ -173,9 +173,20 @@ function insertPatch(
|
|
|
173
173
|
const targetBlockIndex = targetBlockPath[0]
|
|
174
174
|
const normalizedIdx =
|
|
175
175
|
position === 'after' ? targetBlockIndex + 1 : targetBlockIndex
|
|
176
|
+
|
|
176
177
|
debug(`Inserting blocks at path [${normalizedIdx}]`)
|
|
177
178
|
debugState(editor, 'before')
|
|
179
|
+
|
|
180
|
+
const editorWasEmptyBefore = isEqualToEmptyEditor(editor.children, schema)
|
|
181
|
+
|
|
178
182
|
Transforms.insertNodes(editor, blocksToInsert, {at: [normalizedIdx]})
|
|
183
|
+
|
|
184
|
+
if (editorWasEmptyBefore) {
|
|
185
|
+
Transforms.removeNodes(editor, {
|
|
186
|
+
at: [position === 'after' ? targetBlockIndex + 1 : targetBlockIndex],
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
179
190
|
debugState(editor, 'after')
|
|
180
191
|
return true
|
|
181
192
|
}
|
|
@@ -319,9 +330,15 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
|
|
|
319
330
|
debugState(editor, 'before')
|
|
320
331
|
const previousSelection = editor.selection
|
|
321
332
|
Transforms.deselect(editor)
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
|
|
334
|
+
const children = Node.children(editor, [], {
|
|
335
|
+
reverse: true,
|
|
324
336
|
})
|
|
337
|
+
|
|
338
|
+
for (const [_, path] of children) {
|
|
339
|
+
Transforms.removeNodes(editor, {at: path})
|
|
340
|
+
}
|
|
341
|
+
|
|
325
342
|
Transforms.insertNodes(editor, editor.pteCreateTextBlock({decorators: []}))
|
|
326
343
|
if (previousSelection) {
|
|
327
344
|
Transforms.select(editor, {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy/pasted from https://github.com/statelyai/xstate/blob/main/packages/xstate-react/src/stopRootWithRehydration.ts
|
|
3
|
+
* and renamed to `stopActor`
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {AnyActorRef, Snapshot} from 'xstate'
|
|
7
|
+
|
|
8
|
+
const forEachActor = (
|
|
9
|
+
actorRef: AnyActorRef,
|
|
10
|
+
callback: (ref: AnyActorRef) => void,
|
|
11
|
+
) => {
|
|
12
|
+
callback(actorRef)
|
|
13
|
+
const children = actorRef.getSnapshot().children
|
|
14
|
+
if (children) {
|
|
15
|
+
Object.values(children).forEach((child) => {
|
|
16
|
+
forEachActor(child as AnyActorRef, callback)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function stopActor(actorRef: AnyActorRef) {
|
|
22
|
+
// persist snapshot here in a custom way allows us to persist inline actors and to preserve actor references
|
|
23
|
+
// we do it to avoid setState in useEffect when the effect gets "reconnected"
|
|
24
|
+
// this currently only happens in Strict Effects but it simulates the Offscreen aka Activity API
|
|
25
|
+
// it also just allows us to end up with a somewhat more predictable behavior for the users
|
|
26
|
+
const persistedSnapshots: Array<[AnyActorRef, Snapshot<unknown>]> = []
|
|
27
|
+
forEachActor(actorRef, (ref) => {
|
|
28
|
+
persistedSnapshots.push([ref, ref.getSnapshot()])
|
|
29
|
+
// muting observers allow us to avoid `useSelector` from being notified about the stopped snapshot
|
|
30
|
+
// React reconnects its subscribers (from the useSyncExternalStore) on its own
|
|
31
|
+
// and userland subscibers should basically always do the same anyway
|
|
32
|
+
// as each subscription should have its own cleanup logic and that should be called each such reconnect
|
|
33
|
+
;(ref as any).observers = new Set()
|
|
34
|
+
})
|
|
35
|
+
const systemSnapshot = actorRef.system.getSnapshot?.()
|
|
36
|
+
|
|
37
|
+
actorRef.stop()
|
|
38
|
+
;(actorRef.system as any)._snapshot = systemSnapshot
|
|
39
|
+
persistedSnapshots.forEach(([ref, snapshot]) => {
|
|
40
|
+
;(ref as any)._processingStatus = 0
|
|
41
|
+
;(ref as any)._snapshot = snapshot
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -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 {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
type ConstantRef<TConstant> = {constant: TConstant}
|
|
4
|
+
|
|
5
|
+
export default function useConstant<TConstant>(
|
|
6
|
+
factory: () => TConstant,
|
|
7
|
+
): TConstant {
|
|
8
|
+
const ref = React.useRef<ConstantRef<TConstant>>(null)
|
|
9
|
+
|
|
10
|
+
if (!ref.current) {
|
|
11
|
+
ref.current = {constant: factory()}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return ref.current.constant
|
|
15
|
+
}
|
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
-
import {render, waitFor} from '@testing-library/react'
|
|
3
|
-
import {createRef, type RefObject} from 'react'
|
|
4
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
-
import {createTestKeyGenerator} from '../../internal-utils/test-key-generator'
|
|
6
|
-
import {PortableTextEditor} from '../PortableTextEditor'
|
|
7
|
-
import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
|
|
8
|
-
|
|
9
|
-
describe('when PTE would display warnings, instead it self solves', () => {
|
|
10
|
-
it('when child at index is missing required _key in block with _key', async () => {
|
|
11
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
12
|
-
const initialValue = [
|
|
13
|
-
{
|
|
14
|
-
_key: 'abc',
|
|
15
|
-
_type: 'myTestBlockType',
|
|
16
|
-
children: [
|
|
17
|
-
{
|
|
18
|
-
_type: 'span',
|
|
19
|
-
marks: [],
|
|
20
|
-
text: 'Hello with a new key',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
markDefs: [],
|
|
24
|
-
style: 'normal',
|
|
25
|
-
},
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
const onChange = vi.fn()
|
|
29
|
-
render(
|
|
30
|
-
<PortableTextEditorTester
|
|
31
|
-
keyGenerator={createTestKeyGenerator()}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
ref={editorRef}
|
|
34
|
-
schemaType={schemaType}
|
|
35
|
-
value={initialValue}
|
|
36
|
-
/>,
|
|
37
|
-
)
|
|
38
|
-
await waitFor(() => {
|
|
39
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
40
|
-
type: 'value',
|
|
41
|
-
value: initialValue,
|
|
42
|
-
})
|
|
43
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
44
|
-
})
|
|
45
|
-
await waitFor(() => {
|
|
46
|
-
if (editorRef.current) {
|
|
47
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
48
|
-
{
|
|
49
|
-
_key: 'abc',
|
|
50
|
-
_type: 'myTestBlockType',
|
|
51
|
-
children: [
|
|
52
|
-
{
|
|
53
|
-
_key: 'k3',
|
|
54
|
-
_type: 'span',
|
|
55
|
-
text: 'Hello with a new key',
|
|
56
|
-
marks: [],
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
markDefs: [],
|
|
60
|
-
style: 'normal',
|
|
61
|
-
},
|
|
62
|
-
])
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('self-solves missing .markDefs', async () => {
|
|
68
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
69
|
-
const initialValue = [
|
|
70
|
-
{
|
|
71
|
-
_key: 'abc',
|
|
72
|
-
_type: 'myTestBlockType',
|
|
73
|
-
children: [
|
|
74
|
-
{
|
|
75
|
-
_key: 'def',
|
|
76
|
-
_type: 'span',
|
|
77
|
-
marks: [],
|
|
78
|
-
text: 'No markDefs',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
style: 'normal',
|
|
82
|
-
},
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
const onChange = vi.fn()
|
|
86
|
-
render(
|
|
87
|
-
<PortableTextEditorTester
|
|
88
|
-
keyGenerator={createTestKeyGenerator()}
|
|
89
|
-
onChange={onChange}
|
|
90
|
-
ref={editorRef}
|
|
91
|
-
schemaType={schemaType}
|
|
92
|
-
value={initialValue}
|
|
93
|
-
/>,
|
|
94
|
-
)
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
97
|
-
type: 'value',
|
|
98
|
-
value: initialValue,
|
|
99
|
-
})
|
|
100
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
101
|
-
})
|
|
102
|
-
await waitFor(() => {
|
|
103
|
-
if (editorRef.current) {
|
|
104
|
-
PortableTextEditor.focus(editorRef.current)
|
|
105
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
106
|
-
{
|
|
107
|
-
_key: 'abc',
|
|
108
|
-
_type: 'myTestBlockType',
|
|
109
|
-
children: [
|
|
110
|
-
{
|
|
111
|
-
_key: 'def',
|
|
112
|
-
_type: 'span',
|
|
113
|
-
text: 'No markDefs',
|
|
114
|
-
marks: [],
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
markDefs: [],
|
|
118
|
-
style: 'normal',
|
|
119
|
-
},
|
|
120
|
-
])
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('adds missing .children', async () => {
|
|
126
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
127
|
-
const initialValue = [
|
|
128
|
-
{
|
|
129
|
-
_key: 'abc',
|
|
130
|
-
_type: 'myTestBlockType',
|
|
131
|
-
style: 'normal',
|
|
132
|
-
markDefs: [],
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
_key: 'def',
|
|
136
|
-
_type: 'myTestBlockType',
|
|
137
|
-
style: 'normal',
|
|
138
|
-
children: [],
|
|
139
|
-
markDefs: [],
|
|
140
|
-
},
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
const onChange = vi.fn()
|
|
144
|
-
render(
|
|
145
|
-
<PortableTextEditorTester
|
|
146
|
-
keyGenerator={createTestKeyGenerator()}
|
|
147
|
-
onChange={onChange}
|
|
148
|
-
ref={editorRef}
|
|
149
|
-
schemaType={schemaType}
|
|
150
|
-
value={initialValue}
|
|
151
|
-
/>,
|
|
152
|
-
)
|
|
153
|
-
await waitFor(() => {
|
|
154
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
155
|
-
type: 'value',
|
|
156
|
-
value: initialValue,
|
|
157
|
-
})
|
|
158
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
159
|
-
})
|
|
160
|
-
await waitFor(() => {
|
|
161
|
-
if (editorRef.current) {
|
|
162
|
-
PortableTextEditor.focus(editorRef.current)
|
|
163
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
164
|
-
{
|
|
165
|
-
_key: 'abc',
|
|
166
|
-
_type: 'myTestBlockType',
|
|
167
|
-
children: [
|
|
168
|
-
{
|
|
169
|
-
_key: 'k3',
|
|
170
|
-
_type: 'span',
|
|
171
|
-
text: '',
|
|
172
|
-
marks: [],
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
markDefs: [],
|
|
176
|
-
style: 'normal',
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
_key: 'def',
|
|
180
|
-
_type: 'myTestBlockType',
|
|
181
|
-
children: [
|
|
182
|
-
{
|
|
183
|
-
_key: 'k5',
|
|
184
|
-
_type: 'span',
|
|
185
|
-
text: '',
|
|
186
|
-
marks: [],
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
markDefs: [],
|
|
190
|
-
style: 'normal',
|
|
191
|
-
},
|
|
192
|
-
])
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('removes orphaned marks', async () => {
|
|
198
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
199
|
-
const initialValue = [
|
|
200
|
-
{
|
|
201
|
-
_key: 'abc',
|
|
202
|
-
_type: 'myTestBlockType',
|
|
203
|
-
style: 'normal',
|
|
204
|
-
markDefs: [],
|
|
205
|
-
children: [
|
|
206
|
-
{
|
|
207
|
-
_key: 'def',
|
|
208
|
-
_type: 'span',
|
|
209
|
-
marks: ['ghi'],
|
|
210
|
-
text: 'Hello',
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
},
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
const onChange = vi.fn()
|
|
217
|
-
render(
|
|
218
|
-
<PortableTextEditorTester
|
|
219
|
-
keyGenerator={createTestKeyGenerator()}
|
|
220
|
-
onChange={onChange}
|
|
221
|
-
ref={editorRef}
|
|
222
|
-
schemaType={schemaType}
|
|
223
|
-
value={initialValue}
|
|
224
|
-
/>,
|
|
225
|
-
)
|
|
226
|
-
await waitFor(() => {
|
|
227
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
228
|
-
type: 'value',
|
|
229
|
-
value: initialValue,
|
|
230
|
-
})
|
|
231
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
232
|
-
})
|
|
233
|
-
await waitFor(() => {
|
|
234
|
-
if (editorRef.current) {
|
|
235
|
-
PortableTextEditor.focus(editorRef.current)
|
|
236
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
237
|
-
{
|
|
238
|
-
_key: 'abc',
|
|
239
|
-
_type: 'myTestBlockType',
|
|
240
|
-
children: [
|
|
241
|
-
{
|
|
242
|
-
_key: 'def',
|
|
243
|
-
_type: 'span',
|
|
244
|
-
text: 'Hello',
|
|
245
|
-
marks: [],
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
markDefs: [],
|
|
249
|
-
style: 'normal',
|
|
250
|
-
},
|
|
251
|
-
])
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('removes orphaned marksDefs', async () => {
|
|
257
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
258
|
-
const initialValue = [
|
|
259
|
-
{
|
|
260
|
-
_key: 'abc',
|
|
261
|
-
_type: 'myTestBlockType',
|
|
262
|
-
style: 'normal',
|
|
263
|
-
markDefs: [
|
|
264
|
-
{
|
|
265
|
-
_key: 'ghi',
|
|
266
|
-
_type: 'link',
|
|
267
|
-
href: 'https://sanity.io',
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
children: [
|
|
271
|
-
{
|
|
272
|
-
_key: 'def',
|
|
273
|
-
_type: 'span',
|
|
274
|
-
marks: [],
|
|
275
|
-
text: 'Hello',
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
]
|
|
280
|
-
|
|
281
|
-
const onChange = vi.fn()
|
|
282
|
-
render(
|
|
283
|
-
<PortableTextEditorTester
|
|
284
|
-
keyGenerator={createTestKeyGenerator()}
|
|
285
|
-
onChange={onChange}
|
|
286
|
-
ref={editorRef}
|
|
287
|
-
schemaType={schemaType}
|
|
288
|
-
value={initialValue}
|
|
289
|
-
/>,
|
|
290
|
-
)
|
|
291
|
-
await waitFor(() => {
|
|
292
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
293
|
-
type: 'value',
|
|
294
|
-
value: initialValue,
|
|
295
|
-
})
|
|
296
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
297
|
-
})
|
|
298
|
-
await waitFor(() => {
|
|
299
|
-
if (editorRef.current) {
|
|
300
|
-
PortableTextEditor.focus(editorRef.current)
|
|
301
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
302
|
-
{
|
|
303
|
-
_key: 'abc',
|
|
304
|
-
_type: 'myTestBlockType',
|
|
305
|
-
children: [
|
|
306
|
-
{
|
|
307
|
-
_key: 'def',
|
|
308
|
-
_type: 'span',
|
|
309
|
-
text: 'Hello',
|
|
310
|
-
marks: [],
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
markDefs: [],
|
|
314
|
-
style: 'normal',
|
|
315
|
-
},
|
|
316
|
-
])
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('allows empty array of blocks', async () => {
|
|
322
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
323
|
-
const initialValue = [] as PortableTextBlock[]
|
|
324
|
-
|
|
325
|
-
const onChange = vi.fn()
|
|
326
|
-
render(
|
|
327
|
-
<PortableTextEditorTester
|
|
328
|
-
keyGenerator={createTestKeyGenerator()}
|
|
329
|
-
onChange={onChange}
|
|
330
|
-
ref={editorRef}
|
|
331
|
-
schemaType={schemaType}
|
|
332
|
-
value={initialValue}
|
|
333
|
-
/>,
|
|
334
|
-
)
|
|
335
|
-
await waitFor(() => {
|
|
336
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
337
|
-
type: 'value',
|
|
338
|
-
value: initialValue,
|
|
339
|
-
})
|
|
340
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
341
|
-
})
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
if (editorRef.current) {
|
|
344
|
-
PortableTextEditor.focus(editorRef.current)
|
|
345
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
346
|
-
{
|
|
347
|
-
_key: 'k2',
|
|
348
|
-
_type: 'myTestBlockType',
|
|
349
|
-
children: [{_key: 'k3', _type: 'span', marks: [], text: ''}],
|
|
350
|
-
markDefs: [],
|
|
351
|
-
style: 'normal',
|
|
352
|
-
},
|
|
353
|
-
])
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
await waitFor(() => {
|
|
357
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
358
|
-
type: 'value',
|
|
359
|
-
value: initialValue,
|
|
360
|
-
})
|
|
361
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
})
|