@portabletext/editor 1.0.19 → 1.1.1
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/index.d.mts +142 -67
- package/lib/index.d.ts +142 -67
- package/lib/index.esm.js +1130 -371
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1130 -371
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1130 -371
- package/lib/index.mjs.map +1 -1
- package/package.json +4 -18
- package/src/editor/Editable.tsx +128 -55
- package/src/editor/PortableTextEditor.tsx +66 -32
- package/src/editor/__tests__/PortableTextEditor.test.tsx +44 -18
- package/src/editor/__tests__/PortableTextEditorTester.tsx +50 -38
- package/src/editor/__tests__/RangeDecorations.test.tsx +4 -6
- package/src/editor/__tests__/handleClick.test.tsx +28 -9
- package/src/editor/__tests__/insert-block.test.tsx +24 -8
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +31 -63
- package/src/editor/__tests__/utils.ts +10 -4
- package/src/editor/components/DraggableBlock.tsx +36 -13
- package/src/editor/components/Element.tsx +73 -33
- package/src/editor/components/Leaf.tsx +114 -76
- package/src/editor/components/SlateContainer.tsx +14 -7
- package/src/editor/components/Synchronizer.tsx +8 -5
- package/src/editor/hooks/usePortableTextEditor.ts +3 -3
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +10 -4
- package/src/editor/hooks/useSyncValue.test.tsx +9 -4
- package/src/editor/hooks/useSyncValue.ts +198 -133
- package/src/editor/nodes/DefaultAnnotation.tsx +6 -4
- package/src/editor/nodes/DefaultObject.tsx +1 -1
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +23 -8
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +26 -9
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +15 -5
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +60 -19
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +5 -3
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +4 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +61 -19
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +6 -3
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +30 -13
- package/src/editor/plugins/createWithEditableAPI.ts +361 -131
- package/src/editor/plugins/createWithHotKeys.ts +46 -130
- package/src/editor/plugins/createWithInsertBreak.ts +167 -28
- package/src/editor/plugins/createWithInsertData.ts +66 -30
- package/src/editor/plugins/createWithMaxBlocks.ts +6 -3
- package/src/editor/plugins/createWithObjectKeys.ts +7 -3
- package/src/editor/plugins/createWithPatches.ts +66 -24
- package/src/editor/plugins/createWithPlaceholderBlock.ts +9 -5
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +17 -7
- package/src/editor/plugins/createWithPortableTextLists.ts +21 -9
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +217 -52
- package/src/editor/plugins/createWithPortableTextSelections.ts +11 -9
- package/src/editor/plugins/createWithSchemaTypes.ts +26 -10
- package/src/editor/plugins/createWithUndoRedo.ts +106 -27
- package/src/editor/plugins/createWithUtils.ts +33 -11
- package/src/editor/plugins/index.ts +34 -13
- package/src/types/editor.ts +73 -44
- package/src/types/options.ts +7 -5
- package/src/types/slate.ts +6 -6
- package/src/utils/__tests__/dmpToOperations.test.ts +41 -16
- package/src/utils/__tests__/operationToPatches.test.ts +4 -3
- package/src/utils/__tests__/patchToOperations.test.ts +16 -5
- package/src/utils/__tests__/ranges.test.ts +9 -4
- package/src/utils/__tests__/valueNormalization.test.tsx +12 -4
- package/src/utils/__tests__/values.test.ts +0 -1
- package/src/utils/applyPatch.ts +78 -29
- package/src/utils/getPortableTextMemberSchemaTypes.ts +38 -23
- package/src/utils/operationToPatches.ts +123 -44
- package/src/utils/paths.ts +26 -9
- package/src/utils/ranges.ts +16 -10
- package/src/utils/selection.ts +21 -9
- package/src/utils/ucs2Indices.ts +2 -2
- package/src/utils/validateValue.ts +118 -45
- package/src/utils/values.ts +38 -17
- package/src/utils/weakMaps.ts +20 -10
- package/src/utils/withChanges.ts +5 -3
- package/src/utils/withUndoRedo.ts +1 -1
- package/src/utils/withoutPatching.ts +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {describe, expect, it, jest} from '@jest/globals'
|
|
2
2
|
import {render, waitFor} from '@testing-library/react'
|
|
3
3
|
import {createRef, type RefObject} from 'react'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
PortableTextEditorTester,
|
|
6
|
+
schemaType,
|
|
7
|
+
} from '../__tests__/PortableTextEditorTester'
|
|
6
8
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
7
9
|
|
|
8
10
|
const initialValue = [
|
|
@@ -60,7 +62,9 @@ describe('useSyncValue', () => {
|
|
|
60
62
|
)
|
|
61
63
|
await waitFor(() => {
|
|
62
64
|
if (editorRef.current) {
|
|
63
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual(
|
|
65
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual(
|
|
66
|
+
syncedValue,
|
|
67
|
+
)
|
|
64
68
|
}
|
|
65
69
|
})
|
|
66
70
|
})
|
|
@@ -101,7 +105,8 @@ describe('useSyncValue', () => {
|
|
|
101
105
|
)
|
|
102
106
|
await waitFor(() => {
|
|
103
107
|
if (editorRef.current) {
|
|
104
|
-
expect(PortableTextEditor.getValue(editorRef.current))
|
|
108
|
+
expect(PortableTextEditor.getValue(editorRef.current))
|
|
109
|
+
.toMatchInlineSnapshot(`
|
|
105
110
|
Array [
|
|
106
111
|
Object {
|
|
107
112
|
"_key": "77071c3af231",
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import {type PortableTextBlock} from '@sanity/types'
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
3
2
|
import {debounce, isEqual} from 'lodash'
|
|
4
3
|
import {useCallback, useMemo, useRef} from 'react'
|
|
5
|
-
import {
|
|
4
|
+
import {Editor, Text, Transforms, type Descendant, type Node} from 'slate'
|
|
6
5
|
import {useSlate} from 'slate-react'
|
|
7
|
-
|
|
8
|
-
import {type EditorChange, type PortableTextSlateEditor} from '../../types/editor'
|
|
6
|
+
import type {EditorChange, PortableTextSlateEditor} from '../../types/editor'
|
|
9
7
|
import {debugWithName} from '../../utils/debug'
|
|
10
8
|
import {validateValue} from '../../utils/validateValue'
|
|
11
9
|
import {toSlateValue, VOID_CHILD_KEY} from '../../utils/values'
|
|
12
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isChangingLocally,
|
|
12
|
+
isChangingRemotely,
|
|
13
|
+
withRemoteChanges,
|
|
14
|
+
} from '../../utils/withChanges'
|
|
13
15
|
import {withoutPatching} from '../../utils/withoutPatching'
|
|
14
16
|
import {withoutSaving} from '../plugins/createWithUndoRedo'
|
|
15
|
-
import
|
|
17
|
+
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
16
18
|
|
|
17
19
|
const debug = debugWithName('hook:useSyncValue')
|
|
18
20
|
|
|
@@ -26,7 +28,10 @@ export interface UseSyncValueProps {
|
|
|
26
28
|
readOnly: boolean
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
const CURRENT_VALUE = new WeakMap<
|
|
31
|
+
const CURRENT_VALUE = new WeakMap<
|
|
32
|
+
PortableTextEditor,
|
|
33
|
+
PortableTextBlock[] | undefined
|
|
34
|
+
>()
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
37
|
* Sync value with the editor state
|
|
@@ -42,12 +47,16 @@ const CURRENT_VALUE = new WeakMap<PortableTextEditor, PortableTextBlock[] | unde
|
|
|
42
47
|
*/
|
|
43
48
|
export function useSyncValue(
|
|
44
49
|
props: UseSyncValueProps,
|
|
45
|
-
): (
|
|
50
|
+
): (
|
|
51
|
+
value: PortableTextBlock[] | undefined,
|
|
52
|
+
userCallbackFn?: () => void,
|
|
53
|
+
) => void {
|
|
46
54
|
const {portableTextEditor, readOnly, keyGenerator} = props
|
|
47
55
|
const {change$, schemaTypes} = portableTextEditor
|
|
48
56
|
const previousValue = useRef<PortableTextBlock[] | undefined>()
|
|
49
57
|
const slateEditor = useSlate()
|
|
50
|
-
const updateValueFunctionRef =
|
|
58
|
+
const updateValueFunctionRef =
|
|
59
|
+
useRef<(value: PortableTextBlock[] | undefined) => void>()
|
|
51
60
|
|
|
52
61
|
const updateFromCurrentValue = useCallback(() => {
|
|
53
62
|
const currentValue = CURRENT_VALUE.get(portableTextEditor)
|
|
@@ -61,7 +70,8 @@ export function useSyncValue(
|
|
|
61
70
|
}
|
|
62
71
|
}, [portableTextEditor])
|
|
63
72
|
const updateValueDebounced = useMemo(
|
|
64
|
-
() =>
|
|
73
|
+
() =>
|
|
74
|
+
debounce(updateFromCurrentValue, 1000, {trailing: true, leading: false}),
|
|
65
75
|
[updateFromCurrentValue],
|
|
66
76
|
)
|
|
67
77
|
|
|
@@ -103,7 +113,11 @@ export function useSyncValue(
|
|
|
103
113
|
at: [childrenLength - 1 - index],
|
|
104
114
|
})
|
|
105
115
|
})
|
|
106
|
-
Transforms.insertNodes(
|
|
116
|
+
Transforms.insertNodes(
|
|
117
|
+
slateEditor,
|
|
118
|
+
slateEditor.pteCreateTextBlock({decorators: []}),
|
|
119
|
+
{at: [0]},
|
|
120
|
+
)
|
|
107
121
|
// Add a new selection in the top of the document
|
|
108
122
|
if (hadSelection) {
|
|
109
123
|
Transforms.select(slateEditor, [0, 0])
|
|
@@ -125,7 +139,11 @@ export function useSyncValue(
|
|
|
125
139
|
const childrenLength = slateEditor.children.length
|
|
126
140
|
// Remove blocks that have become superfluous
|
|
127
141
|
if (slateValueFromProps.length < childrenLength) {
|
|
128
|
-
for (
|
|
142
|
+
for (
|
|
143
|
+
let i = childrenLength - 1;
|
|
144
|
+
i > slateValueFromProps.length - 1;
|
|
145
|
+
i--
|
|
146
|
+
) {
|
|
129
147
|
Transforms.removeNodes(slateEditor, {
|
|
130
148
|
at: [i],
|
|
131
149
|
})
|
|
@@ -133,70 +151,102 @@ export function useSyncValue(
|
|
|
133
151
|
isChanged = true
|
|
134
152
|
}
|
|
135
153
|
// Go through all of the blocks and see if they need to be updated
|
|
136
|
-
slateValueFromProps.forEach(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
slateValueFromProps.forEach(
|
|
155
|
+
(currentBlock, currentBlockIndex) => {
|
|
156
|
+
const oldBlock = slateEditor.children[currentBlockIndex]
|
|
157
|
+
const hasChanges =
|
|
158
|
+
oldBlock && !isEqual(currentBlock, oldBlock)
|
|
159
|
+
if (hasChanges && isValid) {
|
|
160
|
+
const validationValue = [value[currentBlockIndex]]
|
|
161
|
+
const validation = validateValue(
|
|
162
|
+
validationValue,
|
|
163
|
+
schemaTypes,
|
|
164
|
+
keyGenerator,
|
|
165
|
+
)
|
|
166
|
+
// Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
|
|
167
|
+
if (
|
|
168
|
+
!validation.valid &&
|
|
169
|
+
validation.resolution?.autoResolve &&
|
|
170
|
+
validation.resolution?.patches.length > 0
|
|
171
|
+
) {
|
|
172
|
+
// Only apply auto resolution if the value has been populated before and is different from the last one.
|
|
173
|
+
if (
|
|
174
|
+
!readOnly &&
|
|
175
|
+
previousValue.current &&
|
|
176
|
+
previousValue.current !== value
|
|
177
|
+
) {
|
|
178
|
+
// Give a console warning about the fact that it did an auto resolution
|
|
179
|
+
console.warn(
|
|
180
|
+
`${validation.resolution.action} for block with _key '${validationValue[0]._key}'. ${validation.resolution?.description}`,
|
|
181
|
+
)
|
|
182
|
+
validation.resolution.patches.forEach((patch) => {
|
|
183
|
+
change$.next({type: 'patch', patch})
|
|
184
|
+
})
|
|
185
|
+
}
|
|
157
186
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
if (
|
|
188
|
+
validation.valid ||
|
|
189
|
+
validation.resolution?.autoResolve
|
|
190
|
+
) {
|
|
191
|
+
if (oldBlock._key === currentBlock._key) {
|
|
192
|
+
if (debug.enabled)
|
|
193
|
+
debug('Updating block', oldBlock, currentBlock)
|
|
194
|
+
_updateBlock(
|
|
195
|
+
slateEditor,
|
|
196
|
+
currentBlock,
|
|
197
|
+
oldBlock,
|
|
198
|
+
currentBlockIndex,
|
|
199
|
+
)
|
|
200
|
+
} else {
|
|
201
|
+
if (debug.enabled)
|
|
202
|
+
debug('Replacing block', oldBlock, currentBlock)
|
|
203
|
+
_replaceBlock(
|
|
204
|
+
slateEditor,
|
|
205
|
+
currentBlock,
|
|
206
|
+
currentBlockIndex,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
isChanged = true
|
|
163
210
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
211
|
+
change$.next({
|
|
212
|
+
type: 'invalidValue',
|
|
213
|
+
resolution: validation.resolution,
|
|
214
|
+
value,
|
|
215
|
+
})
|
|
216
|
+
isValid = false
|
|
166
217
|
}
|
|
167
|
-
isChanged = true
|
|
168
|
-
} else {
|
|
169
|
-
change$.next({
|
|
170
|
-
type: 'invalidValue',
|
|
171
|
-
resolution: validation.resolution,
|
|
172
|
-
value,
|
|
173
|
-
})
|
|
174
|
-
isValid = false
|
|
175
218
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
'Validating and inserting new block in the end of the value',
|
|
183
|
-
currentBlock,
|
|
219
|
+
if (!oldBlock && isValid) {
|
|
220
|
+
const validationValue = [value[currentBlockIndex]]
|
|
221
|
+
const validation = validateValue(
|
|
222
|
+
validationValue,
|
|
223
|
+
schemaTypes,
|
|
224
|
+
keyGenerator,
|
|
184
225
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
226
|
+
if (debug.enabled)
|
|
227
|
+
debug(
|
|
228
|
+
'Validating and inserting new block in the end of the value',
|
|
229
|
+
currentBlock,
|
|
230
|
+
)
|
|
231
|
+
if (
|
|
232
|
+
validation.valid ||
|
|
233
|
+
validation.resolution?.autoResolve
|
|
234
|
+
) {
|
|
235
|
+
Transforms.insertNodes(slateEditor, currentBlock, {
|
|
236
|
+
at: [currentBlockIndex],
|
|
237
|
+
})
|
|
238
|
+
} else {
|
|
239
|
+
debug('Invalid', validation)
|
|
240
|
+
change$.next({
|
|
241
|
+
type: 'invalidValue',
|
|
242
|
+
resolution: validation.resolution,
|
|
243
|
+
value,
|
|
244
|
+
})
|
|
245
|
+
isValid = false
|
|
246
|
+
}
|
|
197
247
|
}
|
|
198
|
-
}
|
|
199
|
-
|
|
248
|
+
},
|
|
249
|
+
)
|
|
200
250
|
})
|
|
201
251
|
})
|
|
202
252
|
})
|
|
@@ -286,78 +336,93 @@ function _updateBlock(
|
|
|
286
336
|
at: [currentBlockIndex],
|
|
287
337
|
})
|
|
288
338
|
// Text block's need to have their children updated as well (setNode does not target a node's children)
|
|
289
|
-
if (
|
|
339
|
+
if (
|
|
340
|
+
slateEditor.isTextBlock(currentBlock) &&
|
|
341
|
+
slateEditor.isTextBlock(oldBlock)
|
|
342
|
+
) {
|
|
290
343
|
const oldBlockChildrenLength = oldBlock.children.length
|
|
291
344
|
if (currentBlock.children.length < oldBlockChildrenLength) {
|
|
292
345
|
// Remove any children that have become superfluous
|
|
293
|
-
Array.from(
|
|
294
|
-
(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
)
|
|
304
|
-
}
|
|
305
|
-
currentBlock.children.forEach((currentBlockChild, currentBlockChildIndex) => {
|
|
306
|
-
const oldBlockChild = oldBlock.children[currentBlockChildIndex]
|
|
307
|
-
const isChildChanged = !isEqual(currentBlockChild, oldBlockChild)
|
|
308
|
-
const isTextChanged = !isEqual(currentBlockChild.text, oldBlockChild?.text)
|
|
309
|
-
const path = [currentBlockIndex, currentBlockChildIndex]
|
|
310
|
-
if (isChildChanged) {
|
|
311
|
-
// Update if this is the same child
|
|
312
|
-
if (currentBlockChild._key === oldBlockChild?._key) {
|
|
313
|
-
debug('Updating changed child', currentBlockChild, oldBlockChild)
|
|
314
|
-
Transforms.setNodes(slateEditor, currentBlockChild as Partial<Node>, {
|
|
315
|
-
at: path,
|
|
346
|
+
Array.from(
|
|
347
|
+
Array(oldBlockChildrenLength - currentBlock.children.length),
|
|
348
|
+
).forEach((_, index) => {
|
|
349
|
+
const childIndex = oldBlockChildrenLength - 1 - index
|
|
350
|
+
if (childIndex > 0) {
|
|
351
|
+
debug('Removing child')
|
|
352
|
+
Transforms.removeNodes(slateEditor, {
|
|
353
|
+
at: [currentBlockIndex, childIndex],
|
|
316
354
|
})
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
debug('Updating changed
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
currentBlock.children.forEach(
|
|
359
|
+
(currentBlockChild, currentBlockChildIndex) => {
|
|
360
|
+
const oldBlockChild = oldBlock.children[currentBlockChildIndex]
|
|
361
|
+
const isChildChanged = !isEqual(currentBlockChild, oldBlockChild)
|
|
362
|
+
const isTextChanged = !isEqual(
|
|
363
|
+
currentBlockChild.text,
|
|
364
|
+
oldBlockChild?.text,
|
|
365
|
+
)
|
|
366
|
+
const path = [currentBlockIndex, currentBlockChildIndex]
|
|
367
|
+
if (isChildChanged) {
|
|
368
|
+
// Update if this is the same child
|
|
369
|
+
if (currentBlockChild._key === oldBlockChild?._key) {
|
|
370
|
+
debug('Updating changed child', currentBlockChild, oldBlockChild)
|
|
333
371
|
Transforms.setNodes(
|
|
334
372
|
slateEditor,
|
|
335
|
-
|
|
373
|
+
currentBlockChild as Partial<Node>,
|
|
336
374
|
{
|
|
337
|
-
at:
|
|
338
|
-
voids: true,
|
|
375
|
+
at: path,
|
|
339
376
|
},
|
|
340
377
|
)
|
|
378
|
+
const isSpanNode =
|
|
379
|
+
Text.isText(currentBlockChild) &&
|
|
380
|
+
currentBlockChild._type === 'span' &&
|
|
381
|
+
Text.isText(oldBlockChild) &&
|
|
382
|
+
oldBlockChild._type === 'span'
|
|
383
|
+
if (isSpanNode && isTextChanged) {
|
|
384
|
+
Transforms.delete(slateEditor, {
|
|
385
|
+
at: {
|
|
386
|
+
focus: {path, offset: 0},
|
|
387
|
+
anchor: {path, offset: oldBlockChild.text.length},
|
|
388
|
+
},
|
|
389
|
+
})
|
|
390
|
+
Transforms.insertText(slateEditor, currentBlockChild.text, {
|
|
391
|
+
at: path,
|
|
392
|
+
})
|
|
393
|
+
slateEditor.onChange()
|
|
394
|
+
} else if (!isSpanNode) {
|
|
395
|
+
// If it's a inline block, also update the void text node key
|
|
396
|
+
debug('Updating changed inline object child', currentBlockChild)
|
|
397
|
+
Transforms.setNodes(
|
|
398
|
+
slateEditor,
|
|
399
|
+
{_key: VOID_CHILD_KEY},
|
|
400
|
+
{
|
|
401
|
+
at: [...path, 0],
|
|
402
|
+
voids: true,
|
|
403
|
+
},
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
// Replace the child if _key's are different
|
|
407
|
+
} else if (oldBlockChild) {
|
|
408
|
+
debug('Replacing child', currentBlockChild)
|
|
409
|
+
Transforms.removeNodes(slateEditor, {
|
|
410
|
+
at: [currentBlockIndex, currentBlockChildIndex],
|
|
411
|
+
})
|
|
412
|
+
Transforms.insertNodes(slateEditor, currentBlockChild as Node, {
|
|
413
|
+
at: [currentBlockIndex, currentBlockChildIndex],
|
|
414
|
+
})
|
|
415
|
+
slateEditor.onChange()
|
|
416
|
+
// Insert it if it didn't exist before
|
|
417
|
+
} else if (!oldBlockChild) {
|
|
418
|
+
debug('Inserting new child', currentBlockChild)
|
|
419
|
+
Transforms.insertNodes(slateEditor, currentBlockChild as Node, {
|
|
420
|
+
at: [currentBlockIndex, currentBlockChildIndex],
|
|
421
|
+
})
|
|
422
|
+
slateEditor.onChange()
|
|
341
423
|
}
|
|
342
|
-
// Replace the child if _key's are different
|
|
343
|
-
} else if (oldBlockChild) {
|
|
344
|
-
debug('Replacing child', currentBlockChild)
|
|
345
|
-
Transforms.removeNodes(slateEditor, {
|
|
346
|
-
at: [currentBlockIndex, currentBlockChildIndex],
|
|
347
|
-
})
|
|
348
|
-
Transforms.insertNodes(slateEditor, currentBlockChild as Node, {
|
|
349
|
-
at: [currentBlockIndex, currentBlockChildIndex],
|
|
350
|
-
})
|
|
351
|
-
slateEditor.onChange()
|
|
352
|
-
// Insert it if it didn't exist before
|
|
353
|
-
} else if (!oldBlockChild) {
|
|
354
|
-
debug('Inserting new child', currentBlockChild)
|
|
355
|
-
Transforms.insertNodes(slateEditor, currentBlockChild as Node, {
|
|
356
|
-
at: [currentBlockIndex, currentBlockChildIndex],
|
|
357
|
-
})
|
|
358
|
-
slateEditor.onChange()
|
|
359
424
|
}
|
|
360
|
-
}
|
|
361
|
-
|
|
425
|
+
},
|
|
426
|
+
)
|
|
362
427
|
}
|
|
363
428
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {type ReactNode
|
|
1
|
+
import type {PortableTextObject} from '@sanity/types'
|
|
2
|
+
import {useCallback, type ReactNode} from 'react'
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
annotation: PortableTextObject
|
|
6
6
|
children: ReactNode
|
|
7
7
|
}
|
|
8
8
|
export function DefaultAnnotation(props: Props) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const handleClick = useCallback(
|
|
10
|
+
() => alert(JSON.stringify(props.annotation)),
|
|
11
|
+
[props.annotation],
|
|
12
|
+
)
|
|
11
13
|
return (
|
|
12
14
|
<span style={{color: 'blue'}} onClick={handleClick}>
|
|
13
15
|
{props.children}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {describe, expect, it} from '@jest/globals'
|
|
2
2
|
import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types'
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import type {Descendant} from 'slate'
|
|
5
4
|
import {exportedForTesting} from '../createWithInsertData'
|
|
6
5
|
|
|
7
6
|
const initialValue = [
|
|
@@ -57,10 +56,18 @@ describe('plugin: createWithInsertData _regenerateKeys', () => {
|
|
|
57
56
|
'span',
|
|
58
57
|
{
|
|
59
58
|
annotations: [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
{
|
|
60
|
+
name: 'color',
|
|
61
|
+
jsonType: 'object',
|
|
62
|
+
fields: [],
|
|
63
|
+
__experimental_search: [],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'link',
|
|
67
|
+
jsonType: 'object',
|
|
68
|
+
fields: [],
|
|
69
|
+
__experimental_search: [],
|
|
70
|
+
},
|
|
64
71
|
],
|
|
65
72
|
},
|
|
66
73
|
)
|
|
@@ -140,8 +147,16 @@ describe('plugin: createWithInsertData _regenerateKeys', () => {
|
|
|
140
147
|
return `k${keyCursor}`
|
|
141
148
|
},
|
|
142
149
|
'span',
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
{
|
|
151
|
+
annotations: [
|
|
152
|
+
{
|
|
153
|
+
name: 'color',
|
|
154
|
+
jsonType: 'object',
|
|
155
|
+
fields: [],
|
|
156
|
+
__experimental_search: [],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
145
160
|
)
|
|
146
161
|
|
|
147
162
|
// orphaned children marks are removed later in the normalize function
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {describe, expect, it, jest} from '@jest/globals'
|
|
2
2
|
import {render, waitFor} from '@testing-library/react'
|
|
3
3
|
import {createRef, type RefObject} from 'react'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
PortableTextEditorTester,
|
|
6
|
+
schemaType,
|
|
7
|
+
} from '../../__tests__/PortableTextEditorTester'
|
|
6
8
|
import {PortableTextEditor} from '../../PortableTextEditor'
|
|
7
9
|
|
|
8
10
|
const initialValue = [
|
|
@@ -56,7 +58,10 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
56
58
|
|
|
57
59
|
await waitFor(() => {
|
|
58
60
|
if (editorRef.current) {
|
|
59
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
61
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
62
|
+
type: 'value',
|
|
63
|
+
value: initialValue,
|
|
64
|
+
})
|
|
60
65
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
61
66
|
}
|
|
62
67
|
})
|
|
@@ -70,7 +75,8 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
70
75
|
PortableTextEditor.getSelection(editorRef.current),
|
|
71
76
|
{mode: 'blocks'},
|
|
72
77
|
)
|
|
73
|
-
expect(PortableTextEditor.getValue(editorRef.current))
|
|
78
|
+
expect(PortableTextEditor.getValue(editorRef.current))
|
|
79
|
+
.toMatchInlineSnapshot(`
|
|
74
80
|
Array [
|
|
75
81
|
Object {
|
|
76
82
|
"_key": "a",
|
|
@@ -105,7 +111,10 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
105
111
|
)
|
|
106
112
|
|
|
107
113
|
await waitFor(() => {
|
|
108
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
114
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
115
|
+
type: 'value',
|
|
116
|
+
value: initialValue,
|
|
117
|
+
})
|
|
109
118
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
110
119
|
})
|
|
111
120
|
|
|
@@ -124,7 +133,8 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
124
133
|
await waitFor(() => {
|
|
125
134
|
if (editorRef.current) {
|
|
126
135
|
// New keys here confirms that a placeholder block has been created
|
|
127
|
-
expect(PortableTextEditor.getValue(editorRef.current))
|
|
136
|
+
expect(PortableTextEditor.getValue(editorRef.current))
|
|
137
|
+
.toMatchInlineSnapshot(`
|
|
128
138
|
Array [
|
|
129
139
|
Object {
|
|
130
140
|
"_key": "1",
|
|
@@ -161,7 +171,10 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
161
171
|
|
|
162
172
|
await waitFor(() => {
|
|
163
173
|
if (editorRef.current) {
|
|
164
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
174
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
175
|
+
type: 'value',
|
|
176
|
+
value: initialValue,
|
|
177
|
+
})
|
|
165
178
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
166
179
|
}
|
|
167
180
|
})
|
|
@@ -187,7 +200,8 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
187
200
|
|
|
188
201
|
await waitFor(() => {
|
|
189
202
|
if (editorRef.current) {
|
|
190
|
-
expect(PortableTextEditor.getValue(editorRef.current))
|
|
203
|
+
expect(PortableTextEditor.getValue(editorRef.current))
|
|
204
|
+
.toMatchInlineSnapshot(`
|
|
191
205
|
Array [
|
|
192
206
|
Object {
|
|
193
207
|
"_key": "a",
|
|
@@ -238,7 +252,10 @@ describe('plugin:withEditableAPI: .delete()', () => {
|
|
|
238
252
|
|
|
239
253
|
await waitFor(() => {
|
|
240
254
|
if (editorRef.current) {
|
|
241
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
255
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
256
|
+
type: 'value',
|
|
257
|
+
value: initialValue,
|
|
258
|
+
})
|
|
242
259
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
243
260
|
}
|
|
244
261
|
})
|
|
@@ -2,8 +2,10 @@ import {describe, expect, it, jest} from '@jest/globals'
|
|
|
2
2
|
import {isPortableTextTextBlock} from '@sanity/types'
|
|
3
3
|
import {render, waitFor} from '@testing-library/react'
|
|
4
4
|
import {createRef, type RefObject} from 'react'
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
PortableTextEditorTester,
|
|
7
|
+
schemaType,
|
|
8
|
+
} from '../../__tests__/PortableTextEditorTester'
|
|
7
9
|
import {PortableTextEditor} from '../../PortableTextEditor'
|
|
8
10
|
|
|
9
11
|
const initialValue = [
|
|
@@ -67,7 +69,10 @@ describe('plugin:withEditableAPI: .getFragment()', () => {
|
|
|
67
69
|
|
|
68
70
|
await waitFor(() => {
|
|
69
71
|
if (editorRef.current) {
|
|
70
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
72
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
73
|
+
type: 'value',
|
|
74
|
+
value: initialValue,
|
|
75
|
+
})
|
|
71
76
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
72
77
|
}
|
|
73
78
|
})
|
|
@@ -78,7 +83,9 @@ describe('plugin:withEditableAPI: .getFragment()', () => {
|
|
|
78
83
|
PortableTextEditor.select(editorRef.current, initialSelection)
|
|
79
84
|
const fragment = PortableTextEditor.getFragment(editorRef.current)
|
|
80
85
|
expect(
|
|
81
|
-
fragment &&
|
|
86
|
+
fragment &&
|
|
87
|
+
isPortableTextTextBlock(fragment[0]) &&
|
|
88
|
+
fragment[0]?.children[0]?.text,
|
|
82
89
|
).toBe('A')
|
|
83
90
|
}
|
|
84
91
|
})
|
|
@@ -103,7 +110,10 @@ describe('plugin:withEditableAPI: .getFragment()', () => {
|
|
|
103
110
|
|
|
104
111
|
await waitFor(() => {
|
|
105
112
|
if (editorRef.current) {
|
|
106
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
113
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
114
|
+
type: 'value',
|
|
115
|
+
value: initialValue,
|
|
116
|
+
})
|
|
107
117
|
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
108
118
|
}
|
|
109
119
|
})
|