@portabletext/editor 0.0.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/LICENSE +21 -0
- package/README.md +3 -0
- package/lib/index.d.mts +911 -0
- package/lib/index.d.ts +911 -0
- package/lib/index.esm.js +4896 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +4874 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +4896 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +119 -0
- package/src/editor/Editable.tsx +683 -0
- package/src/editor/PortableTextEditor.tsx +308 -0
- package/src/editor/__tests__/PortableTextEditor.test.tsx +386 -0
- package/src/editor/__tests__/PortableTextEditorTester.tsx +116 -0
- package/src/editor/__tests__/RangeDecorations.test.tsx +115 -0
- package/src/editor/__tests__/handleClick.test.tsx +218 -0
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +389 -0
- package/src/editor/__tests__/utils.ts +39 -0
- package/src/editor/components/DraggableBlock.tsx +287 -0
- package/src/editor/components/Element.tsx +279 -0
- package/src/editor/components/Leaf.tsx +288 -0
- package/src/editor/components/SlateContainer.tsx +81 -0
- package/src/editor/components/Synchronizer.tsx +190 -0
- package/src/editor/hooks/usePortableTextEditor.ts +23 -0
- package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +24 -0
- package/src/editor/hooks/usePortableTextEditorSelection.ts +22 -0
- package/src/editor/hooks/usePortableTextEditorValue.ts +16 -0
- package/src/editor/hooks/usePortableTextReadOnly.ts +20 -0
- package/src/editor/hooks/useSyncValue.test.tsx +125 -0
- package/src/editor/hooks/useSyncValue.ts +372 -0
- package/src/editor/nodes/DefaultAnnotation.tsx +16 -0
- package/src/editor/nodes/DefaultObject.tsx +15 -0
- package/src/editor/nodes/index.ts +189 -0
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +244 -0
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +142 -0
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +346 -0
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +162 -0
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +212 -0
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +204 -0
- package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +133 -0
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +65 -0
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +1377 -0
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +91 -0
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +115 -0
- package/src/editor/plugins/createWithEditableAPI.ts +573 -0
- package/src/editor/plugins/createWithHotKeys.ts +304 -0
- package/src/editor/plugins/createWithInsertBreak.ts +45 -0
- package/src/editor/plugins/createWithInsertData.ts +359 -0
- package/src/editor/plugins/createWithMaxBlocks.ts +24 -0
- package/src/editor/plugins/createWithObjectKeys.ts +63 -0
- package/src/editor/plugins/createWithPatches.ts +274 -0
- package/src/editor/plugins/createWithPlaceholderBlock.ts +36 -0
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +91 -0
- package/src/editor/plugins/createWithPortableTextLists.ts +160 -0
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +441 -0
- package/src/editor/plugins/createWithPortableTextSelections.ts +65 -0
- package/src/editor/plugins/createWithSchemaTypes.ts +76 -0
- package/src/editor/plugins/createWithUndoRedo.ts +494 -0
- package/src/editor/plugins/createWithUtils.ts +81 -0
- package/src/editor/plugins/index.ts +155 -0
- package/src/index.ts +11 -0
- package/src/patch/PatchEvent.ts +33 -0
- package/src/patch/applyPatch.ts +29 -0
- package/src/patch/array.ts +89 -0
- package/src/patch/arrayInsert.ts +27 -0
- package/src/patch/object.ts +39 -0
- package/src/patch/patches.ts +53 -0
- package/src/patch/primitive.ts +43 -0
- package/src/patch/string.ts +51 -0
- package/src/types/editor.ts +576 -0
- package/src/types/options.ts +17 -0
- package/src/types/patch.ts +65 -0
- package/src/types/slate.ts +25 -0
- package/src/utils/__tests__/dmpToOperations.test.ts +181 -0
- package/src/utils/__tests__/operationToPatches.test.ts +421 -0
- package/src/utils/__tests__/patchToOperations.test.ts +293 -0
- package/src/utils/__tests__/ranges.test.ts +18 -0
- package/src/utils/__tests__/valueNormalization.test.tsx +62 -0
- package/src/utils/__tests__/values.test.ts +253 -0
- package/src/utils/applyPatch.ts +407 -0
- package/src/utils/bufferUntil.ts +15 -0
- package/src/utils/debug.ts +12 -0
- package/src/utils/getPortableTextMemberSchemaTypes.ts +100 -0
- package/src/utils/operationToPatches.ts +357 -0
- package/src/utils/patches.ts +36 -0
- package/src/utils/paths.ts +60 -0
- package/src/utils/ranges.ts +77 -0
- package/src/utils/schema.ts +8 -0
- package/src/utils/selection.ts +65 -0
- package/src/utils/ucs2Indices.ts +67 -0
- package/src/utils/validateValue.ts +394 -0
- package/src/utils/values.ts +208 -0
- package/src/utils/weakMaps.ts +24 -0
- package/src/utils/withChanges.ts +25 -0
- package/src/utils/withPreserveKeys.ts +14 -0
- package/src/utils/withoutPatching.ts +14 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {type PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import {throttle} from 'lodash'
|
|
3
|
+
import {
|
|
4
|
+
type PropsWithChildren,
|
|
5
|
+
startTransition,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react'
|
|
12
|
+
import {Editor} from 'slate'
|
|
13
|
+
import {useSlate} from 'slate-react'
|
|
14
|
+
|
|
15
|
+
import {type EditorChange, type EditorChanges, type EditorSelection} from '../../types/editor'
|
|
16
|
+
import {type Patch} from '../../types/patch'
|
|
17
|
+
import {debugWithName} from '../../utils/debug'
|
|
18
|
+
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
19
|
+
import {PortableTextEditorContext} from '../hooks/usePortableTextEditor'
|
|
20
|
+
import {PortableTextEditorKeyGeneratorContext} from '../hooks/usePortableTextEditorKeyGenerator'
|
|
21
|
+
import {PortableTextEditorSelectionContext} from '../hooks/usePortableTextEditorSelection'
|
|
22
|
+
import {PortableTextEditorValueContext} from '../hooks/usePortableTextEditorValue'
|
|
23
|
+
import {PortableTextEditorReadOnlyContext} from '../hooks/usePortableTextReadOnly'
|
|
24
|
+
import {useSyncValue} from '../hooks/useSyncValue'
|
|
25
|
+
import {PortableTextEditor} from '../PortableTextEditor'
|
|
26
|
+
|
|
27
|
+
const debug = debugWithName('component:PortableTextEditor:Synchronizer')
|
|
28
|
+
const debugVerbose = debug.enabled && false
|
|
29
|
+
|
|
30
|
+
// The editor will commit changes in a throttled fashion in order
|
|
31
|
+
// not to overload the network and degrade performance while typing.
|
|
32
|
+
const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export interface SynchronizerProps extends PropsWithChildren {
|
|
38
|
+
change$: EditorChanges
|
|
39
|
+
portableTextEditor: PortableTextEditor
|
|
40
|
+
keyGenerator: () => string
|
|
41
|
+
onChange: (change: EditorChange) => void
|
|
42
|
+
readOnly: boolean
|
|
43
|
+
value: PortableTextBlock[] | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Synchronizes the server value with the editor, and provides various contexts for the editor state.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export function Synchronizer(props: SynchronizerProps) {
|
|
51
|
+
const {change$, portableTextEditor, onChange, keyGenerator, readOnly, value} = props
|
|
52
|
+
const [selection, setSelection] = useState<EditorSelection>(null)
|
|
53
|
+
const pendingPatches = useRef<Patch[]>([])
|
|
54
|
+
|
|
55
|
+
const syncValue = useSyncValue({
|
|
56
|
+
keyGenerator,
|
|
57
|
+
onChange,
|
|
58
|
+
portableTextEditor,
|
|
59
|
+
readOnly,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const slateEditor = useSlate()
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, false)
|
|
66
|
+
}, [slateEditor])
|
|
67
|
+
|
|
68
|
+
const onFlushPendingPatches = useCallback(() => {
|
|
69
|
+
if (pendingPatches.current.length > 0) {
|
|
70
|
+
debug('Flushing pending patches')
|
|
71
|
+
if (debugVerbose) {
|
|
72
|
+
debug(`Patches:\n${JSON.stringify(pendingPatches.current, null, 2)}`)
|
|
73
|
+
}
|
|
74
|
+
const snapshot = PortableTextEditor.getValue(portableTextEditor)
|
|
75
|
+
change$.next({type: 'mutation', patches: pendingPatches.current, snapshot})
|
|
76
|
+
pendingPatches.current = []
|
|
77
|
+
}
|
|
78
|
+
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, false)
|
|
79
|
+
}, [slateEditor, portableTextEditor, change$])
|
|
80
|
+
|
|
81
|
+
const onFlushPendingPatchesThrottled = useMemo(() => {
|
|
82
|
+
return throttle(
|
|
83
|
+
() => {
|
|
84
|
+
// If the editor is normalizing (each operation) it means that it's not in the middle of a bigger transform,
|
|
85
|
+
// and we can flush these changes immediately.
|
|
86
|
+
if (Editor.isNormalizing(slateEditor)) {
|
|
87
|
+
onFlushPendingPatches()
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
// If it's in the middle of something, try again.
|
|
91
|
+
onFlushPendingPatchesThrottled()
|
|
92
|
+
},
|
|
93
|
+
FLUSH_PATCHES_THROTTLED_MS,
|
|
94
|
+
{
|
|
95
|
+
leading: false,
|
|
96
|
+
trailing: true,
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
}, [onFlushPendingPatches, slateEditor])
|
|
100
|
+
|
|
101
|
+
// Flush pending patches immediately on unmount
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
return () => {
|
|
104
|
+
onFlushPendingPatches()
|
|
105
|
+
}
|
|
106
|
+
}, [onFlushPendingPatches])
|
|
107
|
+
|
|
108
|
+
// Subscribe to, and handle changes from the editor
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
debug('Subscribing to editor changes$')
|
|
111
|
+
const sub = change$.subscribe((next: EditorChange): void => {
|
|
112
|
+
switch (next.type) {
|
|
113
|
+
case 'patch':
|
|
114
|
+
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, true)
|
|
115
|
+
pendingPatches.current.push(next.patch)
|
|
116
|
+
onFlushPendingPatchesThrottled()
|
|
117
|
+
onChange(next)
|
|
118
|
+
break
|
|
119
|
+
case 'selection':
|
|
120
|
+
// Set the selection state in a transition, we don't need the state immediately.
|
|
121
|
+
startTransition(() => {
|
|
122
|
+
if (debugVerbose) debug('Setting selection')
|
|
123
|
+
setSelection(next.selection)
|
|
124
|
+
})
|
|
125
|
+
onChange(next) // Keep this out of the startTransition!
|
|
126
|
+
break
|
|
127
|
+
default:
|
|
128
|
+
onChange(next)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
return () => {
|
|
132
|
+
debug('Unsubscribing to changes$')
|
|
133
|
+
sub.unsubscribe()
|
|
134
|
+
}
|
|
135
|
+
}, [change$, onChange, onFlushPendingPatchesThrottled, slateEditor])
|
|
136
|
+
|
|
137
|
+
// Sync the value when going online
|
|
138
|
+
const handleOnline = useCallback(() => {
|
|
139
|
+
debug('Editor is online, syncing from props.value')
|
|
140
|
+
change$.next({type: 'connection', value: 'online'})
|
|
141
|
+
syncValue(value)
|
|
142
|
+
}, [change$, syncValue, value])
|
|
143
|
+
|
|
144
|
+
const handleOffline = useCallback(() => {
|
|
145
|
+
debug('Editor is offline')
|
|
146
|
+
change$.next({type: 'connection', value: 'offline'})
|
|
147
|
+
}, [change$])
|
|
148
|
+
|
|
149
|
+
// Notify about window online and offline status changes
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (portableTextEditor.props.patches$) {
|
|
152
|
+
window.addEventListener('online', handleOnline)
|
|
153
|
+
window.addEventListener('offline', handleOffline)
|
|
154
|
+
}
|
|
155
|
+
return () => {
|
|
156
|
+
if (portableTextEditor.props.patches$) {
|
|
157
|
+
window.removeEventListener('online', handleOnline)
|
|
158
|
+
window.removeEventListener('offline', handleOffline)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// 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
|
+
// This will cause the editor to not be able to signal a validation error and offer invalid value resolution of the initial value.
|
|
165
|
+
const isInitialValueFromProps = useRef(true)
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
debug('Value from props changed, syncing new value')
|
|
168
|
+
syncValue(value)
|
|
169
|
+
// Signal that we have our first value, and are ready to roll.
|
|
170
|
+
if (isInitialValueFromProps.current) {
|
|
171
|
+
change$.next({type: 'loading', isLoading: false})
|
|
172
|
+
change$.next({type: 'ready'})
|
|
173
|
+
isInitialValueFromProps.current = false
|
|
174
|
+
}
|
|
175
|
+
}, [change$, syncValue, value])
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<PortableTextEditorKeyGeneratorContext.Provider value={keyGenerator}>
|
|
179
|
+
<PortableTextEditorContext.Provider value={portableTextEditor}>
|
|
180
|
+
<PortableTextEditorValueContext.Provider value={value}>
|
|
181
|
+
<PortableTextEditorReadOnlyContext.Provider value={readOnly}>
|
|
182
|
+
<PortableTextEditorSelectionContext.Provider value={selection}>
|
|
183
|
+
{props.children}
|
|
184
|
+
</PortableTextEditorSelectionContext.Provider>
|
|
185
|
+
</PortableTextEditorReadOnlyContext.Provider>
|
|
186
|
+
</PortableTextEditorValueContext.Provider>
|
|
187
|
+
</PortableTextEditorContext.Provider>
|
|
188
|
+
</PortableTextEditorKeyGeneratorContext.Provider>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {createContext, useContext} from 'react'
|
|
2
|
+
|
|
3
|
+
import {type PortableTextEditor} from '../PortableTextEditor'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A React context for sharing the editor object.
|
|
7
|
+
*/
|
|
8
|
+
export const PortableTextEditorContext = createContext<PortableTextEditor | null>(null)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get the current editor object from the React context.
|
|
12
|
+
*/
|
|
13
|
+
export const usePortableTextEditor = (): PortableTextEditor => {
|
|
14
|
+
const editor = useContext(PortableTextEditorContext)
|
|
15
|
+
|
|
16
|
+
if (!editor) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`The \`usePortableTextEditor\` hook must be used inside the <PortableTextEditor> component's context.`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return editor
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {randomKey} from '@sanity/util/content'
|
|
2
|
+
import {createContext, useContext} from 'react'
|
|
3
|
+
|
|
4
|
+
export const defaultKeyGenerator = (): string => randomKey(12)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React context for sharing the editor's keyGenerator.
|
|
8
|
+
*/
|
|
9
|
+
export const PortableTextEditorKeyGeneratorContext =
|
|
10
|
+
createContext<() => string>(defaultKeyGenerator)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the current editor selection from the React context.
|
|
14
|
+
*/
|
|
15
|
+
export const usePortableTextEditorKeyGenerator = (): (() => string) => {
|
|
16
|
+
const keyGenerator = useContext(PortableTextEditorKeyGeneratorContext)
|
|
17
|
+
|
|
18
|
+
if (keyGenerator === undefined) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`The \`usePortableTextEditorKeyGenerator\` hook must be used inside the <PortableTextEditor> component's context.`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
return keyGenerator
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {createContext, useContext} from 'react'
|
|
2
|
+
|
|
3
|
+
import {type EditorSelection} from '../../types/editor'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A React context for sharing the editor selection.
|
|
7
|
+
*/
|
|
8
|
+
export const PortableTextEditorSelectionContext = createContext<EditorSelection | null>(null)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get the current editor selection from the React context.
|
|
12
|
+
*/
|
|
13
|
+
export const usePortableTextEditorSelection = (): EditorSelection => {
|
|
14
|
+
const selection = useContext(PortableTextEditorSelectionContext)
|
|
15
|
+
|
|
16
|
+
if (selection === undefined) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`The \`usePortableTextEditorSelection\` hook must be used inside the <PortableTextEditor> component's context.`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
return selection
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {type PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import {createContext, useContext} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A React context for sharing the editor value.
|
|
6
|
+
*/
|
|
7
|
+
export const PortableTextEditorValueContext = createContext<PortableTextBlock[] | undefined>(
|
|
8
|
+
undefined,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the current editor value from the React context.
|
|
13
|
+
*/
|
|
14
|
+
export const usePortableTextEditorValue = () => {
|
|
15
|
+
return useContext(PortableTextEditorValueContext)
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {createContext, useContext} from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A React context for sharing the editor's readOnly status.
|
|
5
|
+
*/
|
|
6
|
+
export const PortableTextEditorReadOnlyContext = createContext<boolean>(false)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the current editor selection from the React context.
|
|
10
|
+
*/
|
|
11
|
+
export const usePortableTextEditorReadOnlyStatus = (): boolean => {
|
|
12
|
+
const readOnly = useContext(PortableTextEditorReadOnlyContext)
|
|
13
|
+
|
|
14
|
+
if (readOnly === undefined) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`The \`usePortableTextEditorReadOnly\` hook must be used inside the <PortableTextEditor> component's context.`,
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return readOnly
|
|
20
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {describe, expect, it, jest} from '@jest/globals'
|
|
2
|
+
import {render, waitFor} from '@testing-library/react'
|
|
3
|
+
import {createRef, type RefObject} from 'react'
|
|
4
|
+
|
|
5
|
+
import {PortableTextEditorTester, schemaType} from '../__tests__/PortableTextEditorTester'
|
|
6
|
+
import {PortableTextEditor} from '../PortableTextEditor'
|
|
7
|
+
|
|
8
|
+
const initialValue = [
|
|
9
|
+
{
|
|
10
|
+
_key: '77071c3af231',
|
|
11
|
+
_type: 'myTestBlockType',
|
|
12
|
+
children: [
|
|
13
|
+
{
|
|
14
|
+
_key: 'c001f0e92c1f0',
|
|
15
|
+
_type: 'span',
|
|
16
|
+
marks: [],
|
|
17
|
+
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
markDefs: [],
|
|
21
|
+
style: 'normal',
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
describe('useSyncValue', () => {
|
|
26
|
+
it('updates span text', async () => {
|
|
27
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
28
|
+
const onChange = jest.fn()
|
|
29
|
+
const syncedValue = [
|
|
30
|
+
{
|
|
31
|
+
_key: '77071c3af231',
|
|
32
|
+
_type: 'myTestBlockType',
|
|
33
|
+
children: [
|
|
34
|
+
{
|
|
35
|
+
_key: 'c001f0e92c1f0',
|
|
36
|
+
_type: 'span',
|
|
37
|
+
marks: [],
|
|
38
|
+
text: 'Lorem my ipsum!',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
markDefs: [],
|
|
42
|
+
style: 'normal',
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
const {rerender} = render(
|
|
46
|
+
<PortableTextEditorTester
|
|
47
|
+
onChange={onChange}
|
|
48
|
+
ref={editorRef}
|
|
49
|
+
schemaType={schemaType}
|
|
50
|
+
value={initialValue}
|
|
51
|
+
/>,
|
|
52
|
+
)
|
|
53
|
+
rerender(
|
|
54
|
+
<PortableTextEditorTester
|
|
55
|
+
onChange={onChange}
|
|
56
|
+
ref={editorRef}
|
|
57
|
+
schemaType={schemaType}
|
|
58
|
+
value={syncedValue}
|
|
59
|
+
/>,
|
|
60
|
+
)
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
if (editorRef.current) {
|
|
63
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual(syncedValue)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
it('replaces span nodes with different keys inside the same children array', async () => {
|
|
68
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
69
|
+
const onChange = jest.fn()
|
|
70
|
+
const syncedValue = [
|
|
71
|
+
{
|
|
72
|
+
_key: '77071c3af231',
|
|
73
|
+
_type: 'myTestBlockType',
|
|
74
|
+
children: [
|
|
75
|
+
{
|
|
76
|
+
_key: 'c001f0e92c1f0__NEW_KEY_YA!',
|
|
77
|
+
_type: 'span',
|
|
78
|
+
marks: [],
|
|
79
|
+
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
markDefs: [],
|
|
83
|
+
style: 'normal',
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
const {rerender} = render(
|
|
87
|
+
<PortableTextEditorTester
|
|
88
|
+
onChange={onChange}
|
|
89
|
+
ref={editorRef}
|
|
90
|
+
schemaType={schemaType}
|
|
91
|
+
value={initialValue}
|
|
92
|
+
/>,
|
|
93
|
+
)
|
|
94
|
+
rerender(
|
|
95
|
+
<PortableTextEditorTester
|
|
96
|
+
onChange={onChange}
|
|
97
|
+
ref={editorRef}
|
|
98
|
+
schemaType={schemaType}
|
|
99
|
+
value={syncedValue}
|
|
100
|
+
/>,
|
|
101
|
+
)
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
if (editorRef.current) {
|
|
104
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toMatchInlineSnapshot(`
|
|
105
|
+
Array [
|
|
106
|
+
Object {
|
|
107
|
+
"_key": "77071c3af231",
|
|
108
|
+
"_type": "myTestBlockType",
|
|
109
|
+
"children": Array [
|
|
110
|
+
Object {
|
|
111
|
+
"_key": "c001f0e92c1f0__NEW_KEY_YA!",
|
|
112
|
+
"_type": "span",
|
|
113
|
+
"marks": Array [],
|
|
114
|
+
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
"markDefs": Array [],
|
|
118
|
+
"style": "normal",
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
`)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|