@portabletext/editor 1.0.17 → 1.0.19
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.esm.js +69 -64
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +69 -64
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +69 -64
- package/lib/index.mjs.map +1 -1
- package/package.json +7 -3
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +0 -533
- package/src/editor/plugins/createWithEditableAPI.ts +10 -1
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +97 -118
- package/src/utils/operationToPatches.ts +58 -14
- package/src/utils/values.ts +0 -1
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +0 -212
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +0 -220
- package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +0 -133
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from '../../types/editor'
|
|
19
19
|
import {debugWithName} from '../../utils/debug'
|
|
20
20
|
import {toPortableTextRange} from '../../utils/ranges'
|
|
21
|
-
import {EMPTY_MARKS} from '../../utils/values'
|
|
22
21
|
import {isChangingRemotely} from '../../utils/withChanges'
|
|
23
22
|
import {isRedoing, isUndoing} from '../../utils/withUndoRedo'
|
|
24
23
|
|
|
@@ -56,9 +55,6 @@ export function createWithPortableTextMarkModel(
|
|
|
56
55
|
editor.normalizeNode = (nodeEntry) => {
|
|
57
56
|
const [node, path] = nodeEntry
|
|
58
57
|
|
|
59
|
-
const isSpan = Text.isText(node) && node._type === types.span.name
|
|
60
|
-
const isTextBlock = editor.isTextBlock(node)
|
|
61
|
-
|
|
62
58
|
if (editor.isTextBlock(node)) {
|
|
63
59
|
const children = Node.children(editor, path)
|
|
64
60
|
|
|
@@ -81,131 +77,89 @@ export function createWithPortableTextMarkModel(
|
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
return
|
|
113
|
-
}
|
|
80
|
+
/**
|
|
81
|
+
* Add missing .marks to span nodes
|
|
82
|
+
*/
|
|
83
|
+
if (editor.isTextSpan(node) && !Array.isArray(node.marks)) {
|
|
84
|
+
debug('Adding .marks to span node')
|
|
85
|
+
Transforms.setNodes(editor, {marks: []}, {at: path})
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove annotations from empty spans
|
|
91
|
+
*/
|
|
92
|
+
if (editor.isTextSpan(node)) {
|
|
93
|
+
const blockPath = Path.parent(path)
|
|
94
|
+
const [block] = Editor.node(editor, blockPath)
|
|
95
|
+
const decorators = types.decorators.map((decorator) => decorator.value)
|
|
96
|
+
const annotations = node.marks?.filter((mark) => !decorators.includes(mark))
|
|
97
|
+
|
|
98
|
+
if (editor.isTextBlock(block)) {
|
|
99
|
+
if (node.text === '' && annotations && annotations.length > 0) {
|
|
100
|
+
debug('Removing annotations from empty span node')
|
|
101
|
+
Transforms.setNodes(
|
|
102
|
+
editor,
|
|
103
|
+
{marks: node.marks?.filter((mark) => decorators.includes(mark))},
|
|
104
|
+
{at: path},
|
|
105
|
+
)
|
|
106
|
+
return
|
|
114
107
|
}
|
|
115
108
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// eslint-disable-next-line max-depth
|
|
134
|
-
if (!isNormalized) {
|
|
135
|
-
Transforms.setNodes(editor, {markDefs: newMarkDefs}, {at: targetPath, voids: false})
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Make sure markDefs are copied over to new block when splitting a block.
|
|
141
|
-
if (
|
|
142
|
-
op.type === 'split_node' &&
|
|
143
|
-
op.path.length === 1 &&
|
|
144
|
-
Element.isElementProps(op.properties) &&
|
|
145
|
-
op.properties._type === types.block.name &&
|
|
146
|
-
'markDefs' in op.properties &&
|
|
147
|
-
Array.isArray(op.properties.markDefs) &&
|
|
148
|
-
op.properties.markDefs.length > 0 &&
|
|
149
|
-
op.path[0] + 1 < editor.children.length
|
|
150
|
-
) {
|
|
151
|
-
const [targetBlock, targetPath] = Editor.node(editor, [op.path[0] + 1])
|
|
152
|
-
debug(`Copying markDefs over to split block`, op)
|
|
153
|
-
if (editor.isTextBlock(targetBlock)) {
|
|
154
|
-
const oldDefs = (Array.isArray(targetBlock.markDefs) && targetBlock.markDefs) || []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remove orphaned annotations from child spans of block nodes
|
|
113
|
+
*/
|
|
114
|
+
if (editor.isTextBlock(node)) {
|
|
115
|
+
const decorators = types.decorators.map((decorator) => decorator.value)
|
|
116
|
+
|
|
117
|
+
for (const [child, childPath] of Node.children(editor, path)) {
|
|
118
|
+
if (editor.isTextSpan(child)) {
|
|
119
|
+
const marks = child.marks ?? []
|
|
120
|
+
const orphanedAnnotations = marks.filter((mark) => {
|
|
121
|
+
return !decorators.includes(mark) && !node.markDefs?.find((def) => def._key === mark)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (orphanedAnnotations.length > 0) {
|
|
125
|
+
debug('Removing orphaned annotations from span node')
|
|
155
126
|
Transforms.setNodes(
|
|
156
127
|
editor,
|
|
157
|
-
{
|
|
158
|
-
{at:
|
|
128
|
+
{marks: marks.filter((mark) => !orphanedAnnotations.includes(mark))},
|
|
129
|
+
{at: childPath},
|
|
159
130
|
)
|
|
160
131
|
return
|
|
161
132
|
}
|
|
162
133
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(op.properties as unknown as Descendant)._type === types.block.name &&
|
|
189
|
-
'markDefs' in op.properties &&
|
|
190
|
-
Array.isArray(op.properties.markDefs) &&
|
|
191
|
-
op.properties.markDefs.length > 0
|
|
192
|
-
) {
|
|
193
|
-
const [block, blockPath] = Editor.node(editor, [op.path[0]])
|
|
194
|
-
if (
|
|
195
|
-
editor.isTextBlock(block) &&
|
|
196
|
-
block.children.length === 1 &&
|
|
197
|
-
block.markDefs &&
|
|
198
|
-
block.markDefs.length > 0 &&
|
|
199
|
-
Text.isText(block.children[0]) &&
|
|
200
|
-
block.children[0].text === '' &&
|
|
201
|
-
(!block.children[0].marks || block.children[0].marks.length === 0)
|
|
202
|
-
) {
|
|
203
|
-
Transforms.setNodes(editor, {markDefs: []}, {at: blockPath})
|
|
204
|
-
return
|
|
205
|
-
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Remove orphaned annotations from span nodes
|
|
139
|
+
*/
|
|
140
|
+
if (editor.isTextSpan(node)) {
|
|
141
|
+
const blockPath = Path.parent(path)
|
|
142
|
+
const [block] = Editor.node(editor, blockPath)
|
|
143
|
+
|
|
144
|
+
if (editor.isTextBlock(block)) {
|
|
145
|
+
const decorators = types.decorators.map((decorator) => decorator.value)
|
|
146
|
+
const marks = node.marks ?? []
|
|
147
|
+
const orphanedAnnotations = marks.filter((mark) => {
|
|
148
|
+
return !decorators.includes(mark) && !block.markDefs?.find((def) => def._key === mark)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (orphanedAnnotations.length > 0) {
|
|
152
|
+
debug('Removing orphaned annotations from span node')
|
|
153
|
+
Transforms.setNodes(
|
|
154
|
+
editor,
|
|
155
|
+
{marks: marks.filter((mark) => !orphanedAnnotations.includes(mark))},
|
|
156
|
+
{at: path},
|
|
157
|
+
)
|
|
158
|
+
return
|
|
206
159
|
}
|
|
207
160
|
}
|
|
208
161
|
}
|
|
162
|
+
|
|
209
163
|
// Check consistency of markDefs (unless we are merging two nodes)
|
|
210
164
|
if (
|
|
211
165
|
editor.isTextBlock(node) &&
|
|
@@ -360,6 +314,31 @@ export function createWithPortableTextMarkModel(
|
|
|
360
314
|
}
|
|
361
315
|
}
|
|
362
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Copy over markDefs when merging blocks
|
|
319
|
+
*/
|
|
320
|
+
if (
|
|
321
|
+
op.type === 'merge_node' &&
|
|
322
|
+
op.path.length === 1 &&
|
|
323
|
+
'markDefs' in op.properties &&
|
|
324
|
+
op.properties._type === types.block.name &&
|
|
325
|
+
Array.isArray(op.properties.markDefs) &&
|
|
326
|
+
op.properties.markDefs.length > 0 &&
|
|
327
|
+
op.path[0] - 1 >= 0
|
|
328
|
+
) {
|
|
329
|
+
const [targetBlock, targetPath] = Editor.node(editor, [op.path[0] - 1])
|
|
330
|
+
|
|
331
|
+
if (editor.isTextBlock(targetBlock)) {
|
|
332
|
+
const oldDefs = (Array.isArray(targetBlock.markDefs) && targetBlock.markDefs) || []
|
|
333
|
+
const newMarkDefs = uniq([...oldDefs, ...op.properties.markDefs])
|
|
334
|
+
|
|
335
|
+
debug(`Copying markDefs over to merged block`, op)
|
|
336
|
+
Transforms.setNodes(editor, {markDefs: newMarkDefs}, {at: targetPath, voids: false})
|
|
337
|
+
apply(op)
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
363
342
|
apply(op)
|
|
364
343
|
}
|
|
365
344
|
|
|
@@ -264,9 +264,19 @@ export function createOperationToPatches(types: PortableTextMemberSchemaTypes):
|
|
|
264
264
|
}
|
|
265
265
|
throw new Error('Block not found')
|
|
266
266
|
} else if (editor.isTextBlock(block) && operation.path.length === 2) {
|
|
267
|
-
const spanToRemove =
|
|
268
|
-
|
|
267
|
+
const spanToRemove = block.children[operation.path[1]]
|
|
268
|
+
|
|
269
269
|
if (spanToRemove) {
|
|
270
|
+
const spansMatchingKey = block.children.filter((span) => span._key === operation.node._key)
|
|
271
|
+
|
|
272
|
+
if (spansMatchingKey.length > 1) {
|
|
273
|
+
console.warn(
|
|
274
|
+
`Multiple spans have \`_key\` ${operation.node._key}. It's ambiguous which one to remove.`,
|
|
275
|
+
JSON.stringify(block, null, 2),
|
|
276
|
+
)
|
|
277
|
+
return []
|
|
278
|
+
}
|
|
279
|
+
|
|
270
280
|
return [unset([{_key: block._key}, 'children', {_key: spanToRemove._key}])]
|
|
271
281
|
}
|
|
272
282
|
debug('Span not found in editor trying to remove node')
|
|
@@ -285,7 +295,7 @@ export function createOperationToPatches(types: PortableTextMemberSchemaTypes):
|
|
|
285
295
|
const patches: Patch[] = []
|
|
286
296
|
|
|
287
297
|
const block = beforeValue[operation.path[0]]
|
|
288
|
-
const
|
|
298
|
+
const updatedBlock = editor.children[operation.path[0]]
|
|
289
299
|
|
|
290
300
|
if (operation.path.length === 1) {
|
|
291
301
|
if (block?._key) {
|
|
@@ -295,17 +305,51 @@ export function createOperationToPatches(types: PortableTextMemberSchemaTypes):
|
|
|
295
305
|
} else {
|
|
296
306
|
throw new Error('Target key not found!')
|
|
297
307
|
}
|
|
298
|
-
} else if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
} else if (
|
|
309
|
+
editor.isTextBlock(block) &&
|
|
310
|
+
editor.isTextBlock(updatedBlock) &&
|
|
311
|
+
operation.path.length === 2
|
|
312
|
+
) {
|
|
313
|
+
const updatedSpan =
|
|
314
|
+
updatedBlock.children[operation.path[1] - 1] &&
|
|
315
|
+
editor.isTextSpan(updatedBlock.children[operation.path[1] - 1])
|
|
316
|
+
? updatedBlock.children[operation.path[1] - 1]
|
|
317
|
+
: undefined
|
|
318
|
+
const removedSpan =
|
|
319
|
+
block.children[operation.path[1]] && editor.isTextSpan(block.children[operation.path[1]])
|
|
320
|
+
? block.children[operation.path[1]]
|
|
321
|
+
: undefined
|
|
322
|
+
|
|
323
|
+
if (updatedSpan) {
|
|
324
|
+
const spansMatchingKey = block.children.filter((span) => span._key === updatedSpan._key)
|
|
325
|
+
|
|
326
|
+
if (spansMatchingKey.length === 1) {
|
|
327
|
+
patches.push(
|
|
328
|
+
set(updatedSpan.text, [
|
|
329
|
+
{_key: block._key},
|
|
330
|
+
'children',
|
|
331
|
+
{_key: updatedSpan._key},
|
|
332
|
+
'text',
|
|
333
|
+
]),
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
console.warn(
|
|
337
|
+
`Multiple spans have \`_key\` ${updatedSpan._key}. It's ambiguous which one to update.`,
|
|
338
|
+
JSON.stringify(block, null, 2),
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (removedSpan) {
|
|
344
|
+
const spansMatchingKey = block.children.filter((span) => span._key === removedSpan._key)
|
|
345
|
+
|
|
346
|
+
if (spansMatchingKey.length === 1) {
|
|
347
|
+
patches.push(unset([{_key: block._key}, 'children', {_key: removedSpan._key}]))
|
|
348
|
+
} else {
|
|
349
|
+
console.warn(
|
|
350
|
+
`Multiple spans have \`_key\` ${removedSpan._key}. It's ambiguous which one to remove.`,
|
|
351
|
+
JSON.stringify(block, null, 2),
|
|
352
|
+
)
|
|
309
353
|
}
|
|
310
354
|
}
|
|
311
355
|
} else {
|
package/src/utils/values.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {type Descendant, Element, type Node, Text} from 'slate'
|
|
|
11
11
|
import {type PortableTextMemberSchemaTypes} from '../types/editor'
|
|
12
12
|
|
|
13
13
|
export const EMPTY_MARKDEFS: PortableTextObject[] = []
|
|
14
|
-
export const EMPTY_MARKS: string[] = []
|
|
15
14
|
|
|
16
15
|
export const VOID_CHILD_KEY = 'void-child'
|
|
17
16
|
|
|
@@ -1,212 +0,0 @@
|
|
|
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 {getEditableElement, triggerKeyboardEvent} from '../../__tests__/utils'
|
|
7
|
-
import {PortableTextEditor} from '../../PortableTextEditor'
|
|
8
|
-
|
|
9
|
-
const newBlock = {
|
|
10
|
-
_type: 'myTestBlockType',
|
|
11
|
-
_key: '3',
|
|
12
|
-
style: 'normal',
|
|
13
|
-
markDefs: [],
|
|
14
|
-
children: [
|
|
15
|
-
{
|
|
16
|
-
_type: 'span',
|
|
17
|
-
_key: '2',
|
|
18
|
-
text: '',
|
|
19
|
-
marks: [],
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
}
|
|
23
|
-
describe('plugin:withHotkeys: .ArrowDown', () => {
|
|
24
|
-
it('a new block is added if the user is focused on the only block which is void, and presses arrow down.', async () => {
|
|
25
|
-
const initialValue = [
|
|
26
|
-
{
|
|
27
|
-
_key: 'a',
|
|
28
|
-
_type: 'someObject',
|
|
29
|
-
},
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
const initialSelection = {
|
|
33
|
-
focus: {path: [{_key: 'a'}], offset: 0},
|
|
34
|
-
anchor: {path: [{_key: 'a'}], offset: 0},
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
38
|
-
const onChange = jest.fn()
|
|
39
|
-
const component = render(
|
|
40
|
-
<PortableTextEditorTester
|
|
41
|
-
onChange={onChange}
|
|
42
|
-
ref={editorRef}
|
|
43
|
-
schemaType={schemaType}
|
|
44
|
-
value={initialValue}
|
|
45
|
-
/>,
|
|
46
|
-
)
|
|
47
|
-
const element = await getEditableElement(component)
|
|
48
|
-
|
|
49
|
-
const editor = editorRef.current
|
|
50
|
-
const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
|
|
51
|
-
await waitFor(async () => {
|
|
52
|
-
if (editor && inlineType && editor) {
|
|
53
|
-
PortableTextEditor.focus(editor)
|
|
54
|
-
PortableTextEditor.select(editor, initialSelection)
|
|
55
|
-
PortableTextEditor.insertBreak(editor)
|
|
56
|
-
await triggerKeyboardEvent('ArrowDown', element)
|
|
57
|
-
|
|
58
|
-
const value = PortableTextEditor.getValue(editor)
|
|
59
|
-
expect(value).toEqual([initialValue[0], newBlock])
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
it('a new block is added if the user is focused on the last block which is void, and presses arrow down.', async () => {
|
|
64
|
-
const initialValue = [
|
|
65
|
-
{
|
|
66
|
-
_type: 'myTestBlockType',
|
|
67
|
-
_key: 'a',
|
|
68
|
-
style: 'normal',
|
|
69
|
-
markDefs: [],
|
|
70
|
-
children: [
|
|
71
|
-
{
|
|
72
|
-
_type: 'span',
|
|
73
|
-
_key: 'a1',
|
|
74
|
-
text: 'This is the first block',
|
|
75
|
-
marks: [],
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
_key: 'b',
|
|
81
|
-
_type: 'someObject',
|
|
82
|
-
},
|
|
83
|
-
]
|
|
84
|
-
const initialSelection = {
|
|
85
|
-
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
|
|
86
|
-
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
90
|
-
const onChange = jest.fn()
|
|
91
|
-
const component = render(
|
|
92
|
-
<PortableTextEditorTester
|
|
93
|
-
onChange={onChange}
|
|
94
|
-
ref={editorRef}
|
|
95
|
-
schemaType={schemaType}
|
|
96
|
-
value={initialValue}
|
|
97
|
-
/>,
|
|
98
|
-
)
|
|
99
|
-
const element = await getEditableElement(component)
|
|
100
|
-
|
|
101
|
-
const editor = editorRef.current
|
|
102
|
-
const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
|
|
103
|
-
await waitFor(async () => {
|
|
104
|
-
if (editor && inlineType && element) {
|
|
105
|
-
PortableTextEditor.focus(editor)
|
|
106
|
-
PortableTextEditor.select(editor, initialSelection)
|
|
107
|
-
await triggerKeyboardEvent('ArrowDown', element)
|
|
108
|
-
const value = PortableTextEditor.getValue(editor)
|
|
109
|
-
// Arrow down on the text block should not add a new block
|
|
110
|
-
expect(value).toEqual(initialValue)
|
|
111
|
-
// Focus on the object block
|
|
112
|
-
PortableTextEditor.select(editor, {
|
|
113
|
-
focus: {path: [{_key: 'b'}], offset: 0},
|
|
114
|
-
anchor: {path: [{_key: 'b'}], offset: 0},
|
|
115
|
-
})
|
|
116
|
-
await triggerKeyboardEvent('ArrowDown', element)
|
|
117
|
-
const value2 = PortableTextEditor.getValue(editor)
|
|
118
|
-
expect(value2).toEqual([
|
|
119
|
-
initialValue[0],
|
|
120
|
-
initialValue[1],
|
|
121
|
-
{
|
|
122
|
-
_type: 'myTestBlockType',
|
|
123
|
-
_key: '3',
|
|
124
|
-
style: 'normal',
|
|
125
|
-
markDefs: [],
|
|
126
|
-
children: [
|
|
127
|
-
{
|
|
128
|
-
_type: 'span',
|
|
129
|
-
_key: '2',
|
|
130
|
-
text: '',
|
|
131
|
-
marks: [],
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
},
|
|
135
|
-
])
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
describe('plugin:withHotkeys: .ArrowUp', () => {
|
|
141
|
-
it('a new block is added at the top, when pressing arrow up, because first block is void, the new block can be deleted with backspace.', async () => {
|
|
142
|
-
const initialValue = [
|
|
143
|
-
{
|
|
144
|
-
_key: 'b',
|
|
145
|
-
_type: 'someObject',
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
_type: 'myTestBlockType',
|
|
149
|
-
_key: 'a',
|
|
150
|
-
style: 'normal',
|
|
151
|
-
markDefs: [],
|
|
152
|
-
children: [
|
|
153
|
-
{
|
|
154
|
-
_type: 'span',
|
|
155
|
-
_key: 'a1',
|
|
156
|
-
text: 'This is the first block',
|
|
157
|
-
marks: [],
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
},
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
const initialSelection = {
|
|
164
|
-
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
|
|
165
|
-
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
169
|
-
const onChange = jest.fn()
|
|
170
|
-
const component = render(
|
|
171
|
-
<PortableTextEditorTester
|
|
172
|
-
onChange={onChange}
|
|
173
|
-
ref={editorRef}
|
|
174
|
-
schemaType={schemaType}
|
|
175
|
-
value={initialValue}
|
|
176
|
-
/>,
|
|
177
|
-
)
|
|
178
|
-
const element = await getEditableElement(component)
|
|
179
|
-
|
|
180
|
-
const editor = editorRef.current
|
|
181
|
-
const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
|
|
182
|
-
await waitFor(async () => {
|
|
183
|
-
if (editor && inlineType && element) {
|
|
184
|
-
PortableTextEditor.focus(editor)
|
|
185
|
-
PortableTextEditor.select(editor, initialSelection)
|
|
186
|
-
await triggerKeyboardEvent('ArrowUp', element)
|
|
187
|
-
// Arrow down on the text block should not add a new block
|
|
188
|
-
expect(PortableTextEditor.getValue(editor)).toEqual(initialValue)
|
|
189
|
-
// Focus on the object block
|
|
190
|
-
PortableTextEditor.select(editor, {
|
|
191
|
-
focus: {path: [{_key: 'b'}], offset: 0},
|
|
192
|
-
anchor: {path: [{_key: 'b'}], offset: 0},
|
|
193
|
-
})
|
|
194
|
-
await triggerKeyboardEvent('ArrowUp', element)
|
|
195
|
-
expect(PortableTextEditor.getValue(editor)).toEqual([
|
|
196
|
-
newBlock,
|
|
197
|
-
initialValue[0],
|
|
198
|
-
initialValue[1],
|
|
199
|
-
])
|
|
200
|
-
// Pressing arrow up again won't add a new block
|
|
201
|
-
await triggerKeyboardEvent('ArrowUp', element)
|
|
202
|
-
expect(PortableTextEditor.getValue(editor)).toEqual([
|
|
203
|
-
newBlock,
|
|
204
|
-
initialValue[0],
|
|
205
|
-
initialValue[1],
|
|
206
|
-
])
|
|
207
|
-
await triggerKeyboardEvent('Backspace', element)
|
|
208
|
-
expect(PortableTextEditor.getValue(editor)).toEqual(initialValue)
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
})
|