@prosekit/core 0.8.7 → 0.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.
Files changed (139) hide show
  1. package/dist/{editor-M9OimMiI.d.ts → editor-BULC1zqX.d.ts} +31 -71
  2. package/dist/editor-BULC1zqX.d.ts.map +1 -0
  3. package/dist/{editor-B0L9BgMi.js → editor-g-Rqn-ZE.js} +119 -136
  4. package/dist/editor-g-Rqn-ZE.js.map +1 -0
  5. package/dist/prosekit-core-test.d.ts +1 -2
  6. package/dist/prosekit-core-test.d.ts.map +1 -1
  7. package/dist/prosekit-core-test.js +1 -1
  8. package/dist/prosekit-core-test.js.map +1 -1
  9. package/dist/prosekit-core.d.ts +182 -202
  10. package/dist/prosekit-core.d.ts.map +1 -1
  11. package/dist/prosekit-core.js +543 -549
  12. package/dist/prosekit-core.js.map +1 -1
  13. package/package.json +9 -12
  14. package/src/commands/add-mark.ts +1 -4
  15. package/src/commands/expand-mark.ts +2 -9
  16. package/src/commands/insert-default-block.spec.ts +1 -5
  17. package/src/commands/insert-default-block.ts +1 -4
  18. package/src/commands/insert-node.ts +4 -8
  19. package/src/commands/remove-mark.ts +1 -4
  20. package/src/commands/remove-node.ts +2 -2
  21. package/src/commands/select-all.ts +3 -8
  22. package/src/commands/select-block.spec.ts +81 -0
  23. package/src/commands/select-block.ts +56 -0
  24. package/src/commands/set-block-type.ts +1 -4
  25. package/src/commands/set-node-attrs-between.spec.ts +221 -0
  26. package/src/commands/set-node-attrs-between.ts +77 -0
  27. package/src/commands/set-node-attrs.spec.ts +129 -0
  28. package/src/commands/set-node-attrs.ts +25 -26
  29. package/src/commands/toggle-mark.ts +1 -4
  30. package/src/commands/toggle-node.ts +2 -5
  31. package/src/commands/toggle-wrap.spec.ts +1 -5
  32. package/src/commands/toggle-wrap.ts +1 -4
  33. package/src/commands/unset-block-type.spec.ts +1 -5
  34. package/src/commands/unset-block-type.ts +2 -8
  35. package/src/commands/unset-mark.spec.ts +1 -5
  36. package/src/commands/wrap.ts +2 -10
  37. package/src/editor/action.spec.ts +2 -6
  38. package/src/editor/action.ts +2 -19
  39. package/src/editor/editor.spec.ts +2 -9
  40. package/src/editor/editor.ts +31 -77
  41. package/src/editor/union.spec.ts +1 -5
  42. package/src/editor/union.ts +1 -4
  43. package/src/extensions/clipboard-serializer.ts +4 -16
  44. package/src/extensions/command.ts +20 -48
  45. package/src/extensions/default-state.spec.ts +1 -9
  46. package/src/extensions/default-state.ts +6 -32
  47. package/src/extensions/events/dom-event.spec.ts +1 -6
  48. package/src/extensions/events/dom-event.ts +5 -20
  49. package/src/extensions/events/editor-event.ts +5 -17
  50. package/src/extensions/events/focus.spec.ts +1 -6
  51. package/src/extensions/events/plugin-view.ts +2 -9
  52. package/src/extensions/history.ts +3 -10
  53. package/src/extensions/keymap-base.spec.ts +89 -0
  54. package/src/extensions/keymap-base.ts +34 -13
  55. package/src/extensions/keymap.spec.ts +12 -22
  56. package/src/extensions/keymap.ts +16 -69
  57. package/src/extensions/mark-spec.spec.ts +5 -20
  58. package/src/extensions/mark-spec.ts +33 -41
  59. package/src/extensions/mark-view-effect.ts +3 -9
  60. package/src/extensions/mark-view.ts +2 -8
  61. package/src/extensions/node-spec.spec.ts +5 -21
  62. package/src/extensions/node-spec.ts +31 -33
  63. package/src/extensions/node-view-effect.ts +3 -9
  64. package/src/extensions/node-view.ts +2 -8
  65. package/src/extensions/plugin.spec.ts +3 -16
  66. package/src/extensions/plugin.ts +4 -14
  67. package/src/facets/base-extension.ts +1 -4
  68. package/src/facets/command.ts +10 -10
  69. package/src/facets/facet-extension.spec.ts +4 -15
  70. package/src/facets/facet-node.spec.ts +2 -9
  71. package/src/facets/facet-node.ts +4 -9
  72. package/src/facets/facet.spec.ts +1 -4
  73. package/src/facets/schema-spec.ts +2 -9
  74. package/src/facets/schema.ts +3 -12
  75. package/src/facets/state.spec.ts +8 -15
  76. package/src/facets/state.ts +5 -20
  77. package/src/facets/union-extension.ts +2 -8
  78. package/src/index.ts +42 -188
  79. package/src/test/index.ts +1 -4
  80. package/src/test/test-builder.ts +1 -4
  81. package/src/test/test-editor.spec.ts +1 -5
  82. package/src/test/test-editor.ts +5 -24
  83. package/src/testing/index.ts +18 -14
  84. package/src/types/extension-command.ts +0 -7
  85. package/src/types/extension.spec.ts +1 -4
  86. package/src/types/extension.ts +3 -29
  87. package/src/types/simplify-union.ts +1 -4
  88. package/src/utils/array-grouping.spec.ts +2 -15
  89. package/src/utils/array-grouping.ts +1 -14
  90. package/src/utils/array.ts +0 -4
  91. package/src/utils/attrs-match.ts +1 -5
  92. package/src/utils/clsx.spec.ts +1 -4
  93. package/src/utils/combine-event-handlers.spec.ts +1 -5
  94. package/src/utils/combine-event-handlers.ts +4 -6
  95. package/src/utils/default-block-at.ts +1 -4
  96. package/src/utils/editor-content.spec.ts +1 -4
  97. package/src/utils/editor-content.ts +7 -19
  98. package/src/utils/find-node.ts +65 -0
  99. package/src/utils/find-parent-node-of-type.ts +6 -12
  100. package/src/utils/find-parent-node.spec.ts +1 -5
  101. package/src/utils/find-parent-node.ts +1 -4
  102. package/src/utils/get-custom-selection.ts +1 -5
  103. package/src/utils/get-mark-type.ts +1 -4
  104. package/src/utils/get-node-type.ts +1 -4
  105. package/src/utils/get-node-types.ts +1 -4
  106. package/src/utils/includes-mark.ts +1 -5
  107. package/src/utils/is-at-block-start.ts +1 -4
  108. package/src/utils/is-mark-absent.spec.ts +1 -4
  109. package/src/utils/is-mark-absent.ts +1 -5
  110. package/src/utils/is-mark-active.ts +1 -4
  111. package/src/utils/is-node-active.spec.ts +109 -0
  112. package/src/utils/is-node-active.ts +17 -8
  113. package/src/utils/is-subset.spec.ts +1 -4
  114. package/src/utils/maybe-run.spec.ts +1 -5
  115. package/src/utils/merge-objects.spec.ts +1 -4
  116. package/src/utils/merge-objects.ts +2 -1
  117. package/src/utils/merge-specs.ts +1 -4
  118. package/src/utils/object-equal.spec.ts +1 -4
  119. package/src/utils/output-spec.test.ts +1 -5
  120. package/src/utils/output-spec.ts +12 -9
  121. package/src/utils/parse.spec.ts +2 -11
  122. package/src/utils/parse.ts +12 -24
  123. package/src/utils/remove-undefined-values.spec.ts +1 -4
  124. package/src/utils/set-selection-around.ts +1 -4
  125. package/src/utils/type-assertion.ts +2 -21
  126. package/src/utils/unicode.spec.ts +1 -4
  127. package/dist/editor-B0L9BgMi.js.map +0 -1
  128. package/dist/editor-M9OimMiI.d.ts.map +0 -1
  129. package/src/extensions/doc.ts +0 -31
  130. package/src/extensions/paragraph.ts +0 -61
  131. package/src/extensions/text.ts +0 -34
  132. package/src/types/base-node-view-options.ts +0 -33
  133. package/src/types/object-entries.ts +0 -13
  134. package/src/utils/collect-children.ts +0 -21
  135. package/src/utils/collect-nodes.ts +0 -37
  136. package/src/utils/deep-equals.spec.ts +0 -26
  137. package/src/utils/deep-equals.ts +0 -29
  138. package/src/utils/get-id.spec.ts +0 -14
  139. package/src/utils/get-id.ts +0 -13
