@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.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/lib/_chunks-cjs/selector.get-text-before.cjs +320 -0
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -0
  4. package/lib/_chunks-es/selector.get-text-before.js +321 -0
  5. package/lib/_chunks-es/selector.get-text-before.js.map +1 -0
  6. package/lib/{index.esm.js → index.cjs} +1954 -1513
  7. package/lib/index.cjs.map +1 -0
  8. package/lib/{index.d.mts → index.d.cts} +4249 -304
  9. package/lib/index.d.ts +4249 -304
  10. package/lib/index.js +1974 -1488
  11. package/lib/index.js.map +1 -1
  12. package/lib/selectors/index.cjs +35 -0
  13. package/lib/selectors/index.cjs.map +1 -0
  14. package/lib/selectors/index.d.cts +243 -0
  15. package/lib/selectors/index.d.ts +243 -0
  16. package/lib/selectors/index.js +36 -0
  17. package/lib/selectors/index.js.map +1 -0
  18. package/package.json +25 -17
  19. package/src/editor/Editable.tsx +61 -6
  20. package/src/editor/PortableTextEditor.tsx +19 -4
  21. package/src/editor/__tests__/handleClick.test.tsx +4 -4
  22. package/src/editor/behavior/behavior.action.insert-block-object.ts +1 -1
  23. package/src/editor/behavior/behavior.action.insert-break.ts +24 -27
  24. package/src/editor/behavior/behavior.action.insert-inline-object.ts +58 -0
  25. package/src/editor/behavior/behavior.action.insert-span.ts +1 -1
  26. package/src/editor/behavior/behavior.action.list-item.ts +100 -0
  27. package/src/editor/behavior/behavior.action.style.ts +108 -0
  28. package/src/editor/behavior/behavior.action.text-block.set.ts +25 -0
  29. package/src/editor/behavior/behavior.action.text-block.unset.ts +17 -0
  30. package/src/editor/behavior/behavior.actions.ts +266 -75
  31. package/src/editor/behavior/behavior.code-editor.ts +76 -0
  32. package/src/editor/behavior/behavior.core.block-objects.ts +52 -19
  33. package/src/editor/behavior/behavior.core.decorators.ts +9 -6
  34. package/src/editor/behavior/behavior.core.lists.ts +139 -17
  35. package/src/editor/behavior/behavior.core.ts +7 -2
  36. package/src/editor/behavior/behavior.guards.ts +28 -0
  37. package/src/editor/behavior/behavior.links.ts +9 -9
  38. package/src/editor/behavior/behavior.markdown.ts +69 -80
  39. package/src/editor/behavior/behavior.types.ts +121 -60
  40. package/src/editor/{use-editor.ts → create-editor.ts} +13 -8
  41. package/src/editor/editor-event-listener.tsx +2 -2
  42. package/src/editor/editor-machine.ts +57 -15
  43. package/src/editor/editor-provider.tsx +5 -5
  44. package/src/editor/editor-selector.ts +49 -0
  45. package/src/editor/editor-snapshot.ts +22 -0
  46. package/src/editor/get-value.ts +11 -0
  47. package/src/editor/plugins/create-with-event-listeners.ts +93 -5
  48. package/src/editor/plugins/createWithEditableAPI.ts +69 -20
  49. package/src/editor/plugins/createWithHotKeys.ts +0 -101
  50. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +1 -55
  51. package/src/editor/plugins/with-plugins.ts +4 -8
  52. package/src/editor/{behavior/behavior.utils.block-offset.test.ts → utils/utils.block-offset.test.ts} +1 -1
  53. package/src/editor/{behavior/behavior.utils.block-offset.ts → utils/utils.block-offset.ts} +1 -8
  54. package/src/editor/{behavior/behavior.utils.reverse-selection.ts → utils/utils.reverse-selection.ts} +3 -5
  55. package/src/editor/utils/utils.ts +21 -0
  56. package/src/index.ts +13 -9
  57. package/src/selectors/index.ts +15 -0
  58. package/src/selectors/selector.get-active-list-item.ts +37 -0
  59. package/src/{editor/behavior/behavior.utils.get-selection-text.ts → selectors/selector.get-selection-text.ts} +10 -15
  60. package/src/selectors/selector.get-text-before.ts +41 -0
  61. package/src/selectors/selectors.ts +329 -0
  62. package/src/types/editor.ts +0 -60
  63. package/src/utils/is-hotkey.test.ts +99 -46
  64. package/src/utils/is-hotkey.ts +1 -1
  65. package/src/utils/operationToPatches.ts +5 -0
  66. package/src/utils/paths.ts +4 -11
  67. package/src/utils/ranges.ts +3 -3
  68. package/lib/index.esm.js.map +0 -1
  69. package/lib/index.mjs +0 -7372
  70. package/lib/index.mjs.map +0 -1
  71. package/src/editor/behavior/behavior.utils.ts +0 -218
  72. package/src/editor/behavior/behavior.utilts.get-text-before.ts +0 -31
  73. package/src/editor/plugins/createWithPortableTextLists.ts +0 -172
  74. /package/src/editor/{behavior/behavior.utils.get-start-point.ts → utils/utils.get-start-point.ts} +0 -0
  75. /package/src/editor/{behavior/behavior.utils.is-keyed-segment.ts → utils/utils.is-keyed-segment.ts} +0 -0
