@portabletext/editor 2.9.2 → 2.10.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 +132 -122
- package/lib/_chunks-dts/behavior.types.action.d.ts +69 -59
- 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/utils/index.d.ts +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 +9 -1
- 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/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
|
@@ -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
|
|
|
@@ -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
|
})
|