@@ -1,52 +1,18 @@
1
- import {
2
- addMark,
3
- type AddMarkOptions,
4
- } from '../commands/add-mark'
5
- import {
6
- insertDefaultBlock,
7
- type InsertDefaultBlockOptions,
8
- } from '../commands/insert-default-block'
9
- import {
10
- insertNode,
11
- type InsertNodeOptions,
12
- } from '../commands/insert-node'
13
- import {
14
- insertText,
15
- type InsertTextOptions,
16
- } from '../commands/insert-text'
17
- import {
18
- removeMark,
19
- type RemoveMarkOptions,
20
- } from '../commands/remove-mark'
21
- import {
22
- removeNode,
23
- type RemoveNodeOptions,
24
- } from '../commands/remove-node'
1
+ import { addMark, type AddMarkOptions } from '../commands/add-mark'
2
+ import { insertDefaultBlock, type InsertDefaultBlockOptions } from '../commands/insert-default-block'
3
+ import { insertNode, type InsertNodeOptions } from '../commands/insert-node'
4
+ import { insertText, type InsertTextOptions } from '../commands/insert-text'
5
+ import { removeMark, type RemoveMarkOptions } from '../commands/remove-mark'
6
+ import { removeNode, type RemoveNodeOptions } from '../commands/remove-node'
25
7
  import { selectAll } from '../commands/select-all'
