@portabletext/editor 1.12.3 → 1.14.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/README.md +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs +320 -0
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -0
- package/lib/_chunks-es/selector.get-text-before.js +321 -0
- package/lib/_chunks-es/selector.get-text-before.js.map +1 -0
- package/lib/{index.esm.js → index.cjs} +1954 -1513
- package/lib/index.cjs.map +1 -0
- package/lib/{index.d.mts → index.d.cts} +4249 -304
- package/lib/index.d.ts +4249 -304
- package/lib/index.js +1974 -1488
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs +35 -0
- package/lib/selectors/index.cjs.map +1 -0
- package/lib/selectors/index.d.cts +243 -0
- package/lib/selectors/index.d.ts +243 -0
- package/lib/selectors/index.js +36 -0
- package/lib/selectors/index.js.map +1 -0
- package/package.json +25 -17
- package/src/editor/Editable.tsx +61 -6
- package/src/editor/PortableTextEditor.tsx +19 -4
- package/src/editor/__tests__/handleClick.test.tsx +4 -4
- package/src/editor/behavior/behavior.action.insert-block-object.ts +1 -1
- package/src/editor/behavior/behavior.action.insert-break.ts +24 -27
- package/src/editor/behavior/behavior.action.insert-inline-object.ts +58 -0
- package/src/editor/behavior/behavior.action.insert-span.ts +1 -1
- package/src/editor/behavior/behavior.action.list-item.ts +100 -0
- package/src/editor/behavior/behavior.action.style.ts +108 -0
- package/src/editor/behavior/behavior.action.text-block.set.ts +25 -0
- package/src/editor/behavior/behavior.action.text-block.unset.ts +17 -0
- package/src/editor/behavior/behavior.actions.ts +266 -75
- package/src/editor/behavior/behavior.code-editor.ts +76 -0
- package/src/editor/behavior/behavior.core.block-objects.ts +52 -19
- package/src/editor/behavior/behavior.core.decorators.ts +9 -6
- package/src/editor/behavior/behavior.core.lists.ts +139 -17
- package/src/editor/behavior/behavior.core.ts +7 -2
- package/src/editor/behavior/behavior.guards.ts +28 -0
- package/src/editor/behavior/behavior.links.ts +9 -9
- package/src/editor/behavior/behavior.markdown.ts +69 -80
- package/src/editor/behavior/behavior.types.ts +121 -60
- package/src/editor/{use-editor.ts → create-editor.ts} +13 -8
- package/src/editor/editor-event-listener.tsx +2 -2
- package/src/editor/editor-machine.ts +57 -15
- package/src/editor/editor-provider.tsx +5 -5
- package/src/editor/editor-selector.ts +49 -0
- package/src/editor/editor-snapshot.ts +22 -0
- package/src/editor/get-value.ts +11 -0
- package/src/editor/plugins/create-with-event-listeners.ts +93 -5
- package/src/editor/plugins/createWithEditableAPI.ts +69 -20
- package/src/editor/plugins/createWithHotKeys.ts +0 -101
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +1 -55
- package/src/editor/plugins/with-plugins.ts +4 -8
- package/src/editor/{behavior/behavior.utils.block-offset.test.ts → utils/utils.block-offset.test.ts} +1 -1
- package/src/editor/{behavior/behavior.utils.block-offset.ts → utils/utils.block-offset.ts} +1 -8
- package/src/editor/{behavior/behavior.utils.reverse-selection.ts → utils/utils.reverse-selection.ts} +3 -5
- package/src/editor/utils/utils.ts +21 -0
- package/src/index.ts +13 -9
- package/src/selectors/index.ts +15 -0
- package/src/selectors/selector.get-active-list-item.ts +37 -0
- package/src/{editor/behavior/behavior.utils.get-selection-text.ts → selectors/selector.get-selection-text.ts} +10 -15
- package/src/selectors/selector.get-text-before.ts +41 -0
- package/src/selectors/selectors.ts +329 -0
- package/src/types/editor.ts +0 -60
- package/src/utils/is-hotkey.test.ts +99 -46
- package/src/utils/is-hotkey.ts +1 -1
- package/src/utils/operationToPatches.ts +5 -0
- package/src/utils/paths.ts +4 -11
- package/src/utils/ranges.ts +3 -3
- package/lib/index.esm.js.map +0 -1
- package/lib/index.mjs +0 -7372
- package/lib/index.mjs.map +0 -1
- package/src/editor/behavior/behavior.utils.ts +0 -218
- package/src/editor/behavior/behavior.utilts.get-text-before.ts +0 -31
- package/src/editor/plugins/createWithPortableTextLists.ts +0 -172
- /package/src/editor/{behavior/behavior.utils.get-start-point.ts → utils/utils.get-start-point.ts} +0 -0
- /package/src/editor/{behavior/behavior.utils.is-keyed-segment.ts → utils/utils.is-keyed-segment.ts} +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isKeySegment,
|
|
3
|
+
isPortableTextSpan,
|
|
4
|
+
isPortableTextTextBlock,
|
|
5
|
+
type KeyedSegment,
|
|
6
|
+
type PortableTextBlock,
|
|
7
|
+
type PortableTextListBlock,
|
|
8
|
+
type PortableTextObject,
|
|
9
|
+
type PortableTextSpan,
|
|
10
|
+
type PortableTextTextBlock,
|
|
11
|
+
} from '@sanity/types'
|
|
12
|
+
import {createGuards} from '../editor/behavior/behavior.guards'
|
|
13
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @alpha
|
|
17
|
+
*/
|
|
18
|
+
export const selectionIsCollapsed: EditorSelector<boolean> = ({context}) => {
|
|
19
|
+
return (
|
|
20
|
+
JSON.stringify(context.selection?.anchor.path) ===
|
|
21
|
+
JSON.stringify(context.selection?.focus.path) &&
|
|
22
|
+
context.selection?.anchor.offset === context.selection?.focus.offset
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @alpha
|
|
28
|
+
*/
|
|
29
|
+
export const getFocusBlock: EditorSelector<
|
|
30
|
+
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
31
|
+
> = ({context}) => {
|
|
32
|
+
const key = context.selection
|
|
33
|
+
? isKeySegment(context.selection.focus.path[0])
|
|
34
|
+
? context.selection.focus.path[0]._key
|
|
35
|
+
: undefined
|
|
36
|
+
: undefined
|
|
37
|
+
|
|
38
|
+
const node = key
|
|
39
|
+
? context.value.find((block) => block._key === key)
|
|
40
|
+
: undefined
|
|
41
|
+
|
|
42
|
+
return node && key ? {node, path: [{_key: key}]} : undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @alpha
|
|
47
|
+
*/
|
|
48
|
+
export const getFocusListBlock: EditorSelector<
|
|
49
|
+
{node: PortableTextListBlock; path: [KeyedSegment]} | undefined
|
|
50
|
+
> = ({context}) => {
|
|
51
|
+
const guards = createGuards(context)
|
|
52
|
+
const focusBlock = getFocusBlock({context})
|
|
53
|
+
|
|
54
|
+
return focusBlock && guards.isListBlock(focusBlock.node)
|
|
55
|
+
? {node: focusBlock.node, path: focusBlock.path}
|
|
56
|
+
: undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @alpha
|
|
61
|
+
*/
|
|
62
|
+
export const getFocusTextBlock: EditorSelector<
|
|
63
|
+
{node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
|
|
64
|
+
> = ({context}) => {
|
|
65
|
+
const focusBlock = getFocusBlock({context})
|
|
66
|
+
|
|
67
|
+
return focusBlock && isPortableTextTextBlock(focusBlock.node)
|
|
68
|
+
? {node: focusBlock.node, path: focusBlock.path}
|
|
69
|
+
: undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @alpha
|
|
74
|
+
*/
|
|
75
|
+
export const getFocusBlockObject: EditorSelector<
|
|
76
|
+
{node: PortableTextObject; path: [KeyedSegment]} | undefined
|
|
77
|
+
> = ({context}) => {
|
|
78
|
+
const focusBlock = getFocusBlock({context})
|
|
79
|
+
|
|
80
|
+
return focusBlock && !isPortableTextTextBlock(focusBlock.node)
|
|
81
|
+
? {node: focusBlock.node, path: focusBlock.path}
|
|
82
|
+
: undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @alpha
|
|
87
|
+
*/
|
|
88
|
+
export const getFocusChild: EditorSelector<
|
|
89
|
+
| {
|
|
90
|
+
node: PortableTextObject | PortableTextSpan
|
|
91
|
+
path: [KeyedSegment, 'children', KeyedSegment]
|
|
92
|
+
}
|
|
93
|
+
| undefined
|
|
94
|
+
> = ({context}) => {
|
|
95
|
+
const focusBlock = getFocusTextBlock({context})
|
|
96
|
+
|
|
97
|
+
if (!focusBlock) {
|
|
98
|
+
return undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const key = context.selection
|
|
102
|
+
? isKeySegment(context.selection.focus.path[2])
|
|
103
|
+
? context.selection.focus.path[2]._key
|
|
104
|
+
: undefined
|
|
105
|
+
: undefined
|
|
106
|
+
|
|
107
|
+
const node = key
|
|
108
|
+
? focusBlock.node.children.find((span) => span._key === key)
|
|
109
|
+
: undefined
|
|
110
|
+
|
|
111
|
+
return node && key
|
|
112
|
+
? {node, path: [...focusBlock.path, 'children', {_key: key}]}
|
|
113
|
+
: undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @alpha
|
|
118
|
+
*/
|
|
119
|
+
export const getFocusSpan: EditorSelector<
|
|
120
|
+
| {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
|
|
121
|
+
| undefined
|
|
122
|
+
> = ({context}) => {
|
|
123
|
+
const focusChild = getFocusChild({context})
|
|
124
|
+
|
|
125
|
+
return focusChild && isPortableTextSpan(focusChild.node)
|
|
126
|
+
? {node: focusChild.node, path: focusChild.path}
|
|
127
|
+
: undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @alpha
|
|
132
|
+
*/
|
|
133
|
+
export const getFirstBlock: EditorSelector<
|
|
134
|
+
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
135
|
+
> = ({context}) => {
|
|
136
|
+
const node = context.value[0]
|
|
137
|
+
|
|
138
|
+
return node ? {node, path: [{_key: node._key}]} : undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @alpha
|
|
143
|
+
*/
|
|
144
|
+
export const getLastBlock: EditorSelector<
|
|
145
|
+
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
146
|
+
> = ({context}) => {
|
|
147
|
+
const node = context.value[context.value.length - 1]
|
|
148
|
+
? context.value[context.value.length - 1]
|
|
149
|
+
: undefined
|
|
150
|
+
|
|
151
|
+
return node ? {node, path: [{_key: node._key}]} : undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @alpha
|
|
156
|
+
*/
|
|
157
|
+
export const getSelectedBlocks: EditorSelector<
|
|
158
|
+
Array<{node: PortableTextBlock; path: [KeyedSegment]}>
|
|
159
|
+
> = ({context}) => {
|
|
160
|
+
if (!context.selection) {
|
|
161
|
+
return []
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const selectedBlocks: Array<{node: PortableTextBlock; path: [KeyedSegment]}> =
|
|
165
|
+
[]
|
|
166
|
+
const startKey = context.selection.backward
|
|
167
|
+
? isKeySegment(context.selection.focus.path[0])
|
|
168
|
+
? context.selection.focus.path[0]._key
|
|
169
|
+
: undefined
|
|
170
|
+
: isKeySegment(context.selection.anchor.path[0])
|
|
171
|
+
? context.selection.anchor.path[0]._key
|
|
172
|
+
: undefined
|
|
173
|
+
const endKey = context.selection.backward
|
|
174
|
+
? isKeySegment(context.selection.anchor.path[0])
|
|
175
|
+
? context.selection.anchor.path[0]._key
|
|
176
|
+
: undefined
|
|
177
|
+
: isKeySegment(context.selection.focus.path[0])
|
|
178
|
+
? context.selection.focus.path[0]._key
|
|
179
|
+
: undefined
|
|
180
|
+
|
|
181
|
+
if (!startKey || !endKey) {
|
|
182
|
+
return selectedBlocks
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const block of context.value) {
|
|
186
|
+
if (block._key === startKey) {
|
|
187
|
+
selectedBlocks.push({node: block, path: [{_key: block._key}]})
|
|
188
|
+
|
|
189
|
+
if (startKey === endKey) {
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (block._key === endKey) {
|
|
196
|
+
selectedBlocks.push({node: block, path: [{_key: block._key}]})
|
|
197
|
+
break
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (selectedBlocks.length > 0) {
|
|
201
|
+
selectedBlocks.push({node: block, path: [{_key: block._key}]})
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return selectedBlocks
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @alpha
|
|
210
|
+
*/
|
|
211
|
+
export const getSelectionStartBlock: EditorSelector<
|
|
212
|
+
| {
|
|
213
|
+
node: PortableTextBlock
|
|
214
|
+
path: [KeyedSegment]
|
|
215
|
+
}
|
|
216
|
+
| undefined
|
|
217
|
+
> = ({context}) => {
|
|
218
|
+
if (!context.selection) {
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const key = context.selection.backward
|
|
223
|
+
? isKeySegment(context.selection.focus.path[0])
|
|
224
|
+
? context.selection.focus.path[0]._key
|
|
225
|
+
: undefined
|
|
226
|
+
: isKeySegment(context.selection.anchor.path[0])
|
|
227
|
+
? context.selection.anchor.path[0]._key
|
|
228
|
+
: undefined
|
|
229
|
+
|
|
230
|
+
const node = key
|
|
231
|
+
? context.value.find((block) => block._key === key)
|
|
232
|
+
: undefined
|
|
233
|
+
|
|
234
|
+
return node && key ? {node, path: [{_key: key}]} : undefined
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @alpha
|
|
239
|
+
*/
|
|
240
|
+
export const getSelectionEndBlock: EditorSelector<
|
|
241
|
+
| {
|
|
242
|
+
node: PortableTextBlock
|
|
243
|
+
path: [KeyedSegment]
|
|
244
|
+
}
|
|
245
|
+
| undefined
|
|
246
|
+
> = ({context}) => {
|
|
247
|
+
if (!context.selection) {
|
|
248
|
+
return undefined
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const key = context.selection.backward
|
|
252
|
+
? isKeySegment(context.selection.anchor.path[0])
|
|
253
|
+
? context.selection.anchor.path[0]._key
|
|
254
|
+
: undefined
|
|
255
|
+
: isKeySegment(context.selection.focus.path[0])
|
|
256
|
+
? context.selection.focus.path[0]._key
|
|
257
|
+
: undefined
|
|
258
|
+
|
|
259
|
+
const node = key
|
|
260
|
+
? context.value.find((block) => block._key === key)
|
|
261
|
+
: undefined
|
|
262
|
+
|
|
263
|
+
return node && key ? {node, path: [{_key: key}]} : undefined
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @alpha
|
|
268
|
+
*/
|
|
269
|
+
export const getPreviousBlock: EditorSelector<
|
|
270
|
+
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
271
|
+
> = ({context}) => {
|
|
272
|
+
let previousBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
273
|
+
const selectionStartBlock = getSelectionStartBlock({context})
|
|
274
|
+
|
|
275
|
+
if (!selectionStartBlock) {
|
|
276
|
+
return undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let foundSelectionStartBlock = false
|
|
280
|
+
|
|
281
|
+
for (const block of context.value) {
|
|
282
|
+
if (block._key === selectionStartBlock.node._key) {
|
|
283
|
+
foundSelectionStartBlock = true
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
previousBlock = {node: block, path: [{_key: block._key}]}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (foundSelectionStartBlock && previousBlock) {
|
|
291
|
+
return previousBlock
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return undefined
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @alpha
|
|
299
|
+
*/
|
|
300
|
+
export const getNextBlock: EditorSelector<
|
|
301
|
+
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
302
|
+
> = ({context}) => {
|
|
303
|
+
let nextBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
|
|
304
|
+
const selectionEndBlock = getSelectionEndBlock({context})
|
|
305
|
+
|
|
306
|
+
if (!selectionEndBlock) {
|
|
307
|
+
return undefined
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let foundSelectionEndBlock = false
|
|
311
|
+
|
|
312
|
+
for (const block of context.value) {
|
|
313
|
+
if (block._key === selectionEndBlock.node._key) {
|
|
314
|
+
foundSelectionEndBlock = true
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (foundSelectionEndBlock) {
|
|
319
|
+
nextBlock = {node: block, path: [{_key: block._key}]}
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (foundSelectionEndBlock && nextBlock) {
|
|
325
|
+
return nextBlock
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return undefined
|
|
329
|
+
}
|
package/src/types/editor.ts
CHANGED
|
@@ -133,66 +133,6 @@ export interface PortableTextSlateEditor extends ReactEditor {
|
|
|
133
133
|
isTextSpan: (value: unknown) => value is PortableTextSpan
|
|
134
134
|
isListBlock: (value: unknown) => value is PortableTextListBlock
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
* Increments selected list items levels, or decrements them if `reverse` is true.
|
|
138
|
-
*
|
|
139
|
-
* @param reverse - if true, decrement instead of incrementing
|
|
140
|
-
* @returns True if anything was incremented in the selection
|
|
141
|
-
*/
|
|
142
|
-
pteIncrementBlockLevels: (reverse?: boolean) => boolean
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Toggle selected blocks as listItem
|
|
146
|
-
*
|
|
147
|
-
* @param listStyle - Style of list item to toggle on/off
|
|
148
|
-
*/
|
|
149
|
-
pteToggleListItem: (listStyle: string) => void
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Set selected block as listItem
|
|
153
|
-
*
|
|
154
|
-
* @param listStyle - Style of list item to set
|
|
155
|
-
*/
|
|
156
|
-
pteSetListItem: (listStyle: string) => void
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Unset selected block as listItem
|
|
160
|
-
*
|
|
161
|
-
* @param listStyle - Style of list item to unset
|
|
162
|
-
*/
|
|
163
|
-
pteUnsetListItem: (listStyle: string) => void
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Ends a list
|
|
167
|
-
*
|
|
168
|
-
* @returns True if a list was ended in the selection
|
|
169
|
-
*/
|
|
170
|
-
pteEndList: () => boolean
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Toggle the selected block style
|
|
174
|
-
*
|
|
175
|
-
* @param style - The style name
|
|
176
|
-
*
|
|
177
|
-
*/
|
|
178
|
-
pteToggleBlockStyle: (style: string) => void
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Test if the current selection has a certain block style
|
|
182
|
-
*
|
|
183
|
-
* @param style - The style name
|
|
184
|
-
*
|
|
185
|
-
*/
|
|
186
|
-
pteHasBlockStyle: (style: string) => boolean
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Test if the current selection has a certain list style
|
|
190
|
-
*
|
|
191
|
-
* @param listStyle - Style name to check whether or not the selection has
|
|
192
|
-
*
|
|
193
|
-
*/
|
|
194
|
-
pteHasListStyle: (style: string) => boolean
|
|
195
|
-
|
|
196
136
|
/**
|
|
197
137
|
* Try to expand the current selection to a word
|
|
198
138
|
*/
|
|
@@ -1,61 +1,114 @@
|
|
|
1
1
|
import {expect, test} from 'vitest'
|
|
2
2
|
import {isHotkey, type KeyboardEventLike} from './is-hotkey'
|
|
3
3
|
|
|
4
|
-
function e(
|
|
4
|
+
function e(
|
|
5
|
+
value: string | number,
|
|
6
|
+
modifiers: Array<'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'> = [],
|
|
7
|
+
) {
|
|
5
8
|
return {
|
|
6
9
|
...(typeof value === 'string' ? {key: value} : {keyCode: value}),
|
|
7
|
-
altKey: modifiers.includes('
|
|
8
|
-
ctrlKey: modifiers.includes('
|
|
9
|
-
metaKey: modifiers.includes('
|
|
10
|
-
shiftKey: modifiers.includes('
|
|
10
|
+
altKey: modifiers.includes('altKey'),
|
|
11
|
+
ctrlKey: modifiers.includes('ctrlKey'),
|
|
12
|
+
metaKey: modifiers.includes('metaKey'),
|
|
13
|
+
shiftKey: modifiers.includes('shiftKey'),
|
|
11
14
|
} as KeyboardEventLike
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
type TestCase = [
|
|
15
|
-
|
|
16
|
-
const testCases = [
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[e(
|
|
20
|
-
[e(
|
|
21
|
-
|
|
22
|
-
[e(
|
|
23
|
-
[e(
|
|
24
|
-
[e(
|
|
25
|
-
[e(
|
|
26
|
-
|
|
27
|
-
[
|
|
28
|
-
[
|
|
29
|
-
[
|
|
30
|
-
[e(
|
|
31
|
-
|
|
32
|
-
[e(
|
|
33
|
-
[e(
|
|
34
|
-
[e(83, '
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
-
[e('s', '
|
|
38
|
-
[e('s', '
|
|
39
|
-
[
|
|
40
|
-
[
|
|
41
|
-
|
|
42
|
-
[e('
|
|
43
|
-
[e('
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
|
|
47
|
-
[
|
|
48
|
-
[e('
|
|
49
|
-
[
|
|
50
|
-
[
|
|
17
|
+
type TestCase = [string, KeyboardEventLike, boolean]
|
|
18
|
+
|
|
19
|
+
const testCases: TestCase[] = [
|
|
20
|
+
['meta', e('Meta', ['metaKey']), true],
|
|
21
|
+
['Meta', e('Meta', ['metaKey']), true],
|
|
22
|
+
['meta', e(93, ['metaKey']), true],
|
|
23
|
+
['Meta', e(93, ['metaKey']), true],
|
|
24
|
+
|
|
25
|
+
['meta+s', e('s', ['metaKey']), true],
|
|
26
|
+
['Meta+S', e('s', ['metaKey']), true],
|
|
27
|
+
['meta+s', e(83, ['metaKey']), true],
|
|
28
|
+
['Meta+S', e(83, ['metaKey']), true],
|
|
29
|
+
|
|
30
|
+
['cmd+space', e(' ', ['metaKey']), true],
|
|
31
|
+
['Cmd+Space', e(' ', ['metaKey']), true],
|
|
32
|
+
['cmd+space', e(32, ['metaKey']), true],
|
|
33
|
+
['Cmd+Space', e(32, ['metaKey']), true],
|
|
34
|
+
|
|
35
|
+
['cmd+alt?+s', e('s', ['metaKey']), true],
|
|
36
|
+
['cmd+alt?+s', e('s', ['metaKey', 'altKey']), true],
|
|
37
|
+
['cmd+alt?+s', e(83, ['metaKey']), true],
|
|
38
|
+
['cmd+alt?+s', e(83, ['metaKey', 'altKey']), true],
|
|
39
|
+
|
|
40
|
+
['Cmd+Alt?+S', e('s', ['metaKey']), true],
|
|
41
|
+
['Cmd+Alt?+S', e('s', ['metaKey', 'altKey']), true],
|
|
42
|
+
['Cmd+Alt?+S', e(83, ['metaKey']), true],
|
|
43
|
+
['Cmd+Alt?+S', e(83, ['metaKey', 'altKey']), true],
|
|
44
|
+
|
|
45
|
+
['cmd+s', e('s', ['metaKey', 'altKey']), false],
|
|
46
|
+
['Cmd+S', e('s', ['metaKey', 'altKey']), false],
|
|
47
|
+
['cmd+s', e(83, ['metaKey', 'altKey']), false],
|
|
48
|
+
['Cmd+S', e(83, ['metaKey', 'altKey']), false],
|
|
49
|
+
|
|
50
|
+
['cmd+s', e('s', ['metaKey']), true],
|
|
51
|
+
['Cmd+s', e('s', ['metaKey']), true],
|
|
52
|
+
['cmd+s', e(83, ['metaKey']), true],
|
|
53
|
+
['Cmd+s', e(83, ['metaKey']), true],
|
|
54
|
+
|
|
55
|
+
['mod+s', e('s', ['ctrlKey']), true],
|
|
56
|
+
['Mod+S', e('s', ['ctrlKey']), true],
|
|
57
|
+
['mod+s', e(83, ['ctrlKey']), true],
|
|
58
|
+
['Mod+S', e(83, ['ctrlKey']), true],
|
|
59
|
+
|
|
60
|
+
['meta+alt+s', e('s', ['metaKey', 'altKey']), true],
|
|
61
|
+
['Meta+Alt+S', e('s', ['metaKey', 'altKey']), true],
|
|
62
|
+
['meta+alt+s', e(83, ['metaKey', 'altKey']), true],
|
|
63
|
+
['Meta+Alt+S', e(83, ['metaKey', 'altKey']), true],
|
|
64
|
+
|
|
65
|
+
['?', e('?'), true],
|
|
66
|
+
['?', e('?', ['altKey']), false],
|
|
67
|
+
|
|
68
|
+
['a', e('a'), true],
|
|
69
|
+
['a', e('A'), true],
|
|
70
|
+
['A', e('a'), true],
|
|
71
|
+
['A', e('A'), true],
|
|
72
|
+
['a', e(65), true],
|
|
73
|
+
['A', e(65), true],
|
|
74
|
+
|
|
75
|
+
['a', e('a', ['ctrlKey']), false],
|
|
76
|
+
['A', e('a', ['ctrlKey']), false],
|
|
77
|
+
['a', e(65, ['ctrlKey']), false],
|
|
78
|
+
['A', e(65, ['ctrlKey']), false],
|
|
79
|
+
|
|
80
|
+
['shift', e('Shift', ['shiftKey']), true],
|
|
81
|
+
['Shift', e('Shift', ['shiftKey']), true],
|
|
82
|
+
['shift', e(16, ['shiftKey']), true],
|
|
83
|
+
['Shift', e(16, ['shiftKey']), true],
|
|
84
|
+
|
|
85
|
+
['meta+a', e('a', ['metaKey']), true],
|
|
86
|
+
['Meta+A', e('a', ['metaKey']), true],
|
|
87
|
+
['cmd+a', e(65, ['metaKey']), true],
|
|
88
|
+
['Cmd+A', e(65, ['metaKey']), true],
|
|
89
|
+
|
|
90
|
+
['enter', e('Enter'), true],
|
|
91
|
+
['Enter', e('Enter'), true],
|
|
92
|
+
['enter', e(13), true],
|
|
93
|
+
['Enter', e(13), true],
|
|
94
|
+
['enter', e('Enter', ['shiftKey']), false],
|
|
95
|
+
['Enter', e('Enter', ['shiftKey']), false],
|
|
96
|
+
|
|
97
|
+
['cmd+=', e(187, ['metaKey']), true],
|
|
98
|
+
['Cmd+=', e(187, ['metaKey']), true],
|
|
99
|
+
['cmd++', e('+', ['metaKey']), true],
|
|
100
|
+
['Cmd++', e('+', ['metaKey']), true],
|
|
101
|
+
|
|
102
|
+
['meta+alt+ß', e('ß', ['metaKey', 'altKey']), true],
|
|
103
|
+
['Meta+Alt+ß', e('ß', ['metaKey', 'altKey']), true],
|
|
51
104
|
] satisfies Array<TestCase>
|
|
52
105
|
|
|
53
106
|
test(isHotkey.name, () => {
|
|
54
107
|
for (const testCase of testCases) {
|
|
55
|
-
expect(isHotkey(testCase[
|
|
108
|
+
expect(isHotkey(testCase[0], testCase[1])).toBe(testCase[2])
|
|
56
109
|
}
|
|
57
110
|
|
|
58
|
-
expect(() =>
|
|
59
|
-
'
|
|
60
|
-
)
|
|
111
|
+
expect(() =>
|
|
112
|
+
isHotkey('ctrlalt+k', e('k', ['ctrlKey', 'altKey'])),
|
|
113
|
+
).toThrowError('Unknown modifier: "ctrlalt"')
|
|
61
114
|
})
|
package/src/utils/is-hotkey.ts
CHANGED
|
@@ -438,6 +438,11 @@ export function createOperationToPatches(
|
|
|
438
438
|
const patches: Patch[] = []
|
|
439
439
|
const block = beforeValue[operation.path[0]]
|
|
440
440
|
const targetBlock = beforeValue[operation.newPath[0]]
|
|
441
|
+
|
|
442
|
+
if (!targetBlock) {
|
|
443
|
+
return patches
|
|
444
|
+
}
|
|
445
|
+
|
|
441
446
|
if (operation.path.length === 1) {
|
|
442
447
|
const position: InsertPosition =
|
|
443
448
|
operation.path[0] > operation.newPath[0] ? 'before' : 'after'
|
package/src/utils/paths.ts
CHANGED
|
@@ -7,10 +7,7 @@ import {
|
|
|
7
7
|
type Point,
|
|
8
8
|
type Path as SlatePath,
|
|
9
9
|
} from 'slate'
|
|
10
|
-
import type {
|
|
11
|
-
EditorSelectionPoint,
|
|
12
|
-
PortableTextMemberSchemaTypes,
|
|
13
|
-
} from '../types/editor'
|
|
10
|
+
import type {PortableTextMemberSchemaTypes} from '../types/editor'
|
|
14
11
|
import type {ObjectWithKeyAndType} from './ranges'
|
|
15
12
|
|
|
16
13
|
export function createKeyedPath(
|
|
@@ -41,10 +38,7 @@ export function createKeyedPath(
|
|
|
41
38
|
) as Path
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
export function
|
|
45
|
-
point: EditorSelectionPoint,
|
|
46
|
-
editor: Editor,
|
|
47
|
-
): SlatePath {
|
|
41
|
+
export function toSlatePath(path: Path, editor: Editor): SlatePath {
|
|
48
42
|
if (!editor) {
|
|
49
43
|
return []
|
|
50
44
|
}
|
|
@@ -52,8 +46,7 @@ export function createArrayedPath(
|
|
|
52
46
|
Editor.nodes(editor, {
|
|
53
47
|
at: [],
|
|
54
48
|
match: (n) =>
|
|
55
|
-
isKeySegment(
|
|
56
|
-
(n as Descendant)._key === point.path[0]._key,
|
|
49
|
+
isKeySegment(path[0]) && (n as Descendant)._key === path[0]._key,
|
|
57
50
|
}),
|
|
58
51
|
)[0] || [undefined, undefined]
|
|
59
52
|
if (!block || !Element.isElement(block)) {
|
|
@@ -62,7 +55,7 @@ export function createArrayedPath(
|
|
|
62
55
|
if (editor.isVoid(block)) {
|
|
63
56
|
return [blockPath[0], 0]
|
|
64
57
|
}
|
|
65
|
-
const childPath = [
|
|
58
|
+
const childPath = [path[2]]
|
|
66
59
|
const childIndex = block.children.findIndex((child) =>
|
|
67
60
|
isEqual([{_key: child._key}], childPath),
|
|
68
61
|
)
|
package/src/utils/ranges.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
EditorSelectionPoint,
|
|
5
5
|
PortableTextMemberSchemaTypes,
|
|
6
6
|
} from '../types/editor'
|
|
7
|
-
import {
|
|
7
|
+
import {createKeyedPath, toSlatePath} from './paths'
|
|
8
8
|
|
|
9
9
|
export interface ObjectWithKeyAndType {
|
|
10
10
|
_key: string
|
|
@@ -50,11 +50,11 @@ export function toSlateRange(
|
|
|
50
50
|
return null
|
|
51
51
|
}
|
|
52
52
|
const anchor = {
|
|
53
|
-
path:
|
|
53
|
+
path: toSlatePath(selection.anchor.path, editor),
|
|
54
54
|
offset: selection.anchor.offset,
|
|
55
55
|
}
|
|
56
56
|
const focus = {
|
|
57
|
-
path:
|
|
57
|
+
path: toSlatePath(selection.focus.path, editor),
|
|
58
58
|
offset: selection.focus.offset,
|
|
59
59
|
}
|
|
60
60
|
if (focus.path.length === 0 || anchor.path.length === 0) {
|