@portabletext/editor 2.9.2 → 2.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/lib/_chunks-cjs/util.merge-text-blocks.cjs +1 -0
- package/lib/_chunks-cjs/util.merge-text-blocks.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs +6 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-dts/behavior.types.action.d.cts +79 -69
- package/lib/_chunks-dts/behavior.types.action.d.ts +79 -69
- package/lib/_chunks-es/util.merge-text-blocks.js +1 -0
- package/lib/_chunks-es/util.merge-text-blocks.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js +6 -1
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/index.cjs +232 -68
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +234 -70
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +3 -3
- package/lib/plugins/index.d.ts +3 -3
- package/lib/utils/index.d.cts +2 -2
- package/package.json +10 -10
- package/src/behaviors/behavior.abstract.insert.ts +109 -24
- package/src/behaviors/behavior.abstract.split.ts +1 -0
- package/src/behaviors/behavior.perform-event.ts +89 -67
- package/src/behaviors/behavior.types.event.ts +10 -3
- package/src/converters/converter.portable-text.ts +1 -0
- package/src/converters/converter.text-html.ts +1 -0
- package/src/converters/converter.text-plain.ts +1 -0
- package/src/editor/Editable.tsx +1 -0
- package/src/editor/editor-selector.ts +10 -1
- package/src/editor/without-normalizing-conditional.ts +13 -0
- package/src/internal-utils/parse-blocks.test.ts +14 -14
- package/src/internal-utils/parse-blocks.ts +9 -2
- package/src/internal-utils/test-editor.tsx +1 -25
- package/src/operations/behavior.operation.block.set.ts +4 -3
- package/src/operations/behavior.operation.block.unset.ts +8 -2
- package/src/operations/behavior.operation.insert.block.ts +4 -1
- package/src/operations/behavior.operation.insert.child.ts +95 -0
- package/src/operations/behavior.operations.ts +9 -0
- package/src/selectors/selector.get-trimmed-selection.test.ts +1 -0
- package/src/types/block-with-optional-key.ts +13 -1
- package/src/utils/util.merge-text-blocks.ts +1 -1
- package/src/utils/util.slice-blocks.ts +3 -3
- package/src/utils/util.slice-text-block.test.ts +54 -28
- package/src/plugins/plugin.internal.editor-actor-ref.tsx +0 -15
|
@@ -12,7 +12,7 @@ describe(parseBlock.name, () => {
|
|
|
12
12
|
keyGenerator: createTestKeyGenerator(),
|
|
13
13
|
schema: compileSchema(defineSchema({})),
|
|
14
14
|
},
|
|
15
|
-
options: {validateFields: true},
|
|
15
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
16
16
|
}),
|
|
17
17
|
).toBe(undefined)
|
|
18
18
|
})
|
|
@@ -25,7 +25,7 @@ describe(parseBlock.name, () => {
|
|
|
25
25
|
keyGenerator: createTestKeyGenerator(),
|
|
26
26
|
schema: compileSchema(defineSchema({})),
|
|
27
27
|
},
|
|
28
|
-
options: {validateFields: true},
|
|
28
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
29
29
|
}),
|
|
30
30
|
).toBe(undefined)
|
|
31
31
|
})
|
|
@@ -39,7 +39,7 @@ describe(parseBlock.name, () => {
|
|
|
39
39
|
keyGenerator: createTestKeyGenerator(),
|
|
40
40
|
schema: compileSchema(defineSchema({})),
|
|
41
41
|
},
|
|
42
|
-
options: {validateFields: true},
|
|
42
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
43
43
|
}),
|
|
44
44
|
).toBe(undefined)
|
|
45
45
|
})
|
|
@@ -54,7 +54,7 @@ describe(parseBlock.name, () => {
|
|
|
54
54
|
defineSchema({blockObjects: [{name: 'image'}]}),
|
|
55
55
|
),
|
|
56
56
|
},
|
|
57
|
-
options: {validateFields: true},
|
|
57
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
58
58
|
}),
|
|
59
59
|
).toBe(undefined)
|
|
60
60
|
})
|
|
@@ -69,7 +69,7 @@ describe(parseBlock.name, () => {
|
|
|
69
69
|
defineSchema({blockObjects: [{name: 'image'}]}),
|
|
70
70
|
),
|
|
71
71
|
},
|
|
72
|
-
options: {validateFields: true},
|
|
72
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
73
73
|
}),
|
|
74
74
|
).toEqual({
|
|
75
75
|
_key: 'k0',
|
|
@@ -87,7 +87,7 @@ describe(parseBlock.name, () => {
|
|
|
87
87
|
keyGenerator: createTestKeyGenerator(),
|
|
88
88
|
schema: compileSchema(defineSchema({})),
|
|
89
89
|
},
|
|
90
|
-
options: {validateFields: true},
|
|
90
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
91
91
|
}),
|
|
92
92
|
).toEqual({
|
|
93
93
|
_key: 'k0',
|
|
@@ -114,7 +114,7 @@ describe(parseBlock.name, () => {
|
|
|
114
114
|
keyGenerator: createTestKeyGenerator(),
|
|
115
115
|
schema: {...schema, block: {...schema.block, name: 'text'}},
|
|
116
116
|
},
|
|
117
|
-
options: {validateFields: true},
|
|
117
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
118
118
|
}),
|
|
119
119
|
).toEqual({
|
|
120
120
|
_key: 'k0',
|
|
@@ -149,7 +149,7 @@ describe(parseBlock.name, () => {
|
|
|
149
149
|
keyGenerator: createTestKeyGenerator(),
|
|
150
150
|
schema: compileSchema(defineSchema({})),
|
|
151
151
|
},
|
|
152
|
-
options: {validateFields: true},
|
|
152
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
153
153
|
}),
|
|
154
154
|
).toBe(undefined)
|
|
155
155
|
})
|
|
@@ -188,7 +188,7 @@ describe(parseBlock.name, () => {
|
|
|
188
188
|
}),
|
|
189
189
|
),
|
|
190
190
|
},
|
|
191
|
-
options: {validateFields: true},
|
|
191
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
192
192
|
}),
|
|
193
193
|
).toEqual({
|
|
194
194
|
_key: 'k0',
|
|
@@ -236,7 +236,7 @@ describe(parseBlock.name, () => {
|
|
|
236
236
|
keyGenerator: createTestKeyGenerator(),
|
|
237
237
|
schema: compileSchema(defineSchema({lists: [{name: 'bullet'}]})),
|
|
238
238
|
},
|
|
239
|
-
options: {validateFields: true},
|
|
239
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
240
240
|
}),
|
|
241
241
|
).toEqual({
|
|
242
242
|
_key: 'k0',
|
|
@@ -263,7 +263,7 @@ describe(parseBlock.name, () => {
|
|
|
263
263
|
keyGenerator: createTestKeyGenerator(),
|
|
264
264
|
schema: compileSchema(defineSchema({lists: [{name: 'bullet'}]})),
|
|
265
265
|
},
|
|
266
|
-
options: {validateFields: true},
|
|
266
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
267
267
|
}),
|
|
268
268
|
).toEqual({
|
|
269
269
|
_key: 'k0',
|
|
@@ -290,7 +290,7 @@ describe(parseBlock.name, () => {
|
|
|
290
290
|
keyGenerator: createTestKeyGenerator(),
|
|
291
291
|
schema: compileSchema(defineSchema({})),
|
|
292
292
|
},
|
|
293
|
-
options: {validateFields: true},
|
|
293
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
294
294
|
}),
|
|
295
295
|
).toEqual({
|
|
296
296
|
_type: 'block',
|
|
@@ -320,7 +320,7 @@ describe(parseBlock.name, () => {
|
|
|
320
320
|
}),
|
|
321
321
|
),
|
|
322
322
|
},
|
|
323
|
-
options: {validateFields: true},
|
|
323
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
324
324
|
}),
|
|
325
325
|
).toEqual({
|
|
326
326
|
_type: 'block',
|
|
@@ -351,7 +351,7 @@ describe(parseBlock.name, () => {
|
|
|
351
351
|
}),
|
|
352
352
|
),
|
|
353
353
|
},
|
|
354
|
-
options: {validateFields: true},
|
|
354
|
+
options: {removeUnusedMarkDefs: true, validateFields: true},
|
|
355
355
|
}),
|
|
356
356
|
).toEqual({
|
|
357
357
|
_type: 'block',
|
|
@@ -19,6 +19,7 @@ export function parseBlocks({
|
|
|
19
19
|
context: Pick<EditorContext, 'keyGenerator' | 'schema'>
|
|
20
20
|
blocks: unknown
|
|
21
21
|
options: {
|
|
22
|
+
removeUnusedMarkDefs: boolean
|
|
22
23
|
validateFields: boolean
|
|
23
24
|
}
|
|
24
25
|
}): Array<PortableTextBlock> {
|
|
@@ -41,6 +42,7 @@ export function parseBlock({
|
|
|
41
42
|
context: Pick<EditorContext, 'keyGenerator' | 'schema'>
|
|
42
43
|
block: unknown
|
|
43
44
|
options: {
|
|
45
|
+
removeUnusedMarkDefs: boolean
|
|
44
46
|
validateFields: boolean
|
|
45
47
|
}
|
|
46
48
|
}): PortableTextBlock | undefined {
|
|
@@ -99,7 +101,10 @@ export function parseTextBlock({
|
|
|
99
101
|
}: {
|
|
100
102
|
block: unknown
|
|
101
103
|
context: Pick<EditorContext, 'keyGenerator' | 'schema'>
|
|
102
|
-
options: {
|
|
104
|
+
options: {
|
|
105
|
+
removeUnusedMarkDefs: boolean
|
|
106
|
+
validateFields: boolean
|
|
107
|
+
}
|
|
103
108
|
}): PortableTextTextBlock | undefined {
|
|
104
109
|
if (!isTypedObject(block)) {
|
|
105
110
|
return undefined
|
|
@@ -204,7 +209,9 @@ export function parseTextBlock({
|
|
|
204
209
|
marks: [],
|
|
205
210
|
},
|
|
206
211
|
],
|
|
207
|
-
markDefs:
|
|
212
|
+
markDefs: options.removeUnusedMarkDefs
|
|
213
|
+
? markDefs.filter((markDef) => marks.includes(markDef._key))
|
|
214
|
+
: markDefs,
|
|
208
215
|
...customFields,
|
|
209
216
|
}
|
|
210
217
|
|
|
@@ -6,18 +6,13 @@ import React from 'react'
|
|
|
6
6
|
import {expect, vi} from 'vitest'
|
|
7
7
|
import {render} from 'vitest-browser-react'
|
|
8
8
|
import type {Context} from '../../gherkin-tests-v2/step-context'
|
|
9
|
-
import type {NativeBehaviorEvent} from '../behaviors'
|
|
10
9
|
import type {Editor} from '../editor'
|
|
11
10
|
import {
|
|
12
11
|
PortableTextEditable,
|
|
13
12
|
type PortableTextEditableProps,
|
|
14
13
|
} from '../editor/Editable'
|
|
15
|
-
import type {EditorActor} from '../editor/editor-machine'
|
|
16
14
|
import {EditorProvider} from '../editor/editor-provider'
|
|
17
15
|
import {EditorRefPlugin} from '../plugins/plugin.editor-ref'
|
|
18
|
-
import {InternalEditorAfterRefPlugin} from '../plugins/plugin.internal.editor-actor-ref'
|
|
19
|
-
import {InternalSlateEditorRefPlugin} from '../plugins/plugin.internal.slate-editor-ref'
|
|
20
|
-
import type {PortableTextSlateEditor} from '../types/editor'
|
|
21
16
|
|
|
22
17
|
type CreateTestEditorOptions = {
|
|
23
18
|
initialValue?: Array<PortableTextBlock>
|
|
@@ -35,8 +30,6 @@ export async function createTestEditor(
|
|
|
35
30
|
}
|
|
36
31
|
> {
|
|
37
32
|
const editorRef = React.createRef<Editor>()
|
|
38
|
-
const editorActorRef = React.createRef<EditorActor>()
|
|
39
|
-
const slateRef = React.createRef<PortableTextSlateEditor>()
|
|
40
33
|
const keyGenerator = options.keyGenerator ?? createTestKeyGenerator()
|
|
41
34
|
|
|
42
35
|
const renderResult = render(
|
|
@@ -48,8 +41,6 @@ export async function createTestEditor(
|
|
|
48
41
|
}}
|
|
49
42
|
>
|
|
50
43
|
<EditorRefPlugin ref={editorRef} />
|
|
51
|
-
<InternalEditorAfterRefPlugin ref={editorActorRef} />
|
|
52
|
-
<InternalSlateEditorRefPlugin ref={slateRef} />
|
|
53
44
|
<PortableTextEditable {...options.editableProps} />
|
|
54
45
|
{options.children}
|
|
55
46
|
</EditorProvider>,
|
|
@@ -66,8 +57,6 @@ export async function createTestEditor(
|
|
|
66
57
|
}}
|
|
67
58
|
>
|
|
68
59
|
<EditorRefPlugin ref={editorRef} />
|
|
69
|
-
<InternalEditorAfterRefPlugin ref={editorActorRef} />
|
|
70
|
-
<InternalSlateEditorRefPlugin ref={slateRef} />
|
|
71
60
|
<PortableTextEditable {...newOptions.editableProps} />
|
|
72
61
|
{newOptions.children}
|
|
73
62
|
</EditorProvider>,
|
|
@@ -81,8 +70,6 @@ export async function createTestEditor(
|
|
|
81
70
|
}}
|
|
82
71
|
>
|
|
83
72
|
<EditorRefPlugin ref={editorRef} />
|
|
84
|
-
<InternalEditorAfterRefPlugin ref={editorActorRef} />
|
|
85
|
-
<InternalSlateEditorRefPlugin ref={slateRef} />
|
|
86
73
|
<PortableTextEditable {...options.editableProps} />
|
|
87
74
|
{options.children}
|
|
88
75
|
</EditorProvider>,
|
|
@@ -93,19 +80,8 @@ export async function createTestEditor(
|
|
|
93
80
|
|
|
94
81
|
await vi.waitFor(() => expect.element(locator).toBeInTheDocument())
|
|
95
82
|
|
|
96
|
-
function sendNativeEvent(event: NativeBehaviorEvent) {
|
|
97
|
-
editorActorRef.current?.send({
|
|
98
|
-
type: 'behavior event',
|
|
99
|
-
behaviorEvent: event,
|
|
100
|
-
editor: slateRef.current!,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
83
|
return {
|
|
105
|
-
editor:
|
|
106
|
-
...editorRef.current!,
|
|
107
|
-
sendNativeEvent,
|
|
108
|
-
},
|
|
84
|
+
editor: editorRef.current!,
|
|
109
85
|
locator,
|
|
110
86
|
rerender,
|
|
111
87
|
}
|
|
@@ -30,15 +30,16 @@ export const blockSetOperationImplementation: BehaviorOperationImplementation<
|
|
|
30
30
|
const parsedBlock = parseBlock({
|
|
31
31
|
context,
|
|
32
32
|
block: updatedBlock,
|
|
33
|
-
options: {
|
|
33
|
+
options: {
|
|
34
|
+
removeUnusedMarkDefs: false,
|
|
35
|
+
validateFields: true,
|
|
36
|
+
},
|
|
34
37
|
})
|
|
35
38
|
|
|
36
39
|
if (!parsedBlock) {
|
|
37
40
|
throw new Error(`Unable to update block at ${JSON.stringify(operation.at)}`)
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
parsedBlock.markDefs = updatedBlock.markDefs
|
|
41
|
-
|
|
42
43
|
const slateBlock = toSlateValue([parsedBlock], {
|
|
43
44
|
schemaTypes: context.schema,
|
|
44
45
|
})?.at(0) as SlateElement | undefined
|
|
@@ -51,7 +51,10 @@ export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
|
51
51
|
const updatedTextBlock = parseBlock({
|
|
52
52
|
context,
|
|
53
53
|
block: omit(parsedBlock, propsToRemove),
|
|
54
|
-
options: {
|
|
54
|
+
options: {
|
|
55
|
+
removeUnusedMarkDefs: true,
|
|
56
|
+
validateFields: true,
|
|
57
|
+
},
|
|
55
58
|
})
|
|
56
59
|
|
|
57
60
|
if (!updatedTextBlock) {
|
|
@@ -81,7 +84,10 @@ export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
|
81
84
|
parsedBlock,
|
|
82
85
|
operation.props.filter((prop) => prop !== '_type'),
|
|
83
86
|
),
|
|
84
|
-
options: {
|
|
87
|
+
options: {
|
|
88
|
+
removeUnusedMarkDefs: true,
|
|
89
|
+
validateFields: true,
|
|
90
|
+
},
|
|
85
91
|
})
|
|
86
92
|
|
|
87
93
|
if (!updatedBlockObject) {
|
|
@@ -24,7 +24,10 @@ export const insertBlockOperationImplementation: BehaviorOperationImplementation
|
|
|
24
24
|
const parsedBlock = parseBlock({
|
|
25
25
|
block: operation.block,
|
|
26
26
|
context,
|
|
27
|
-
options: {
|
|
27
|
+
options: {
|
|
28
|
+
removeUnusedMarkDefs: true,
|
|
29
|
+
validateFields: true,
|
|
30
|
+
},
|
|
28
31
|
})
|
|
29
32
|
|
|
30
33
|
if (!parsedBlock) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {isTextBlock} from '@portabletext/schema'
|
|
2
|
+
import {Transforms} from 'slate'
|
|
3
|
+
import {parseInlineObject, parseSpan} from '../internal-utils/parse-blocks'
|
|
4
|
+
import {getFocusBlock, getFocusSpan} from '../internal-utils/slate-utils'
|
|
5
|
+
import {VOID_CHILD_KEY} from '../internal-utils/values'
|
|
6
|
+
import type {BehaviorOperationImplementation} from './behavior.operations'
|
|
7
|
+
|
|
8
|
+
export const insertChildOperationImplementation: BehaviorOperationImplementation<
|
|
9
|
+
'insert.child'
|
|
10
|
+
> = ({context, operation}) => {
|
|
11
|
+
const focus = operation.editor.selection?.focus
|
|
12
|
+
const focusBlockIndex = focus?.path.at(0)
|
|
13
|
+
const focusChildIndex = focus?.path.at(1)
|
|
14
|
+
|
|
15
|
+
if (focusBlockIndex === undefined || focusChildIndex === undefined) {
|
|
16
|
+
throw new Error('Unable to insert child without a focus')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const [focusBlock, focusBlockPath] = getFocusBlock({editor: operation.editor})
|
|
20
|
+
|
|
21
|
+
if (!focus || !focusBlock || !focusBlockPath) {
|
|
22
|
+
throw new Error('Unable to insert child without a focus block')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!isTextBlock(context, focusBlock)) {
|
|
26
|
+
throw new Error('Unable to insert child into a non-text block')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const markDefs = focusBlock.markDefs ?? []
|
|
30
|
+
const markDefKeyMap = new Map<string, string>()
|
|
31
|
+
for (const markDef of markDefs) {
|
|
32
|
+
markDefKeyMap.set(markDef._key, markDef._key)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const span = parseSpan({
|
|
36
|
+
span: operation.child,
|
|
37
|
+
context,
|
|
38
|
+
markDefKeyMap,
|
|
39
|
+
options: {validateFields: true},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (span) {
|
|
43
|
+
const [focusSpan] = getFocusSpan({editor: operation.editor})
|
|
44
|
+
|
|
45
|
+
if (focusSpan) {
|
|
46
|
+
Transforms.insertNodes(operation.editor, span, {
|
|
47
|
+
at: focus,
|
|
48
|
+
select: true,
|
|
49
|
+
})
|
|
50
|
+
} else {
|
|
51
|
+
Transforms.insertNodes(operation.editor, span, {
|
|
52
|
+
at: [focusBlockIndex, focusChildIndex + 1],
|
|
53
|
+
select: true,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const inlineObject = parseInlineObject({
|
|
61
|
+
inlineObject: operation.child,
|
|
62
|
+
context,
|
|
63
|
+
options: {validateFields: true},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
if (inlineObject) {
|
|
67
|
+
const {_key, _type, ...rest} = inlineObject
|
|
68
|
+
|
|
69
|
+
Transforms.insertNodes(
|
|
70
|
+
operation.editor,
|
|
71
|
+
{
|
|
72
|
+
_key,
|
|
73
|
+
_type,
|
|
74
|
+
children: [
|
|
75
|
+
{
|
|
76
|
+
_key: VOID_CHILD_KEY,
|
|
77
|
+
_type: 'span',
|
|
78
|
+
text: '',
|
|
79
|
+
marks: [],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
value: rest,
|
|
83
|
+
__inline: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
at: [focusBlockIndex, focusChildIndex + 1],
|
|
87
|
+
select: true,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new Error('Unable to parse child')
|
|
95
|
+
}
|
|
@@ -20,6 +20,7 @@ import {childUnsetOperationImplementation} from './behavior.operation.child.unse
|
|
|
20
20
|
import {decoratorAddOperationImplementation} from './behavior.operation.decorator.add'
|
|
21
21
|
import {deleteOperationImplementation} from './behavior.operation.delete'
|
|
22
22
|
import {insertBlockOperationImplementation} from './behavior.operation.insert.block'
|
|
23
|
+
import {insertChildOperationImplementation} from './behavior.operation.insert.child'
|
|
23
24
|
import {insertTextOperationImplementation} from './behavior.operation.insert.text'
|
|
24
25
|
import {moveBackwardOperationImplementation} from './behavior.operation.move.backward'
|
|
25
26
|
import {moveBlockOperationImplementation} from './behavior.operation.move.block'
|
|
@@ -66,6 +67,7 @@ const behaviorOperationImplementations: BehaviorOperationImplementations = {
|
|
|
66
67
|
'history.redo': historyRedoOperationImplementation,
|
|
67
68
|
'history.undo': historyUndoOperationImplementation,
|
|
68
69
|
'insert.block': insertBlockOperationImplementation,
|
|
70
|
+
'insert.child': insertChildOperationImplementation,
|
|
69
71
|
'insert.text': insertTextOperationImplementation,
|
|
70
72
|
'move.backward': moveBackwardOperationImplementation,
|
|
71
73
|
'move.block': moveBlockOperationImplementation,
|
|
@@ -167,6 +169,13 @@ export function performOperation({
|
|
|
167
169
|
})
|
|
168
170
|
break
|
|
169
171
|
}
|
|
172
|
+
case 'insert.child': {
|
|
173
|
+
behaviorOperationImplementations['insert.child']({
|
|
174
|
+
context,
|
|
175
|
+
operation: operation,
|
|
176
|
+
})
|
|
177
|
+
break
|
|
178
|
+
}
|
|
170
179
|
case 'insert.text': {
|
|
171
180
|
behaviorOperationImplementations['insert.text']({
|
|
172
181
|
context,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
PortableTextObject,
|
|
3
|
+
PortableTextSpan,
|
|
4
|
+
PortableTextTextBlock,
|
|
5
|
+
} from '@portabletext/schema'
|
|
2
6
|
|
|
3
7
|
export type TextBlockWithOptionalKey = Omit<PortableTextTextBlock, '_key'> & {
|
|
4
8
|
_key?: PortableTextTextBlock['_key']
|
|
@@ -11,3 +15,11 @@ export type ObjectBlockWithOptionalKey = Omit<PortableTextObject, '_key'> & {
|
|
|
11
15
|
export type BlockWithOptionalKey =
|
|
12
16
|
| TextBlockWithOptionalKey
|
|
13
17
|
| ObjectBlockWithOptionalKey
|
|
18
|
+
|
|
19
|
+
export type SpanWithOptionalKey = Omit<PortableTextSpan, '_key'> & {
|
|
20
|
+
_key?: PortableTextSpan['_key']
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ChildWithOptionalKey =
|
|
24
|
+
| SpanWithOptionalKey
|
|
25
|
+
| ObjectBlockWithOptionalKey
|
|
@@ -18,7 +18,7 @@ export function mergeTextBlocks({
|
|
|
18
18
|
const parsedIncomingBlock = parseBlock({
|
|
19
19
|
context,
|
|
20
20
|
block: incomingBlock,
|
|
21
|
-
options: {validateFields: false},
|
|
21
|
+
options: {removeUnusedMarkDefs: true, validateFields: false},
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
if (!parsedIncomingBlock || !isTextBlock(context, parsedIncomingBlock)) {
|
|
@@ -171,7 +171,7 @@ export function sliceBlocks({
|
|
|
171
171
|
keyGenerator: defaultKeyGenerator,
|
|
172
172
|
},
|
|
173
173
|
block,
|
|
174
|
-
options: {validateFields: false},
|
|
174
|
+
options: {removeUnusedMarkDefs: true, validateFields: false},
|
|
175
175
|
}) ?? block,
|
|
176
176
|
)
|
|
177
177
|
}
|
|
@@ -184,7 +184,7 @@ export function sliceBlocks({
|
|
|
184
184
|
keyGenerator: defaultKeyGenerator,
|
|
185
185
|
},
|
|
186
186
|
block: startBlock,
|
|
187
|
-
options: {validateFields: false},
|
|
187
|
+
options: {removeUnusedMarkDefs: true, validateFields: false},
|
|
188
188
|
})
|
|
189
189
|
: undefined
|
|
190
190
|
|
|
@@ -195,7 +195,7 @@ export function sliceBlocks({
|
|
|
195
195
|
keyGenerator: defaultKeyGenerator,
|
|
196
196
|
},
|
|
197
197
|
block: endBlock,
|
|
198
|
-
options: {validateFields: false},
|
|
198
|
+
options: {removeUnusedMarkDefs: true, validateFields: false},
|
|
199
199
|
})
|
|
200
200
|
: undefined
|
|
201
201
|
|
|
@@ -107,7 +107,7 @@ describe(sliceTextBlock.name, () => {
|
|
|
107
107
|
})
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
describe('multiple children', () => {
|
|
111
111
|
const fooSpan = createSpan({
|
|
112
112
|
text: 'foo',
|
|
113
113
|
marks: ['strong'],
|
|
@@ -127,36 +127,62 @@ describe(sliceTextBlock.name, () => {
|
|
|
127
127
|
children: [fooSpan, barSpan, stockTicker, bazSpan],
|
|
128
128
|
})
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
test('mid-span', () => {
|
|
131
|
+
expect(
|
|
132
|
+
sliceTextBlock({
|
|
133
|
+
context: {
|
|
134
|
+
schema,
|
|
135
|
+
selection: {
|
|
136
|
+
anchor: {
|
|
137
|
+
path: [{_key: block._key}, 'children', {_key: barSpan._key}],
|
|
138
|
+
offset: 1,
|
|
139
|
+
},
|
|
140
|
+
focus: {
|
|
141
|
+
path: [{_key: block._key}, 'children', {_key: bazSpan._key}],
|
|
142
|
+
offset: 1,
|
|
143
|
+
},
|
|
138
144
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
},
|
|
146
|
+
block,
|
|
147
|
+
}),
|
|
148
|
+
).toEqual({
|
|
149
|
+
...block,
|
|
150
|
+
children: [
|
|
151
|
+
{
|
|
152
|
+
...barSpan,
|
|
153
|
+
text: 'ar',
|
|
154
|
+
},
|
|
155
|
+
stockTicker,
|
|
156
|
+
{
|
|
157
|
+
...bazSpan,
|
|
158
|
+
text: 'b',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('from end of span to end of block', () => {
|
|
165
|
+
expect(
|
|
166
|
+
sliceTextBlock({
|
|
167
|
+
context: {
|
|
168
|
+
schema,
|
|
169
|
+
selection: {
|
|
170
|
+
anchor: {
|
|
171
|
+
path: [{_key: block._key}, 'children', {_key: fooSpan._key}],
|
|
172
|
+
offset: 3,
|
|
173
|
+
},
|
|
174
|
+
focus: {
|
|
175
|
+
path: [{_key: block._key}, 'children', {_key: bazSpan._key}],
|
|
176
|
+
offset: 3,
|
|
177
|
+
},
|
|
142
178
|
},
|
|
143
179
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
...barSpan,
|
|
152
|
-
text: 'ar',
|
|
153
|
-
},
|
|
154
|
-
stockTicker,
|
|
155
|
-
{
|
|
156
|
-
...bazSpan,
|
|
157
|
-
text: 'b',
|
|
158
|
-
},
|
|
159
|
-
],
|
|
180
|
+
block,
|
|
181
|
+
}),
|
|
182
|
+
).toEqual({
|
|
183
|
+
...block,
|
|
184
|
+
children: [{...fooSpan, text: ''}, barSpan, stockTicker, bazSpan],
|
|
185
|
+
})
|
|
160
186
|
})
|
|
161
187
|
})
|
|
162
188
|
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React, {useContext} from 'react'
|
|
2
|
-
import {EditorActorContext} from '../editor/editor-actor-context'
|
|
3
|
-
import type {EditorActor} from '../editor/editor-machine'
|
|
4
|
-
|
|
5
|
-
export const InternalEditorAfterRefPlugin =
|
|
6
|
-
React.forwardRef<EditorActor | null>((_, ref) => {
|
|
7
|
-
const editorActor = useContext(EditorActorContext)
|
|
8
|
-
|
|
9
|
-
const editorActorRef = React.useRef(editorActor)
|
|
10
|
-
|
|
11
|
-
React.useImperativeHandle(ref, () => editorActorRef.current, [])
|
|
12
|
-
|
|
13
|
-
return null
|
|
14
|
-
})
|
|
15
|
-
InternalEditorAfterRefPlugin.displayName = 'InternalEditorAfterRefPlugin'
|