26
- import {
27
- setBlockType,
28
- type SetBlockTypeOptions,
29
- } from '../commands/set-block-type'
30
- import {
31
- setNodeAttrs,
32
- type SetNodeAttrsOptions,
33
- } from '../commands/set-node-attrs'
34
- import {
35
- toggleWrap,
36
- type ToggleWrapOptions,
37
- } from '../commands/toggle-wrap'
38
- import {
39
- unsetBlockType,
40
- type UnsetBlockTypeOptions,
41
- } from '../commands/unset-block-type'
42
- import {
43
- unsetMark,
44
- type UnsetMarkOptions,
45
- } from '../commands/unset-mark'
46
- import {
47
- wrap,
48
- type WrapOptions,
49
- } from '../commands/wrap'
8
+ import { selectBlock } from '../commands/select-block'
9
+ import { setBlockType, type SetBlockTypeOptions } from '../commands/set-block-type'
10
+ import { setNodeAttrs, type SetNodeAttrsOptions } from '../commands/set-node-attrs'
11
+ import { setNodeAttrsBetween, type SetNodeAttrsBetweenOptions } from '../commands/set-node-attrs-between'
12
+ import { toggleWrap, type ToggleWrapOptions } from '../commands/toggle-wrap'
13
+ import { unsetBlockType, type UnsetBlockTypeOptions } from '../commands/unset-block-type'
14
+ import { unsetMark, type UnsetMarkOptions } from '../commands/unset-mark'
15
+ import { wrap, type WrapOptions } from '../commands/wrap'
50
16
  import { commandFacet } from '../facets/command'
51
17
  import { defineFacetPayload } from '../facets/facet-extension'
52
18
  import type { Extension } from '../types/extension'
