@prosekit/core 0.8.3 → 0.8.4

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 (168) hide show
  1. package/dist/editor-CfkZ4TNU.d.ts +748 -0
  2. package/dist/editor-CfkZ4TNU.d.ts.map +1 -0
  3. package/dist/{editor-DlGlYOp-.js → editor-CizSwUN8.js} +76 -168
  4. package/dist/editor-CizSwUN8.js.map +1 -0
  5. package/dist/prosekit-core-test.d.ts +20 -19
  6. package/dist/prosekit-core-test.d.ts.map +1 -0
  7. package/dist/prosekit-core-test.js +4 -5
  8. package/dist/prosekit-core-test.js.map +1 -0
  9. package/dist/prosekit-core.d.ts +766 -743
  10. package/dist/prosekit-core.d.ts.map +1 -0
  11. package/dist/prosekit-core.js +26 -43
  12. package/dist/prosekit-core.js.map +1 -0
  13. package/package.json +12 -9
  14. package/src/commands/add-mark.ts +53 -0
  15. package/src/commands/expand-mark.ts +96 -0
  16. package/src/commands/insert-default-block.spec.ts +102 -0
  17. package/src/commands/insert-default-block.ts +49 -0
  18. package/src/commands/insert-node.ts +71 -0
  19. package/src/commands/insert-text.ts +24 -0
  20. package/src/commands/remove-mark.ts +54 -0
  21. package/src/commands/remove-node.ts +43 -0
  22. package/src/commands/select-all.ts +16 -0
  23. package/src/commands/set-block-type.ts +64 -0
  24. package/src/commands/set-node-attrs.ts +68 -0
  25. package/src/commands/toggle-mark.ts +65 -0
  26. package/src/commands/toggle-node.ts +47 -0
  27. package/src/commands/toggle-wrap.spec.ts +35 -0
  28. package/src/commands/toggle-wrap.ts +42 -0
  29. package/src/commands/unset-block-type.spec.ts +49 -0
  30. package/src/commands/unset-block-type.ts +84 -0
  31. package/src/commands/unset-mark.spec.ts +35 -0
  32. package/src/commands/unset-mark.ts +38 -0
  33. package/src/commands/wrap.ts +50 -0
  34. package/src/editor/action.spec.ts +143 -0
  35. package/src/editor/action.ts +248 -0
  36. package/src/editor/editor.spec.ts +186 -0
  37. package/src/editor/editor.ts +563 -0
  38. package/src/editor/union.spec.ts +108 -0
  39. package/src/editor/union.ts +47 -0
  40. package/src/editor/with-priority.ts +25 -0
  41. package/src/error.ts +28 -0
  42. package/src/extensions/clipboard-serializer.ts +107 -0
  43. package/src/extensions/command.ts +121 -0
  44. package/src/extensions/default-state.spec.ts +60 -0
  45. package/src/extensions/default-state.ts +76 -0
  46. package/src/extensions/doc.ts +31 -0
  47. package/src/extensions/events/doc-change.ts +34 -0
  48. package/src/extensions/events/dom-event.spec.ts +70 -0
  49. package/src/extensions/events/dom-event.ts +117 -0
  50. package/src/extensions/events/editor-event.ts +293 -0
  51. package/src/extensions/events/focus.spec.ts +50 -0
  52. package/src/extensions/events/focus.ts +28 -0
  53. package/src/extensions/events/plugin-view.ts +132 -0
  54. package/src/extensions/history.ts +81 -0
  55. package/src/extensions/keymap-base.ts +60 -0
  56. package/src/extensions/keymap.spec.ts +89 -0
  57. package/src/extensions/keymap.ts +96 -0
  58. package/src/extensions/mark-spec.spec.ts +177 -0
  59. package/src/extensions/mark-spec.ts +181 -0
  60. package/src/extensions/mark-view-effect.ts +85 -0
  61. package/src/extensions/mark-view.ts +43 -0
  62. package/src/extensions/node-spec.spec.ts +224 -0
  63. package/src/extensions/node-spec.ts +199 -0
  64. package/src/extensions/node-view-effect.ts +85 -0
  65. package/src/extensions/node-view.ts +43 -0
  66. package/src/extensions/paragraph.ts +61 -0
  67. package/src/extensions/plugin.ts +91 -0
  68. package/src/extensions/text.ts +34 -0
  69. package/src/facets/base-extension.ts +54 -0
  70. package/src/facets/command.ts +21 -0
  71. package/src/facets/facet-extension.spec.ts +173 -0
  72. package/src/facets/facet-extension.ts +53 -0
  73. package/src/facets/facet-node.spec.ts +265 -0
  74. package/src/facets/facet-node.ts +185 -0
  75. package/src/facets/facet-types.ts +9 -0
  76. package/src/facets/facet.spec.ts +76 -0
  77. package/src/facets/facet.ts +84 -0
  78. package/src/facets/root.ts +44 -0
  79. package/src/facets/schema-spec.ts +30 -0
  80. package/src/facets/schema.ts +26 -0
  81. package/src/facets/state.ts +57 -0
  82. package/src/facets/union-extension.ts +41 -0
  83. package/src/index.ts +302 -0
  84. package/src/test/index.ts +4 -0
  85. package/src/test/test-builder.ts +68 -0
  86. package/src/test/test-editor.spec.ts +104 -0
  87. package/src/test/test-editor.ts +113 -0
  88. package/src/testing/index.ts +283 -0
  89. package/src/testing/keyboard.ts +5 -0
  90. package/src/types/any-function.ts +4 -0
  91. package/src/types/assert-type-equal.ts +8 -0
  92. package/src/types/attrs.ts +32 -0
  93. package/src/types/base-node-view-options.ts +33 -0
  94. package/src/types/dom-node.ts +1 -0
  95. package/src/types/extension-command.ts +52 -0
  96. package/src/types/extension-mark.ts +15 -0
  97. package/src/types/extension-node.ts +15 -0
  98. package/src/types/extension.spec.ts +56 -0
  99. package/src/types/extension.ts +168 -0
  100. package/src/types/model.ts +54 -0
  101. package/src/types/object-entries.ts +13 -0
  102. package/src/types/pick-string-literal.spec.ts +10 -0
  103. package/src/types/pick-string-literal.ts +6 -0
  104. package/src/types/pick-sub-type.spec.ts +20 -0
  105. package/src/types/pick-sub-type.ts +6 -0
  106. package/src/types/priority.ts +12 -0
  107. package/src/types/setter.ts +4 -0
  108. package/src/types/simplify-deeper.spec.ts +40 -0
  109. package/src/types/simplify-deeper.ts +6 -0
  110. package/src/types/simplify-union.spec.ts +21 -0
  111. package/src/types/simplify-union.ts +11 -0
  112. package/src/utils/array-grouping.spec.ts +29 -0
  113. package/src/utils/array-grouping.ts +25 -0
  114. package/src/utils/array.ts +21 -0
  115. package/src/utils/assert.ts +13 -0
  116. package/src/utils/attrs-match.ts +20 -0
  117. package/src/utils/can-use-regex-lookbehind.ts +12 -0
  118. package/src/utils/clsx.spec.ts +14 -0
  119. package/src/utils/clsx.ts +12 -0
  120. package/src/utils/collect-children.ts +21 -0
  121. package/src/utils/collect-nodes.ts +37 -0
  122. package/src/utils/combine-event-handlers.spec.ts +27 -0
  123. package/src/utils/combine-event-handlers.ts +27 -0
  124. package/src/utils/contains-inline-node.ts +17 -0
  125. package/src/utils/deep-equals.spec.ts +26 -0
  126. package/src/utils/deep-equals.ts +29 -0
  127. package/src/utils/default-block-at.ts +15 -0
  128. package/src/utils/editor-content.spec.ts +47 -0
  129. package/src/utils/editor-content.ts +77 -0
  130. package/src/utils/env.ts +6 -0
  131. package/src/utils/find-parent-node-of-type.ts +29 -0
  132. package/src/utils/find-parent-node.spec.ts +68 -0
  133. package/src/utils/find-parent-node.ts +55 -0
  134. package/src/utils/get-custom-selection.ts +19 -0
  135. package/src/utils/get-dom-api.ts +56 -0
  136. package/src/utils/get-id.spec.ts +14 -0
  137. package/src/utils/get-id.ts +13 -0
  138. package/src/utils/get-mark-type.ts +20 -0
  139. package/src/utils/get-node-type.ts +20 -0
  140. package/src/utils/get-node-types.ts +19 -0
  141. package/src/utils/includes-mark.ts +18 -0
  142. package/src/utils/is-at-block-start.ts +26 -0
  143. package/src/utils/is-in-code-block.ts +18 -0
  144. package/src/utils/is-mark-absent.spec.ts +53 -0
  145. package/src/utils/is-mark-absent.ts +42 -0
  146. package/src/utils/is-mark-active.ts +27 -0
  147. package/src/utils/is-node-active.ts +25 -0
  148. package/src/utils/is-subset.spec.ts +12 -0
  149. package/src/utils/is-subset.ts +11 -0
  150. package/src/utils/maybe-run.spec.ts +39 -0
  151. package/src/utils/maybe-run.ts +11 -0
  152. package/src/utils/merge-objects.spec.ts +30 -0
  153. package/src/utils/merge-objects.ts +11 -0
  154. package/src/utils/merge-specs.ts +35 -0
  155. package/src/utils/object-equal.spec.ts +26 -0
  156. package/src/utils/object-equal.ts +28 -0
  157. package/src/utils/output-spec.test.ts +95 -0
  158. package/src/utils/output-spec.ts +130 -0
  159. package/src/utils/parse.spec.ts +46 -0
  160. package/src/utils/parse.ts +321 -0
  161. package/src/utils/remove-undefined-values.spec.ts +15 -0
  162. package/src/utils/remove-undefined-values.ts +9 -0
  163. package/src/utils/set-selection-around.ts +11 -0
  164. package/src/utils/type-assertion.ts +91 -0
  165. package/src/utils/unicode.spec.ts +10 -0
  166. package/src/utils/unicode.ts +4 -0
  167. package/src/utils/with-skip-code-block.ts +15 -0
  168. package/dist/editor-OUH5V8BA.d.ts +0 -754