@@ -1,9 +1,17 @@
1
- import {deleteBackward, deleteForward, insertText, Transforms} from 'slate'
1
+ import {
2
+ deleteBackward,
3
+ deleteForward,
4
+ insertText,
5
+ Path,
6
+ Transforms,
7
+ } from 'slate'
2
8
  import {ReactEditor} from 'slate-react'
3
- import type {PortableTextMemberSchemaTypes} from '../../types/editor'
9
+ import debug from '../../utils/debug'
10
+ import {toSlatePath} from '../../utils/paths'
4
11
  import {toSlateRange} from '../../utils/ranges'
5
12
  import {fromSlateValue, toSlateValue} from '../../utils/values'
6
13
  import {KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
14
+ import type {EditorContext} from '../editor-snapshot'
7
15
  import {
8
16
  addAnnotationActionImplementation,
9
17
  removeAnnotationActionImplementation,
@@ -14,24 +22,37 @@ import {
14
22
  removeDecoratorActionImplementation,
15
23
  toggleDecoratorActionImplementation,
16
24
  } from '../plugins/createWithPortableTextMarkModel'
25
+ import {blockOffsetToSpanSelectionPoint} from '../utils/utils.block-offset'
17
26
  import {insertBlock} from './behavior.action-utils.insert-block'
18
27
  import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
19
28
  import {
20
29
  insertBreakActionImplementation,
21
30
  insertSoftBreakActionImplementation,
22
31
  } from './behavior.action.insert-break'
32
+ import {insertInlineObjectActionImplementation} from './behavior.action.insert-inline-object'
23
33
  import {insertSpanActionImplementation} from './behavior.action.insert-span'
34
+ import {
35
+ addListItemActionImplementation,
36
+ removeListItemActionImplementation,
37
+ toggleListItemActionImplementation,
38
+ } from './behavior.action.list-item'
39
+ import {
40
+ addStyleActionImplementation,
41
+ removeStyleActionImplementation,
42
+ toggleStyleActionImplementation,
43
+ } from './behavior.action.style'
44
+ import {textBlockSetActionImplementation} from './behavior.action.text-block.set'
45
+ import {textBlockUnsetActionImplementation} from './behavior.action.text-block.unset'
24
46
  import type {
25
47
  BehaviorAction,
26
48
  BehaviorEvent,
27
49
  PickFromUnion,
28
50
  } from './behavior.types'
29
- import {blockOffsetToSpanSelectionPoint} from './behavior.utils.block-offset'
30
51
 
31
- export type BehaviorActionContext = {
32
- keyGenerator: () => string
33
- schema: PortableTextMemberSchemaTypes
34
- }
52
+ export type BehaviorActionImplementationContext = Pick<
53
+ EditorContext,
54
+ 'keyGenerator' | 'schema'
55
+ >
35
56
 
36
57
  export type BehaviorActionImplementation<
37
58
  TBehaviorActionType extends BehaviorAction['type'],
@@ -40,7 +61,7 @@ export type BehaviorActionImplementation<
40
61
  context,
41
62
  action,
42
63
  }: {
43
- context: BehaviorActionContext
64
+ context: BehaviorActionImplementationContext
44
65
  action: PickFromUnion<BehaviorAction, 'type', TBehaviorActionType>
45
66
  }) => TReturnType
46
67
 