@@ -76,8 +42,10 @@ export type BaseCommandsExtension = Extension<{
76
42
  toggleWrap: [options: ToggleWrapOptions]
77
43
  setBlockType: [options: SetBlockTypeOptions]
78
44
  setNodeAttrs: [options: SetNodeAttrsOptions]
45
+ setNodeAttrsBetween: [options: SetNodeAttrsBetweenOptions]
79
46
  insertDefaultBlock: [options?: InsertDefaultBlockOptions]
80
47
  selectAll: []
48
+ selectBlock: []
81
49
  addMark: [options: AddMarkOptions]
82
50
  removeMark: [options: RemoveMarkOptions]
83
51
  unsetBlockType: [options?: UnsetBlockTypeOptions]
@@ -106,10 +74,14 @@ export function defineBaseCommands(): BaseCommandsExtension {
106
74
 
107
75
  setNodeAttrs,
108
76
 
77
+ setNodeAttrsBetween,
78
+
109
79
  insertDefaultBlock,
110
80
 
111
81
  selectAll,
112
82
 
83
+ selectBlock,
84
+
113
85
  addMark,
114
86
 
115
87
  removeMark,
@@ -1,8 +1,4 @@
1
- import {
2
- describe,
3
- expect,
4
- it,
5
- } from 'vitest'
1
+ import { describe, expect, it } from 'vitest'
6
2
 
7
3
  import { createEditor } from '../editor/editor'
8
4
  import { defineTestExtension } from '../testing'
@@ -27,10 +23,6 @@ describe('defineDefaultState', () => {
27
23
  return editor.state.doc.toString()
28
24
  }
29
25
 
30
- expect(run({ defaultDoc: docJSON })).toContain('docJSON')
31
- expect(run({ defaultHTML: docHTMLString })).toContain('docHTMLString')
32
- expect(run({ defaultHTML: docHTMLElement })).toContain('docHTMLElement')
33
-
34
26
  expect(run({ defaultContent: docJSON })).toContain('docJSON')
35
27
  expect(run({ defaultContent: docHTMLString })).toContain('docHTMLString')
36
28
  expect(run({ defaultContent: docHTMLElement })).toContain('docHTMLElement')
@@ -1,15 +1,9 @@
1
- import {
2
- Selection,
3
- type EditorStateConfig,
4
- } from '@prosekit/pm/state'
1
+ import { Selection, type EditorStateConfig } from '@prosekit/pm/state'
5
2
 
6
3
  import { defineFacetPayload } from '../facets/facet-extension'
7
4
  import { stateFacet } from '../facets/state'
8
5
  import type { PlainExtension } from '../types/extension'
9
- import type {
10
- NodeJSON,
11
- SelectionJSON,
12
- } from '../types/model'
6
+ import type { NodeJSON, SelectionJSON } from '../types/model'
13
7
  import { getEditorContentJSON } from '../utils/editor-content'
14
8
 
15
9
  /**
@@ -18,25 +12,9 @@ import { getEditorContentJSON } from '../utils/editor-content'
18
12
  export interface DefaultStateOptions {
19
13
  /**
20
14
  * The starting document to use when creating the editor. It can be a
21
- * ProseMirror node JSON object, a HTML string, or a HTML element instance.
15
+ * ProseMirror node JSON object, an HTML string, or a DOM element instance.
22
16
  */
23
- defaultContent?: NodeJSON | string | HTMLElement
24
-
25
- /**
26
- * A JSON object representing the starting document to use when creating the
27
- * editor.
28
- *
29
- * @deprecated Use `defaultContent` instead.
30
- */
31
- defaultDoc?: NodeJSON
32
-
33
- /**
34
- * A HTML element or a HTML string representing the starting document to use
35
- * when creating the editor.
36
- *
37
- * @deprecated Use `defaultContent` instead.
38
- */
39
- defaultHTML?: string | HTMLElement
17
+ defaultContent?: NodeJSON | string | Element
40
18
 
41
19
  /**
42
20
  * A JSON object representing the starting selection to use when creating the
@@ -55,16 +33,12 @@ export interface DefaultStateOptions {
55
33
  export function defineDefaultState({
56
34
  defaultSelection,
57
35
  defaultContent,
58
- defaultDoc,
59
- defaultHTML,
60
36
  }: DefaultStateOptions): PlainExtension {
61
- const defaultDocContent = defaultContent || defaultDoc || defaultHTML
62
-
63
37
  return defineFacetPayload(stateFacet, [
64
38
  ({ schema }) => {
65
39
  const config: EditorStateConfig = {}
66
- if (defaultDocContent) {
67
- const json = getEditorContentJSON(schema, defaultDocContent)
40
+ if (defaultContent) {
41
+ const json = getEditorContentJSON(schema, defaultContent)
68
42
  config.doc = schema.nodeFromJSON(json)
69
43
  if (defaultSelection) {
70
44
  config.selection = Selection.fromJSON(config.doc, defaultSelection)
@@ -1,9 +1,4 @@
1
- import {
2
- describe,
3
- expect,
4
- it,
5
- vi,
6
- } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vitest'
7
2
 
8
3
  import { createEditor } from '../../editor/editor'
9
4
  import { defineTestExtension } from '../../testing'
@@ -1,25 +1,13 @@
1
- import {
2
- PluginKey,
3
- ProseMirrorPlugin,
4
- } from '@prosekit/pm/state'
5
- import type {
6
- DOMEventMap,
7
- EditorView,
8
- } from '@prosekit/pm/view'
1
+ import { PluginKey, ProseMirrorPlugin } from '@prosekit/pm/state'
2
+ import type { DOMEventMap, EditorView } from '@prosekit/pm/view'
9
3
 
10
- import {
11
- defineFacet,
12
- type Facet,
13
- } from '../../facets/facet'
4
+ import { defineFacet, type Facet } from '../../facets/facet'
14
5
  import { defineFacetPayload } from '../../facets/facet-extension'
15
6
  import type { PlainExtension } from '../../types/extension'
16
7
  import type { Setter } from '../../types/setter'
17
8
  import { groupEntries } from '../../utils/array-grouping'
18
9
  import { combineEventHandlers } from '../../utils/combine-event-handlers'
19
- import {
20
- pluginFacet,
21
- type PluginPayload,
22
- } from '../plugin'
10
+ import { pluginFacet, type PluginPayload } from '../plugin'
23
11
 
24
12
  /**
25
13
  * A function to handle the events fired on the editable DOM element. Returns
@@ -85,10 +73,7 @@ const domEventFacet: Facet<DOMEventPayload, PluginPayload> = defineFacet(
85
73
  hasNewEvent = true
86
74
  const [setHandlers, combinedHandler] = combineEventHandlers<DOMEventHandler>()
87
75
  setHandlersMap[event] = setHandlers
88
- const e: DOMEventHandler = (view, eventObject) => {
89
- return combinedHandler(view, eventObject)
90
- }
91
- combinedHandlerMap[event] = e
76
+ combinedHandlerMap[event] = combinedHandler
92
77
  }
93
78
  }
94
79
 
@@ -1,26 +1,14 @@
1
- import type {
2
- Node,
3
- Slice,
4
- } from '@prosekit/pm/model'
5
- import {
6
- PluginKey,
7
- ProseMirrorPlugin,
8
- } from '@prosekit/pm/state'
1
+ import type { ObjectEntries } from '@ocavue/utils'
2
+ import type { Node, Slice } from '@prosekit/pm/model'
3
+ import { PluginKey, ProseMirrorPlugin } from '@prosekit/pm/state'
9
4
  import type { EditorView } from '@prosekit/pm/view'
10
5
 
11
- import {
12
- defineFacet,
13
- type Facet,
14
- } from '../../facets/facet'
6
+ import { defineFacet, type Facet } from '../../facets/facet'
15
7
  import { defineFacetPayload } from '../../facets/facet-extension'
16
8
  import type { PlainExtension } from '../../types/extension'
17
- import type { ObjectEntries } from '../../types/object-entries'
18
9
  import { groupEntries } from '../../utils/array-grouping'
19
10
  import { combineEventHandlers } from '../../utils/combine-event-handlers'
20
- import {
21
- pluginFacet,
22
- type PluginPayload,
23
- } from '../plugin'
11
+ import { pluginFacet, type PluginPayload } from '../plugin'
24
12
 
25
13
  export type KeyDownHandler = (
26
14
  view: EditorView,
@@ -1,9 +1,4 @@
1
- import {
2
- describe,
3
- expect,
4
- it,
5
- vi,
6
- } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vitest'
7
2
 
8
3
  import { createEditor } from '../../editor/editor'
9
4
  import { union } from '../../editor/union'
@@ -1,17 +1,10 @@
1
- import {
2
- PluginKey,
3
- ProseMirrorPlugin,
4
- type EditorState,
5
- } from '@prosekit/pm/state'
1
+ import { PluginKey, ProseMirrorPlugin, type EditorState } from '@prosekit/pm/state'
6
2
  import type { EditorView } from '@prosekit/pm/view'
7
3
 
8
4
  import { defineFacet } from '../../facets/facet'
9
5
  import { defineFacetPayload } from '../../facets/facet-extension'
10
6
  import type { PlainExtension } from '../../types/extension'
11
- import {
12
- pluginFacet,
13
- type PluginPayload,
14
- } from '../plugin'
7
+ import { pluginFacet, type PluginPayload } from '../plugin'
15
8
 
16
9
  /**
17
10
  * A function that is called when the editor view is mounted.
@@ -1,23 +1,16 @@
1
- import {
2
- history,
3
- redo,
4
- undo,
5
- } from '@prosekit/pm/history'
1
+ import { history, redo, undo } from '@prosekit/pm/history'
6
2
 
7
3
  import { union } from '../editor/union'
8
4
  import type { Extension } from '../types/extension'
9
5
  import { isApple } from '../utils/env'
10
6
 
11
7
  import { defineCommands } from './command'
12
- import {
13
- defineKeymap,
14
- type Keymap,
15
- } from './keymap'
8
+ import { defineKeymap, type Keymap } from './keymap'
16
9
  import { definePlugin } from './plugin'
17
10
 
18
11
  const keymap: Keymap = {
19
12
  'Mod-z': undo,
20
- 'Shift-Mod-z': redo,
13
+ 'Mod-Z': redo,
21
14
  }
22
15
 
23
16
  if (!isApple) {
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { keyboard } from 'vitest-browser-commands/playwright'
3
+
4
+ import { union } from '../editor/union'
5
+ import type { TestEditor } from '../test'
6
+ import { defineDoc, defineParagraph, defineText, setupTestFromExtension } from '../testing'
7
+ import type { SelectionJSON } from '../types/model'
8
+
9
+ import { defineBaseKeymap } from './keymap-base'
10
+
11
+ describe('Mod-a', () => {
12
+ it('can select the block for the first Mod-a press', async () => {
13
+ const { editor, n } = setupTestFromExtension(union(
14
+ defineDoc(),
15
+ defineText(),
16
+ defineParagraph(),
17
+ defineBaseKeymap(),
18
+ ))
19
+
20
+ editor.set(n.doc(n.paragraph('Fo<a>o<b>'), n.paragraph('Bar')))
21
+ await keyboard.press('ControlOrMeta+a')
22
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
23
+ await keyboard.press('ControlOrMeta+a')
24
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
25
+ })
26
+
27
+ it('can select the entire document if the current textblock is already selected', async () => {
28
+ const { editor, n } = setupTestFromExtension(union(
29
+ defineDoc(),
30
+ defineText(),
31
+ defineParagraph(),
32
+ defineBaseKeymap(),
33
+ ))
34
+
35
+ editor.set(n.doc(n.paragraph('<a>Foo<b>'), n.paragraph('Bar')))
36
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
37
+ await keyboard.press('ControlOrMeta+a')
38
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
39
+ })
40
+
41
+ it('can select the entire document if multiple textblocks are already selected', async () => {
42
+ const { editor, n } = setupTestFromExtension(union(
43
+ defineDoc(),
44
+ defineText(),
45
+ defineParagraph(),
46
+ defineBaseKeymap(),
47
+ ))
48
+
49
+ editor.set(n.doc(n.paragraph('<a>Foo'), n.paragraph('Bar<b>'), n.paragraph('Baz')))
50
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo"), paragraph("Bar")>"`)
51
+ await keyboard.press('ControlOrMeta+a')
52
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar"), paragraph("Baz")>"`)
53
+ })
54
+
55
+ it('can select the entire document if the current textblock is empty', async () => {
56
+ const { editor, n } = setupTestFromExtension(union(
57
+ defineDoc(),
58
+ defineText(),
59
+ defineParagraph(),
60
+ defineBaseKeymap(),
61
+ ))
62
+
63
+ editor.set(n.doc(n.paragraph('Foo'), n.paragraph('<a>'), n.paragraph('Bar')))
64
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <>"`)
65
+ await keyboard.press('ControlOrMeta+a')
66
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph, paragraph("Bar")>"`)
67
+ })
68
+
69
+ it('can select the entire document directly if `preferBlockSelection` is false', async () => {
70
+ const { editor, n } = setupTestFromExtension(union(
71
+ defineDoc(),
72
+ defineText(),
73
+ defineParagraph(),
74
+ defineBaseKeymap({ preferBlockSelection: false }),
75
+ ))
76
+
77
+ editor.set(n.doc(n.paragraph('<a>Foo<b>'), n.paragraph('Bar')))
78
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
79
+ await keyboard.press('ControlOrMeta+a')
80
+ expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
81
+ })
82
+ })
83
+
84
+ function inspectSelection(editor: TestEditor) {
85
+ const selection = editor.state.selection
86
+ const text = selection.content().content.toString()
87
+ const json = selection.toJSON() as SelectionJSON
88
+ return `${json.type}: ${text}`
89
+ }
@@ -6,15 +6,17 @@ import {
6
6
  joinTextblockBackward,
7
7
  liftEmptyBlock,
8
8
  newlineInCode,
9
+ selectAll,
9
10
  selectNodeBackward,
10
11
  } from '@prosekit/pm/commands'
11
12
  import { splitSplittableBlock } from 'prosemirror-splittable'
12
13
 
14
+ import { selectBlockCommand } from '../commands/select-block'
13
15
  import { withPriority } from '../editor/with-priority'
14
16
  import type { PlainExtension } from '../types/extension'
15
17
  import { Priority } from '../types/priority'
16
18
 
17
- import { defineKeymap } from './keymap'
19
+ import { defineKeymap, type Keymap } from './keymap'
18
20
 
19
21
  // Replace `splitBlock` with `splitSplittableBlock`
20
22
  const customEnter = chainCommands(
@@ -31,30 +33,49 @@ const customBackspace = chainCommands(
31
33
  selectNodeBackward,
32
34
  )
33
35
 
34
- const customBaseKeymap = {
35
- ...baseKeymap,
36
- Enter: customEnter,
37
- Backspace: customBackspace,
38
- }
39
-
40
36
  /**
41
37
  * @internal
42
38
  */
43
39
  export type BaseKeymapExtension = PlainExtension
44
40
 
45
41
  /**
46
- * Defines some basic key bindings.
47
- *
48
42
  * @public
49
43
  */
50
- export function defineBaseKeymap(options?: {
44
+ export interface BaseKeymapOptions {
51
45
  /**
52
46
  * The priority of the keymap.
53
47
  *
54
48
  * @default Priority.low
55
49
  */
56
50
  priority?: Priority
57
- }): BaseKeymapExtension {
58
- const priority = options?.priority ?? Priority.low
59
- return withPriority(defineKeymap(customBaseKeymap), priority)
51
+
52
+ /**
53
+ * If `true`, the first `Mod-a` press selects the current block that the
54
+ * cursor is in, and a second press selects the entire document.
55
+ *
56
+ * If `false`, `Mod-a` immediately selects the entire document.
57
+ *
58
+ * @default true
59
+ */
60
+ preferBlockSelection?: boolean
61
+ }
62
+
63
+ /**
64
+ * Defines some basic key bindings.
65
+ *
66
+ * @param options
67
+ *
68
+ * @public
69
+ */
70
+ export function defineBaseKeymap({
71
+ priority = Priority.low,
72
+ preferBlockSelection = true,
73
+ }: BaseKeymapOptions = {}): BaseKeymapExtension {
74
+ const keymap: Keymap = {
75
+ ...baseKeymap,
76
+ 'Mod-a': preferBlockSelection ? chainCommands(selectBlockCommand, selectAll) : selectAll,
77
+ 'Enter': customEnter,
78
+ 'Backspace': customBackspace,
79
+ }
80
+ return withPriority(defineKeymap(keymap), priority)
60
81
  }
@@ -1,27 +1,13 @@
1
1
  import type { Command } from '@prosekit/pm/state'
2
- import {
3
- describe,
4
- expect,
5
- it,
6
- vi,
7
- } from 'vitest'
2
+ import { describe, expect, it, vi } from 'vitest'
8
3
  import { keyboard } from 'vitest-browser-commands/playwright'
9
4
 
10
5
  import { union } from '../editor/union'
11
6
  import { withPriority } from '../editor/with-priority'
12
- import {
13
- defineDoc,
14
- defineParagraph,
15
- defineText,
16
- setupTest,
17
- setupTestFromExtension,
18
- } from '../testing'
7
+ import { defineDoc, defineParagraph, defineText, setupTest, setupTestFromExtension } from '../testing'
19
8
  import { Priority } from '../types/priority'
20
9
 
21
- import {
22
- defineKeymap,
23
- type Keymap,
24
- } from './keymap'
10
+ import { defineKeymap, type Keymap } from './keymap'
25
11
 
26
12
  describe('keymap', () => {
27
13
  it('can register and unregister keymap', () => {
@@ -159,9 +145,13 @@ describe('keymap', () => {
159
145
  // Do not match
160
146
  'ctrl-c',
161
147
  ]
162
- const keymap: Keymap = Object.fromEntries(keybindings.map(binding => [binding, record(binding)]))
163
148
 
164
- editor.use(defineKeymap(keymap))
149
+ for (const key of keybindings) {
150
+ const command: Command = record(key)
151
+ const keymap: Keymap = { [key]: command }
152
+ const extension = defineKeymap(keymap)
153
+ editor.use(extension)
154
+ }
165
155
 
166
156
  called.length = 0
167
157
  await keyboard.down('Control')
@@ -170,8 +160,8 @@ describe('keymap', () => {
170
160
  await keyboard.up('Control')
171
161
  expect(called).toMatchInlineSnapshot(`
172
162
  [
173
- "ctrl-b",
174
163
  "CTRL-b",
164
+ "ctrl-b",
175
165
  ]
176
166
  `)
177
167
 
@@ -184,10 +174,10 @@ describe('keymap', () => {
184
174
  await keyboard.up('Control')
185
175
  expect(called).toMatchInlineSnapshot(`
186
176
  [
187
- "c-s-B",
188
- "c-B",
189
177
  "Ctrl-B",
178
+ "c-s-B",
190
179
  "ctrl-shift-b",
180
+ "c-B",
191
181
  ]
192
182
  `)
193
183
  })
@@ -1,26 +1,12 @@
1
- import { chainCommands } from '@prosekit/pm/commands'
2
1
  import { keydownHandler } from '@prosekit/pm/keymap'
3
- import {
4
- Plugin,
5
- PluginKey,
6
- type Command,
7
- } from '@prosekit/pm/state'
2
+ import { Plugin, PluginKey, type Command } from '@prosekit/pm/state'
8
3
  import type { EditorView } from '@prosekit/pm/view'
9
- import mapValues from 'just-map-values'
10
4
 
11
- import {
12
- defineFacet,
13
- type Facet,
14
- } from '../facets/facet'
5
+ import { defineFacet, type Facet } from '../facets/facet'
15
6
  import { defineFacetPayload } from '../facets/facet-extension'
16
7
  import type { PlainExtension } from '../types/extension'
17
- import { toReversed } from '../utils/array'
18
- import { isApple } from '../utils/env'
19
8
 
20
- import {
21
- pluginFacet,
22
- type PluginPayload,
23
- } from './plugin'
9
+ import { pluginFacet, type PluginPayload } from './plugin'
24
10
 
25
11
  /**
26
12
  * A set of keybindings. Please read the
@@ -55,27 +41,29 @@ export const keymapFacet: Facet<KeymapPayload, PluginPayload> = defineFacet<
55
41
  PluginPayload
56
42
  >({
57
43
  reduce: () => {
58
- type Handler = (view: EditorView, event: KeyboardEvent) => boolean
44
+ type KeydownHandler = (view: EditorView, event: KeyboardEvent) => boolean
59
45
 
60
- let handler: Handler | undefined
46
+ // An array of keymap handlers, ordered from the highest priority to the lowest.
47
+ let subHandlers: KeydownHandler[] = []
61
48
 
62
- const handlerWrapper: Handler = (view, event) => {
63
- if (handler) return handler(view, event)
49
+ // A root handler that combines all the sub handlers.
50
+ const rootHandler: KeydownHandler = (view, event) => {
51
+ for (const handler of subHandlers) {
52
+ if (handler(view, event)) return true
53
+ }
64
54
  return false
65
55
  }
66
56
 
67
57
  const plugin = new Plugin({
68
58
  key: keymapPluginKey,
69
- props: { handleKeyDown: handlerWrapper },
59
+ props: { handleKeyDown: rootHandler },
70
60
  })
71
61
 
72
62
  return (keymaps: Keymap[]) => {
73
- handler = keydownHandler(
74
- mergeKeymaps(
75
- // The keymap at the end have a higher priority.
76
- toReversed(keymaps),
77
- ),
78
- )
63
+ // The keymap at the end has a higher priority, so we need to reverse the
64
+ // order here.
65
+ subHandlers = keymaps.map(keydownHandler).reverse()
66
+
79
67
  return plugin
80
68
  }
81
69
  },
@@ -83,45 +71,4 @@ export const keymapFacet: Facet<KeymapPayload, PluginPayload> = defineFacet<
83
71
  singleton: true,
84
72
  })
85
73
 
86
- function mergeKeymaps(keymaps: Keymap[]): Keymap {
87
- const bindings: Record<string, Command[]> = {}
88
-
89
- for (const keymap of keymaps) {
90
- for (const [key, command] of Object.entries(keymap)) {
91
- const normalizedKey = normalizeKeyName(key)
92
- const commands = bindings[normalizedKey] ||= []
93
- commands.push(command)
94
- }
95
- }
96
-
97
- return mapValues(bindings, mergeCommands)
98
- }
99
-
100
- function mergeCommands(commands: Command[]): Command {
101
- return chainCommands(...commands)
102
- }
103
-
104
- // Copied from https://github.com/ProseMirror/prosemirror-keymap/blob/1.2.3/src/keymap.ts#L8
105
- function normalizeKeyName(name: string) {
106
- let parts = name.split(/-(?!$)/), result = parts[parts.length - 1]
107
- if (result == 'Space') result = ' '
108
- let alt, ctrl, shift, meta
109
- for (let i = 0; i < parts.length - 1; i++) {
110
- let mod = parts[i]
111
- if (/^(cmd|meta|m)$/i.test(mod)) meta = true
112
- else if (/^a(lt)?$/i.test(mod)) alt = true
113
- else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true
114
- else if (/^s(hift)?$/i.test(mod)) shift = true
115
- else if (/^mod$/i.test(mod)) {
116
- if (isApple) meta = true
117
- else ctrl = true
118
- } else throw new Error('Unrecognized modifier name: ' + mod)
119
- }
120
- if (alt) result = 'Alt-' + result
121
- if (ctrl) result = 'Ctrl-' + result
122
- if (meta) result = 'Meta-' + result
123
- if (shift) result = 'Shift-' + result
124
- return result
125
- }
126
-
127
74
  const keymapPluginKey = new PluginKey('prosekit-keymap')