@@ -0,0 +1,53 @@
1
+ import type {
2
+ Attrs,
3
+ MarkType,
4
+ } from '@prosekit/pm/model'
5
+ import type { Command } from '@prosekit/pm/state'
6
+
7
+ import type { CommandCreator } from '../types/extension-command'
8
+ import { getMarkType } from '../utils/get-mark-type'
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export interface AddMarkOptions {
14
+ /**
15
+ * The type of the mark to add.
16
+ */
17
+ type: string | MarkType
18
+
19
+ /**
20
+ * The attributes of the mark to add.
21
+ */
22
+ attrs?: Attrs | null
23
+
24
+ /**
25
+ * The start position of the document. By default it will be the start position of current selection.
26
+ */
27
+ from?: number
28
+
29
+ /**
30
+ * The end position of the document. By default it will be the end position of current selection.
31
+ */
32
+ to?: number
33
+ }
34
+
35
+ /**
36
+ * Returns a command that adds the given mark with the given attributes.
37
+ *
38
+ * @public
39
+ */
40
+ export function addMark(options: AddMarkOptions): Command {
41
+ return (state, dispatch) => {
42
+ const mark = getMarkType(state.schema, options.type).create(options.attrs)
43
+ const from = options.from ?? state.selection.from
44
+ const to = options.to ?? state.selection.to
45
+ if (from > to) {
46
+ return false
47
+ }
48
+ dispatch?.(state.tr.addMark(from, to, mark))
49
+ return true
50
+ }
51
+ }
52
+
53
+ addMark satisfies CommandCreator
@@ -0,0 +1,96 @@
1
+ import type {
2
+ Mark,
3
+ MarkType,
4
+ ResolvedPos,
5
+ } from '@prosekit/pm/model'
6
+ import {
7
+ TextSelection,
8
+ type Command,
9
+ } from '@prosekit/pm/state'
10
+
11
+ import { getMarkType } from '../utils/get-mark-type'
12
+
13
+ /**
14
+ * @public
15
+ */
16
+ export interface ExpandMarkOptions {
17
+ /**
18
+ * The type of the mark to expand.
19
+ */
20
+ type: string | MarkType
21
+ }
22
+
23
+ /**
24
+ * Expands the selection to include the entire mark at the current position.
25
+ *
26
+ * @public
27
+ */
28
+ export function expandMark(options: ExpandMarkOptions): Command {
29
+ return (state, dispatch) => {
30
+ const markType = getMarkType(state.schema, options.type)
31
+ const predicate = (mark: Mark) => mark.type === markType
32
+
33
+ const from = expandMarkBefore(state.selection.$from, predicate)
34
+ const to = expandMarkAfter(state.selection.$to, predicate)
35
+
36
+ if (from === state.selection.from && to === state.selection.to) {
37
+ return false
38
+ }
39
+
40
+ if (dispatch) {
41
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, from, to)))
42
+ }
43
+ return true
44
+ }
45
+ }
46
+
47
+ function expandMarkBefore(
48
+ $pos: ResolvedPos,
49
+ predicate: (mark: Mark) => boolean,
50
+ ): number {
51
+ const { parent } = $pos
52
+
53
+ if (!$pos.marks().some(predicate)) {
54
+ return $pos.pos
55
+ }
56
+
57
+ const index = $pos.index()
58
+ let boundaryIndex = index
59
+
60
+ for (let i = index; i >= 0; i--) {
61
+ const node = parent.child(i)
62
+ if (node.marks.some(predicate)) {
63
+ boundaryIndex = i
64
+ } else {
65
+ break
66
+ }
67
+ }
68
+
69
+ return $pos.posAtIndex(boundaryIndex)
70
+ }
71
+
72
+ function expandMarkAfter(
73
+ $pos: ResolvedPos,
74
+ predicate: (mark: Mark) => boolean,
75
+ ): number {
76
+ const { parent } = $pos
77
+
78
+ if (!$pos.marks().some(predicate)) {
79
+ return $pos.pos
80
+ }
81
+
82
+ const index = Math.max(0, $pos.indexAfter() - 1)
83
+ const childCount = parent.childCount
84
+ let boundaryIndex = index
85
+
86
+ for (let i = index; i < childCount; i++) {
87
+ const node = parent.child(i)
88
+ if (node.marks.some(predicate)) {
89
+ boundaryIndex = i
90
+ } else {
91
+ break
92
+ }
93
+ }
94
+
95
+ return $pos.posAtIndex(boundaryIndex) + parent.child(boundaryIndex).nodeSize
96
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ } from 'vitest'
6
+
7
+ import { setupTest } from '../testing'
8
+ import { inputText } from '../testing/keyboard'
9
+
10
+ import { insertDefaultBlock } from './insert-default-block'
11
+
12
+ describe('insertDefaultBlock', () => {
13
+ const { editor, n } = setupTest()
14
+ const doc = n.doc(
15
+ /*0*/
16
+ n.p(/*1*/ 'ab' /*3*/),
17
+ /*4*/
18
+ n.p(/*5*/ 'cd' /*7*/),
19
+ /*8*/
20
+ )
21
+
22
+ it('can insert a default block before the first text block', async () => {
23
+ editor.set(doc)
24
+ editor.exec(insertDefaultBlock({ pos: 0 }))
25
+ expect(editor.state.doc.toJSON()).toEqual(
26
+ n.doc(n.p(), n.p('ab'), n.p('cd')).toJSON(),
27
+ )
28
+ await inputText('x')
29
+ expect(editor.state.doc.toJSON()).toEqual(
30
+ n.doc(n.p('x'), n.p('ab'), n.p('cd')).toJSON(),
31
+ )
32
+ })
33
+
34
+ it('can insert a default block between two text blocks', async () => {
35
+ editor.set(doc)
36
+ editor.exec(insertDefaultBlock({ pos: 4 }))
37
+ expect(editor.state.doc.toJSON()).toEqual(
38
+ n.doc(n.p('ab'), n.p(), n.p('cd')).toJSON(),
39
+ )
40
+ await inputText('x')
41
+ expect(editor.state.doc.toJSON()).toEqual(
42
+ n.doc(n.p('ab'), n.p('x'), n.p('cd')).toJSON(),
43
+ )
44
+ })
45
+
46
+ it('can insert a default block after the last text block', async () => {
47
+ editor.set(doc)
48
+ editor.exec(insertDefaultBlock({ pos: 8 }))
49
+ expect(editor.state.doc.toJSON()).toEqual(
50
+ n.doc(n.p('ab'), n.p('cd'), n.p()).toJSON(),
51
+ )
52
+ await inputText('x')
53
+ expect(editor.state.doc.toJSON()).toEqual(
54
+ n.doc(n.p('ab'), n.p('cd'), n.p('x')).toJSON(),
55
+ )
56
+ })
57
+
58
+ it('can insert a default block after current text block', async () => {
59
+ for (const pos of [1, 2, 3]) {
60
+ editor.set(doc)
61
+ editor.exec(insertDefaultBlock({ pos }))
62
+ expect(editor.state.doc.toJSON()).toEqual(
63
+ n.doc(n.p('ab'), n.p(), n.p('cd')).toJSON(),
64
+ )
65
+ await inputText('x')
66
+ expect(editor.state.doc.toJSON()).toEqual(
67
+ n.doc(n.p('ab'), n.p('x'), n.p('cd')).toJSON(),
68
+ )
69
+ }
70
+
71
+ for (const pos of [5, 6, 7]) {
72
+ editor.set(doc)
73
+ editor.exec(insertDefaultBlock({ pos }))
74
+ expect(editor.state.doc.toJSON()).toEqual(
75
+ n.doc(n.p('ab'), n.p('cd'), n.p()).toJSON(),
76
+ )
77
+ await inputText('x')
78
+ expect(editor.state.doc.toJSON()).toEqual(
79
+ n.doc(n.p('ab'), n.p('cd'), n.p('x')).toJSON(),
80
+ )
81
+ }
82
+ })
83
+
84
+ it('can insert a default block after current selection', async () => {
85
+ const doc = n.doc(
86
+ /*0*/
87
+ n.p(/*1*/ 'a<a>b' /*3*/),
88
+ /*4*/
89
+ n.p(/*5*/ 'cd' /*7*/),
90
+ /*8*/
91
+ )
92
+ editor.set(doc)
93
+ editor.exec(insertDefaultBlock())
94
+ expect(editor.state.doc.toJSON()).toEqual(
95
+ n.doc(n.p('ab'), n.p(), n.p('cd')).toJSON(),
96
+ )
97
+ await inputText('x')
98
+ expect(editor.state.doc.toJSON()).toEqual(
99
+ n.doc(n.p('ab'), n.p('x'), n.p('cd')).toJSON(),
100
+ )
101
+ })
102
+ })
@@ -0,0 +1,49 @@
1
+ import {
2
+ TextSelection,
3
+ type Command,
4
+ } from '@prosekit/pm/state'
5
+
6
+ import { defaultBlockAt } from '../utils/default-block-at'
7
+
8
+ /**
9
+ * @public
10
+ */
11
+ export interface InsertDefaultBlockOptions {
12
+ /**
13
+ * The position to insert the node at. By default it will insert after the
14
+ * current selection.
15
+ */
16
+ pos?: number
17
+ }
18
+
19
+ /**
20
+ * Returns a command that inserts a default block after current selection or at
21
+ * the given position.
22
+ *
23
+ * @public
24
+ */
25
+ export function insertDefaultBlock(
26
+ options?: InsertDefaultBlockOptions,
27
+ ): Command {
28
+ return (state, dispatch) => {
29
+ const $pos = options?.pos == null
30
+ ? state.selection.$to
31
+ : state.doc.resolve(options.pos)
32
+ const depth = $pos.parent.isTextblock ? $pos.depth - 1 : $pos.depth
33
+ const parent = $pos.node(depth)
34
+ const index = $pos.indexAfter(depth)
35
+ const type = defaultBlockAt(parent.contentMatchAt(index))
36
+ if (!type) return false
37
+ if (dispatch) {
38
+ const pos = $pos.posAtIndex(index, depth)
39
+ const node = type.createAndFill()
40
+ if (!node) return false
41
+ const tr = state.tr.insert(pos, node)
42
+ const selection = TextSelection.findFrom(tr.doc.resolve(pos), 1)
43
+ if (!selection) return false
44
+ tr.setSelection(selection)
45
+ dispatch(tr.scrollIntoView())
46
+ }
47
+ return true
48
+ }
49
+ }
@@ -0,0 +1,71 @@
1
+ import type {
2
+ Attrs,
3
+ NodeType,
4
+ ProseMirrorNode,
5
+ } from '@prosekit/pm/model'
6
+ import type { Command } from '@prosekit/pm/state'
7
+ import { insertPoint } from '@prosekit/pm/transform'
8
+
9
+ import { assert } from '../utils/assert'
10
+ import { getNodeType } from '../utils/get-node-type'
11
+ import { setSelectionAround } from '../utils/set-selection-around'
12
+
13
+ /**
14
+ * @public
15
+ */
16
+ export interface InsertNodeOptions {
17
+ /**
18
+ * The node to insert. Either this or `type` must be provided.
19
+ */
20
+ node?: ProseMirrorNode
21
+
22
+ /**
23
+ * The type of the node to insert. Either this or `node` must be provided.
24
+ */
25
+ type?: string | NodeType
26
+
27
+ /**
28
+ * When `type` is provided, the attributes of the node to insert.
29
+ */
30
+ attrs?: Attrs
31
+
32
+ /**
33
+ * The position to insert the node at. By default it will be the anchor
34
+ * position of current selection.
35
+ */
36
+ pos?: number
37
+ }
38
+
39
+ /**
40
+ * Returns a command that inserts the given node at the current selection or at
41
+ * the given position.
42
+ *
43
+ * @public
44
+ */
45
+ function insertNode(options: InsertNodeOptions): Command {
46
+ return (state, dispatch) => {
47
+ const node = options.node
48
+ ? options.node
49
+ : options.type
50
+ ? getNodeType(state.schema, options.type).createAndFill(options.attrs)
51
+ : null
52
+
53
+ assert(node, 'You must provide either a node or a type')
54
+
55
+ const insertPos = insertPoint(
56
+ state.doc,
57
+ options.pos ?? state.selection.anchor,
58
+ node.type,
59
+ )
60
+ if (insertPos == null) return false
61
+
62
+ if (dispatch) {
63
+ const tr = state.tr.insert(insertPos, node)
64
+ setSelectionAround(tr, insertPos + node.nodeSize)
65
+ dispatch(tr)
66
+ }
67
+ return true
68
+ }
69
+ }
70
+
71
+ export { insertNode }
@@ -0,0 +1,24 @@
1
+ import type { Command } from '@prosekit/pm/state'
2
+
3
+ /**
4
+ * @public
5
+ */
6
+ export type InsertTextOptions = {
7
+ text: string
8
+ from?: number
9
+ to?: number
10
+ }
11
+
12
+ /**
13
+ * Returns a command that inserts the given text.
14
+ *
15
+ * @public
16
+ */
17
+ export function insertText({ text, from, to }: InsertTextOptions): Command {
18
+ return (state, dispatch) => {
19
+ if (text) {
20
+ dispatch?.(state.tr.insertText(text, from, to))
21
+ }
22
+ return true
23
+ }
24
+ }
@@ -0,0 +1,54 @@
1
+ import type {
2
+ Attrs,
3
+ MarkType,
4
+ } from '@prosekit/pm/model'
5
+ import type { Command } from '@prosekit/pm/state'
6
+
7
+ import type { CommandCreator } from '../types/extension-command'
8
+ import { getMarkType } from '../utils/get-mark-type'
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export interface RemoveMarkOptions {
14
+ /**
15
+ * The type of the mark to remove.
16
+ */
17
+ type: string | MarkType
18
+
19
+ /**
20
+ * If attrs is given, remove precisely the mark with the given attrs. Otherwise, remove all marks of the given type.
21
+ */
22
+ attrs?: Attrs | null
23
+
24
+ /**
25
+ * The start position of the document. By default it will be the start position of current selection.
26
+ */
27
+ from?: number
28
+
29
+ /**
30
+ * The end position of the document. By default it will be the end position of current selection.
31
+ */
32
+ to?: number
33
+ }
34
+
35
+ /**
36
+ * Returns a command that removes the given mark.
37
+ *
38
+ * @public
39
+ */
40
+ export function removeMark(options: RemoveMarkOptions): Command {
41
+ return (state, dispatch) => {
42
+ const markType = getMarkType(state.schema, options.type)
43
+ const mark = options.attrs ? markType.create(options.attrs) : markType
44
+ const from = options.from ?? state.selection.from
45
+ const to = options.to ?? state.selection.to
46
+ if (from > to) {
47
+ return false
48
+ }
49
+ dispatch?.(state.tr.removeMark(from, to, mark))
50
+ return true
51
+ }
52
+ }
53
+
54
+ removeMark satisfies CommandCreator
@@ -0,0 +1,43 @@
1
+ import type { NodeType } from '@prosekit/pm/model'
2
+ import type { Command } from '@prosekit/pm/state'
3
+
4
+ import type { CommandCreator } from '../types/extension-command'
5
+ import { findParentNodeOfType } from '../utils/find-parent-node-of-type'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export interface RemoveNodeOptions {
11
+ /**
12
+ * The type of the node to remove.
13
+ */
14
+ type: string | NodeType
15
+
16
+ /**
17
+ * The document position to start searching node. By default it will be the
18
+ * anchor position of current selection.
19
+ */
20
+ pos?: number
21
+ }
22
+
23
+ /**
24
+ * Returns a command to remove the nearest ancestor node of a specific type from the current position.
25
+ *
26
+ * @public
27
+ */
28
+ export function removeNode(options: RemoveNodeOptions): Command {
29
+ return (state, dispatch) => {
30
+ const $pos = typeof options.pos === 'number'
31
+ ? state.doc.resolve(options.pos)
32
+ : state.selection.$anchor
33
+
34
+ const found = findParentNodeOfType(options.type, $pos)
35
+ if (!found) return false
36
+
37
+ const { pos, node } = found
38
+ dispatch?.(state.tr.delete(pos, pos + node.nodeSize))
39
+ return true
40
+ }
41
+ }
42
+
43
+ removeNode satisfies CommandCreator
@@ -0,0 +1,16 @@
1
+ import {
2
+ AllSelection,
3
+ type Command,
4
+ } from '@prosekit/pm/state'
5
+
6
+ /**
7
+ * Returns a command that selects the whole document.
8
+ *
9
+ * @public
10
+ */
11
+ export function selectAll(): Command {
12
+ return (state, dispatch) => {
13
+ dispatch?.(state.tr.setSelection(new AllSelection(state.doc)))
14
+ return true
15
+ }
16
+ }
@@ -0,0 +1,64 @@
1
+ import type {
2
+ Attrs,
3
+ NodeType,
4
+ } from '@prosekit/pm/model'
5
+ import type { Command } from '@prosekit/pm/state'
6
+
7
+ import { getCustomSelection } from '../utils/get-custom-selection'
8
+ import { getNodeType } from '../utils/get-node-type'
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export interface SetBlockTypeOptions {
14
+ type: NodeType | string
15
+ attrs?: Attrs | null
16
+ from?: number
17
+ to?: number
18
+ }
19
+
20
+ /**
21
+ * Returns a command that tries to set the selected textblocks to the given node
22
+ * type with the given attributes.
23
+ *
24
+ * @public
25
+ */
26
+ export function setBlockType(options: SetBlockTypeOptions): Command {
27
+ return (state, dispatch) => {
28
+ const nodeType = getNodeType(state.schema, options.type)
29
+ const selection = getCustomSelection(state, options.from, options.to)
30
+ const attrs = options.attrs
31
+
32
+ let applicable = false
33
+ for (let i = 0; i < selection.ranges.length && !applicable; i++) {
34
+ const {
35
+ $from: { pos: from },
36
+ $to: { pos: to },
37
+ } = selection.ranges[i]
38
+ state.doc.nodesBetween(from, to, (node, pos) => {
39
+ if (applicable) return false
40
+ if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return
41
+ if (node.type == nodeType) {
42
+ applicable = true
43
+ } else {
44
+ const $pos = state.doc.resolve(pos),
45
+ index = $pos.index()
46
+ applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType)
47
+ }
48
+ })
49
+ }
50
+ if (!applicable) return false
51
+ if (dispatch) {
52
+ const tr = state.tr
53
+ for (const range of selection.ranges) {
54
+ const {
55
+ $from: { pos: from },
56
+ $to: { pos: to },
57
+ } = range
58
+ tr.setBlockType(from, to, nodeType, attrs)
59
+ }
60
+ dispatch(tr.scrollIntoView())
61
+ }
62
+ return true
63
+ }
64
+ }
@@ -0,0 +1,68 @@
1
+ import type {
2
+ Attrs,
3
+ NodeType,
4
+ } from '@prosekit/pm/model'
5
+ import type { Command } from '@prosekit/pm/state'
6
+
7
+ import { getNodeTypes } from '../utils/get-node-types'
8
+
9
+ /**
10
+ * @public
11
+ */
12
+ export interface SetNodeAttrsOptions {
13
+ /**
14
+ * The type of node to set the attributes of.
15
+ *
16
+ * If current node is not of this type, the command will do nothing.
17
+ */
18
+ type: string | NodeType | string[] | NodeType[]
19
+
20
+ /**
21
+ * The attributes to set.
22
+ */
23
+ attrs: Attrs
24
+
25
+ /**
26
+ * The position of the node. Defaults to the position of the wrapping node
27
+ * containing the current selection.
28
+ */
29
+ pos?: number
30
+ }
31
+
32
+ /**
33
+ * Returns a command that set the attributes of the current node.
34
+ *
35
+ * @public
36
+ */
37
+ export function setNodeAttrs(options: SetNodeAttrsOptions): Command {
38
+ return (state, dispatch) => {
39
+ const nodeTypes = getNodeTypes(state.schema, options.type)
40
+ const from = options.pos ?? state.selection.from
41
+ const to = options.pos ?? state.selection.to
42
+ const positions: number[] = []
43
+
44
+ state.doc.nodesBetween(from, to, (node, pos) => {
45
+ if (nodeTypes.includes(node.type)) {
46
+ positions.push(pos)
47
+ }
48
+ if (!dispatch && positions.length > 0) {
49
+ return false
50
+ }
51
+ })
52
+
53
+ if (positions.length === 0) {
54
+ return false
55
+ }
56
+
57
+ if (dispatch) {
58
+ const { tr } = state
59
+ for (const pos of positions) {
60
+ for (const [key, value] of Object.entries(options.attrs)) {
61
+ tr.setNodeAttribute(pos, key, value)
62
+ }
63
+ }
64
+ dispatch(tr)
65
+ }
66
+ return true
67
+ }
68
+ }