@@ -52,47 +73,23 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
52
73
  'annotation.add': addAnnotationActionImplementation,
53
74
  'annotation.remove': removeAnnotationActionImplementation,
54
75
  'annotation.toggle': toggleAnnotationActionImplementation,
76
+ 'blur': ({action}) => {
77
+ ReactEditor.blur(action.editor)
78
+ },
55
79
  'decorator.add': addDecoratorActionImplementation,
56
80
  'decorator.remove': removeDecoratorActionImplementation,
57
81
  'decorator.toggle': toggleDecoratorActionImplementation,
58
82
  'focus': ({action}) => {
59
83
  ReactEditor.focus(action.editor)
60
84
  },
61
- 'set block': ({action}) => {
62
- for (const path of action.paths) {
63
- const at = toSlateRange(
64
- {anchor: {path, offset: 0}, focus: {path, offset: 0}},
65
- action.editor,
66
- )!
67
-
68
- Transforms.setNodes(
69
- action.editor,
70
- {
71
- ...(action.style ? {style: action.style} : {}),
72
- ...(action.listItem ? {listItem: action.listItem} : {}),
73
- ...(action.level ? {level: action.level} : {}),
74
- },
75
- {at},
76
- )
77
- }
78
- },
79
- 'unset block': ({action}) => {
80
- for (const path of action.paths) {
81
- const at = toSlateRange(
82
- {anchor: {path, offset: 0}, focus: {path, offset: 0}},
83
- action.editor,
84
- )!
85
-
86
- Transforms.unsetNodes(action.editor, action.props, {at})
87
- }
88
- },
89
- 'delete backward': ({action}) => {
85
+ 'copy': () => {},
86
+ 'delete.backward': ({action}) => {
90
87
  deleteBackward(action.editor, action.unit)
91
88
  },
92
- 'delete forward': ({action}) => {
89
+ 'delete.forward': ({action}) => {
93
90
  deleteForward(action.editor, action.unit)
94
91
  },
95
- 'delete block': ({action}) => {
92
+ 'delete.block': ({action}) => {
96
93
  const range = toSlateRange(
97
94
  {
98
95
  anchor: {path: action.blockPath, offset: 0},
@@ -110,7 +107,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
110
107
  at: range,
111
108
  })
112
109
  },
113
- 'delete text': ({context, action}) => {
110
+ 'delete.text': ({context, action}) => {
114
111
  const value = fromSlateValue(
115
112
  action.editor.children,
116
113
  context.schema.block.name,
@@ -148,14 +145,15 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
148
145
  at: range,
149
146
  })
150
147
  },
151
- 'insert block object': insertBlockObjectActionImplementation,
152
- 'insert break': insertBreakActionImplementation,
153
- 'insert soft break': insertSoftBreakActionImplementation,
154
- 'insert span': insertSpanActionImplementation,
155
- 'insert text': ({action}) => {
148
+ 'insert.block object': insertBlockObjectActionImplementation,
149
+ 'insert.break': insertBreakActionImplementation,
150
+ 'insert.inline object': insertInlineObjectActionImplementation,
151
+ 'insert.soft break': insertSoftBreakActionImplementation,
152
+ 'insert.span': insertSpanActionImplementation,
153
+ 'insert.text': ({action}) => {
156
154
  insertText(action.editor, action.text)
157
155
  },
158
- 'insert text block': ({context, action}) => {
156
+ 'insert.text block': ({context, action}) => {
159
157
  const block = toSlateValue(
160
158
  [
161
159
  {
@@ -188,9 +186,48 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
188
186
  'effect': ({action}) => {
189
187
  action.effect()
190
188
  },
191
- 'paste': ({action}) => {
192
- action.editor.insertData(action.clipboardData)
189
+ 'key.down': () => {},
190
+ 'key.up': () => {},
191
+ 'list item.add': addListItemActionImplementation,
192
+ 'list item.remove': removeListItemActionImplementation,
193
+ 'list item.toggle': toggleListItemActionImplementation,
194
+ 'move.block': ({action}) => {
195
+ const at = [toSlatePath(action.at, action.editor)[0]]
196
+ const to = [toSlatePath(action.to, action.editor)[0]]
197
+
198
+ Transforms.moveNodes(action.editor, {
199
+ at,
200
+ to,
201
+ mode: 'highest',
202
+ })
203
+ },
204
+ 'move.block down': ({action}) => {
205
+ const at = [toSlatePath(action.at, action.editor)[0]]
206
+ const to = [Path.next(at)[0]]
207
+
208
+ Transforms.moveNodes(action.editor, {
209
+ at,
210
+ to,
211
+ mode: 'highest',
212
+ })
213
+ },
214
+ 'move.block up': ({action}) => {
215
+ const at = [toSlatePath(action.at, action.editor)[0]]
216
+
217
+ if (!Path.hasPrevious(at)) {
218
+ return
219
+ }
220
+
221
+ const to = [Path.previous(at)[0]]
222
+
223
+ Transforms.moveNodes(action.editor, {
224
+ at,
225
+ to,
226
+ mode: 'highest',
227
+ })
193
228
  },
229
+ 'noop': () => {},
230
+ 'paste': () => {},
194
231
  'select': ({action}) => {
195
232
  const newSelection = toSlateRange(action.selection, action.editor)
196
233
 
@@ -200,6 +237,34 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
200
237
  Transforms.deselect(action.editor)
201
238
  }
202
239
  },
240
+ 'select previous block': ({action}) => {
241
+ if (!action.editor.selection) {
242
+ console.error('Unable to select previous block without a selection')
243
+ return
244
+ }
245
+
246
+ const blockPath = action.editor.selection.focus.path.slice(0, 1)
247
+
248
+ if (!Path.hasPrevious(blockPath)) {
249
+ console.error("There's no previous block to select")
250
+ return
251
+ }
252
+
253
+ const previousBlockPath = Path.previous(blockPath)
254
+
255
+ Transforms.select(action.editor, previousBlockPath)
256
+ },
257
+ 'select next block': ({action}) => {
258
+ if (!action.editor.selection) {
259
+ console.error('Unable to select next block without a selection')
260
+ return
261
+ }
262
+
263
+ const blockPath = action.editor.selection.focus.path.slice(0, 1)
264
+ const nextBlockPath = [blockPath[0] + 1]
265
+
266
+ Transforms.select(action.editor, nextBlockPath)
267
+ },
203
268
  'reselect': ({action}) => {
204
269
  const selection = action.editor.selection
205
270
 
@@ -208,60 +273,88 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
208
273
  action.editor.selection = {...selection}
209
274
  }
210
275
  },
276
+ 'style.toggle': toggleStyleActionImplementation,
277
+ 'style.add': addStyleActionImplementation,
278
+ 'style.remove': removeStyleActionImplementation,
279
+ 'text block.set': textBlockSetActionImplementation,
280
+ 'text block.unset': textBlockUnsetActionImplementation,
211
281
  }
212
282
 
213
283
  export function performAction({
214
284
  context,
215
285
  action,
216
286
  }: {
217
- context: BehaviorActionContext
287
+ context: BehaviorActionImplementationContext
218
288
  action: BehaviorAction
219
289
  }) {
290
+ debug('Behavior action', action)
291
+
220
292
  switch (action.type) {
221
- case 'delete block': {
222
- behaviorActionImplementations['delete block']({
293
+ case 'delete.block': {
294
+ behaviorActionImplementations['delete.block']({
295
+ context,
296
+ action,
297
+ })
298
+ break
299
+ }
300
+ case 'delete.text': {
301
+ behaviorActionImplementations['delete.text']({
302
+ context,
303
+ action,
304
+ })
305
+ break
306
+ }
307
+ case 'insert.span': {
308
+ behaviorActionImplementations['insert.span']({
309
+ context,
310
+ action,
311
+ })
312
+ break
313
+ }
314
+ case 'insert.text block': {
315
+ behaviorActionImplementations['insert.text block']({
223
316
  context,
224
317
  action,
225
318
  })
226
319
  break
227
320
  }
228
- case 'delete text': {
229
- behaviorActionImplementations['delete text']({
321
+ case 'list item.add': {
322
+ behaviorActionImplementations['list item.add']({
230
323
  context,
231
324
  action,
232
325
  })
233
326
  break
234
327
  }
235
- case 'insert block object': {
236
- behaviorActionImplementations['insert block object']({
328
+ case 'list item.remove': {
329
+ behaviorActionImplementations['list item.remove']({
237
330
  context,
238
331
  action,
239
332
  })
240
333
  break
241
334
  }
242
- case 'insert span': {
243
- behaviorActionImplementations['insert span']({
335
+ case 'move.block': {
336
+ behaviorActionImplementations['move.block']({
244
337
  context,
245
338
  action,
246
339
  })
247
340
  break
248
341
  }
249
- case 'insert text block': {
250
- behaviorActionImplementations['insert text block']({
342
+ case 'move.block down': {
343
+ behaviorActionImplementations['move.block down']({
251
344
  context,
252
345
  action,
253
346
  })
254
347
  break
255
348
  }
256
- case 'set block': {
257
- behaviorActionImplementations['set block']({
349
+ case 'move.block up': {
350
+ behaviorActionImplementations['move.block up']({
258
351
  context,
259
352
  action,
260
353
  })
261
354
  break
262
355
  }
263
- case 'unset block': {
264
- behaviorActionImplementations['unset block']({
356
+ case 'noop': {
357
+ behaviorActionImplementations.noop({
265
358
  context,
266
359
  action,
267
360
  })
@@ -281,6 +374,20 @@ export function performAction({
281
374
  })
282
375
  break
283
376
  }
377
+ case 'select previous block': {
378
+ behaviorActionImplementations['select previous block']({
379
+ context,
380
+ action,
381
+ })
382
+ break
383
+ }
384
+ case 'select next block': {
385
+ behaviorActionImplementations['select next block']({
386
+ context,
387
+ action,
388
+ })
389
+ break
390
+ }
284
391
  case 'reselect': {
285
392
  behaviorActionImplementations.reselect({
286
393
  context,
@@ -288,6 +395,34 @@ export function performAction({
288
395
  })
289
396
  break
290
397
  }
398
+ case 'style.add': {
399
+ behaviorActionImplementations['style.add']({
400
+ context,
401
+ action,
402
+ })
403
+ break
404
+ }
405
+ case 'style.remove': {
406
+ behaviorActionImplementations['style.remove']({
407
+ context,
408
+ action,
409
+ })
410
+ break
411
+ }
412
+ case 'text block.set': {
413
+ behaviorActionImplementations['text block.set']({
414
+ context,
415
+ action,
416
+ })
417
+ break
418
+ }
419
+ case 'text block.unset': {
420
+ behaviorActionImplementations['text block.unset']({
421
+ context,
422
+ action,
423
+ })
424
+ break
425
+ }
291
426
  default: {
292
427
  performDefaultAction({context, action})
293
428
  }
@@ -298,7 +433,7 @@ function performDefaultAction({
298
433
  context,
299
434
  action,
300
435
  }: {
301
- context: BehaviorActionContext
436
+ context: BehaviorActionImplementationContext
302
437
  action: PickFromUnion<BehaviorAction, 'type', BehaviorEvent['type']>
303
438
  }) {
304
439
  switch (action.type) {
@@ -323,6 +458,20 @@ function performDefaultAction({
323
458
  })
324
459
  break
325
460
  }
461
+ case 'blur': {
462
+ behaviorActionImplementations.blur({
463
+ context,
464
+ action,
465
+ })
466
+ break
467
+ }
468
+ case 'copy': {
469
+ behaviorActionImplementations.copy({
470
+ context,
471
+ action,
472
+ })
473
+ break
474
+ }
326
475
  case 'decorator.add': {
327
476
  behaviorActionImplementations['decorator.add']({
328
477
  context,
@@ -344,15 +493,15 @@ function performDefaultAction({
344
493
  })
345
494
  break
346
495
  }
347
- case 'delete backward': {
348
- behaviorActionImplementations['delete backward']({
496
+ case 'delete.backward': {
497
+ behaviorActionImplementations['delete.backward']({
349
498
  context,
350
499
  action,
351
500
  })
352
501
  break
353
502
  }
354
- case 'delete forward': {
355
- behaviorActionImplementations['delete forward']({
503
+ case 'delete.forward': {
504
+ behaviorActionImplementations['delete.forward']({
356
505
  context,
357
506
  action,
358
507
  })
@@ -365,32 +514,74 @@ function performDefaultAction({
365
514
  })
366
515
  break
367
516
  }
368
- case 'insert break': {
369
- behaviorActionImplementations['insert break']({
517
+ case 'insert.block object': {
518
+ behaviorActionImplementations['insert.block object']({
370
519
  context,
371
520
  action,
372
521
  })
373
522
  break
374
523
  }
375
- case 'insert soft break': {
376
- behaviorActionImplementations['insert soft break']({
524
+ case 'insert.inline object': {
525
+ behaviorActionImplementations['insert.inline object']({
377
526
  context,
378
527
  action,
379
528
  })
380
529
  break
381
530
  }
382
- case 'insert text': {
383
- behaviorActionImplementations['insert text']({
531
+ case 'insert.break': {
532
+ behaviorActionImplementations['insert.break']({
384
533
  context,
385
534
  action,
386
535
  })
387
536
  break
388
537
  }
389
- default: {
538
+ case 'insert.soft break': {
539
+ behaviorActionImplementations['insert.soft break']({
540
+ context,
541
+ action,
542
+ })
543
+ break
544
+ }
545
+ case 'insert.text': {
546
+ behaviorActionImplementations['insert.text']({
547
+ context,
548
+ action,
549
+ })
550
+ break
551
+ }
552
+ case 'key.down': {
553
+ behaviorActionImplementations['key.down']({
554
+ context,
555
+ action,
556
+ })
557
+ break
558
+ }
559
+ case 'key.up': {
560
+ behaviorActionImplementations['key.up']({
561
+ context,
562
+ action,
563
+ })
564
+ break
565
+ }
566
+ case 'list item.toggle': {
567
+ behaviorActionImplementations['list item.toggle']({
568
+ context,
569
+ action,
570
+ })
571
+ break
572
+ }
573
+ case 'paste': {
390
574
  behaviorActionImplementations.paste({
391
575
  context,
392
576
  action,
393
577
  })
578
+ break
579
+ }
580
+ default: {
581
+ behaviorActionImplementations['style.toggle']({
582
+ context,
583
+ action,
584
+ })
394
585
  }
395
586
  }
396
587
  }
@@ -0,0 +1,76 @@
1
+ import {
2
+ getFirstBlock,
3
+ getLastBlock,
4
+ getSelectedBlocks,
5
+ } from '../../selectors/selectors'
6
+ import {isHotkey} from '../../utils/is-hotkey'
7
+ import {defineBehavior} from './behavior.types'
8
+
9
+ /**
10
+ * @alpha
11
+ */
12
+ export type CodeEditorBehaviorsConfig = {
13
+ moveBlockUpShortcut: string
14
+ moveBlockDownShortcut: string
15
+ }
16
+
17
+ /**
18
+ * @alpha
19
+ */
20
+ export function createCodeEditorBehaviors(config: CodeEditorBehaviorsConfig) {
21
+ return [
22
+ defineBehavior({
23
+ on: 'key.down',
24
+ guard: ({context, event}) => {
25
+ const isMoveUpShortcut = isHotkey(
26
+ config.moveBlockUpShortcut,
27
+ event.keyboardEvent,
28
+ )
29
+ const firstBlock = getFirstBlock({context})
30
+ const selectedBlocks = getSelectedBlocks({context})
31
+ const blocksAbove =
32
+ firstBlock?.node._key !== selectedBlocks[0]?.node._key
33
+
34
+ if (!isMoveUpShortcut || !blocksAbove) {
35
+ return false
36
+ }
37
+
38
+ return {paths: selectedBlocks.map((block) => block.path)}
39
+ },
40
+ actions: [
41
+ ({paths}) =>
42
+ paths.map((at) => ({
43
+ type: 'move.block up',
44
+ at,
45
+ })),
46
+ ],
47
+ }),
48
+ defineBehavior({
49
+ on: 'key.down',
50
+ guard: ({context, event}) => {
51
+ const isMoveDownShortcut = isHotkey(
52
+ config.moveBlockDownShortcut,
53
+ event.keyboardEvent,
54
+ )
55
+ const lastBlock = getLastBlock({context})
56
+ const selectedBlocks = getSelectedBlocks({context})
57
+ const blocksBelow =
58
+ lastBlock?.node._key !==
59
+ selectedBlocks[selectedBlocks.length - 1]?.node._key
60
+
61
+ if (!isMoveDownShortcut || !blocksBelow) {
62
+ return false
63
+ }
64
+
65
+ return {paths: selectedBlocks.map((block) => block.path).reverse()}
66
+ },
67
+ actions: [
68
+ ({paths}) =>
69
+ paths.map((at) => ({
70
+ type: 'move.block down',
71
+ at,
72
+ })),
73
+ ],
74
+ }),
75
+ ]
76
+ }