@prosekit/core 0.8.3 → 0.8.5
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/dist/editor-KZlceNQ1.d.ts +722 -0
- package/dist/editor-KZlceNQ1.d.ts.map +1 -0
- package/dist/{editor-DlGlYOp-.js → editor-TvRTsFdO.js} +102 -196
- package/dist/editor-TvRTsFdO.js.map +1 -0
- package/dist/prosekit-core-test.d.ts +20 -19
- package/dist/prosekit-core-test.d.ts.map +1 -0
- package/dist/prosekit-core-test.js +5 -8
- package/dist/prosekit-core-test.js.map +1 -0
- package/dist/prosekit-core.d.ts +797 -792
- package/dist/prosekit-core.d.ts.map +1 -0
- package/dist/prosekit-core.js +42 -79
- package/dist/prosekit-core.js.map +1 -0
- package/package.json +14 -12
- package/src/commands/add-mark.ts +53 -0
- package/src/commands/expand-mark.ts +96 -0
- package/src/commands/insert-default-block.spec.ts +102 -0
- package/src/commands/insert-default-block.ts +49 -0
- package/src/commands/insert-node.ts +71 -0
- package/src/commands/insert-text.ts +24 -0
- package/src/commands/remove-mark.ts +54 -0
- package/src/commands/remove-node.ts +43 -0
- package/src/commands/select-all.ts +16 -0
- package/src/commands/set-block-type.ts +64 -0
- package/src/commands/set-node-attrs.ts +68 -0
- package/src/commands/toggle-mark.ts +65 -0
- package/src/commands/toggle-node.ts +47 -0
- package/src/commands/toggle-wrap.spec.ts +35 -0
- package/src/commands/toggle-wrap.ts +42 -0
- package/src/commands/unset-block-type.spec.ts +49 -0
- package/src/commands/unset-block-type.ts +84 -0
- package/src/commands/unset-mark.spec.ts +35 -0
- package/src/commands/unset-mark.ts +38 -0
- package/src/commands/wrap.ts +50 -0
- package/src/editor/action.spec.ts +143 -0
- package/src/editor/action.ts +248 -0
- package/src/editor/editor.spec.ts +186 -0
- package/src/editor/editor.ts +563 -0
- package/src/editor/union.spec.ts +108 -0
- package/src/editor/union.ts +47 -0
- package/src/editor/with-priority.ts +25 -0
- package/src/error.ts +28 -0
- package/src/extensions/clipboard-serializer.ts +107 -0
- package/src/extensions/command.ts +121 -0
- package/src/extensions/default-state.spec.ts +60 -0
- package/src/extensions/default-state.ts +76 -0
- package/src/extensions/doc.ts +31 -0
- package/src/extensions/events/doc-change.ts +34 -0
- package/src/extensions/events/dom-event.spec.ts +70 -0
- package/src/extensions/events/dom-event.ts +117 -0
- package/src/extensions/events/editor-event.ts +293 -0
- package/src/extensions/events/focus.spec.ts +50 -0
- package/src/extensions/events/focus.ts +28 -0
- package/src/extensions/events/plugin-view.ts +132 -0
- package/src/extensions/history.ts +81 -0
- package/src/extensions/keymap-base.ts +60 -0
- package/src/extensions/keymap.spec.ts +125 -0
- package/src/extensions/keymap.ts +96 -0
- package/src/extensions/mark-spec.spec.ts +177 -0
- package/src/extensions/mark-spec.ts +181 -0
- package/src/extensions/mark-view-effect.ts +85 -0
- package/src/extensions/mark-view.ts +43 -0
- package/src/extensions/node-spec.spec.ts +224 -0
- package/src/extensions/node-spec.ts +199 -0
- package/src/extensions/node-view-effect.ts +85 -0
- package/src/extensions/node-view.ts +43 -0
- package/src/extensions/paragraph.ts +61 -0
- package/src/extensions/plugin.spec.ts +153 -0
- package/src/extensions/plugin.ts +81 -0
- package/src/extensions/text.ts +34 -0
- package/src/facets/base-extension.ts +54 -0
- package/src/facets/command.ts +21 -0
- package/src/facets/facet-extension.spec.ts +173 -0
- package/src/facets/facet-extension.ts +53 -0
- package/src/facets/facet-node.spec.ts +265 -0
- package/src/facets/facet-node.ts +185 -0
- package/src/facets/facet-types.ts +9 -0
- package/src/facets/facet.spec.ts +76 -0
- package/src/facets/facet.ts +84 -0
- package/src/facets/root.ts +44 -0
- package/src/facets/schema-spec.ts +30 -0
- package/src/facets/schema.ts +26 -0
- package/src/facets/state.spec.ts +53 -0
- package/src/facets/state.ts +85 -0
- package/src/facets/union-extension.ts +41 -0
- package/src/index.ts +302 -0
- package/src/test/index.ts +4 -0
- package/src/test/test-builder.ts +68 -0
- package/src/test/test-editor.spec.ts +104 -0
- package/src/test/test-editor.ts +113 -0
- package/src/testing/index.ts +283 -0
- package/src/testing/keyboard.ts +5 -0
- package/src/types/any-function.ts +4 -0
- package/src/types/assert-type-equal.ts +8 -0
- package/src/types/attrs.ts +32 -0
- package/src/types/base-node-view-options.ts +33 -0
- package/src/types/dom-node.ts +1 -0
- package/src/types/extension-command.ts +52 -0
- package/src/types/extension-mark.ts +15 -0
- package/src/types/extension-node.ts +15 -0
- package/src/types/extension.spec.ts +56 -0
- package/src/types/extension.ts +168 -0
- package/src/types/model.ts +54 -0
- package/src/types/object-entries.ts +13 -0
- package/src/types/pick-string-literal.spec.ts +10 -0
- package/src/types/pick-string-literal.ts +6 -0
- package/src/types/pick-sub-type.spec.ts +20 -0
- package/src/types/pick-sub-type.ts +6 -0
- package/src/types/priority.ts +12 -0
- package/src/types/setter.ts +4 -0
- package/src/types/simplify-deeper.spec.ts +40 -0
- package/src/types/simplify-deeper.ts +6 -0
- package/src/types/simplify-union.spec.ts +21 -0
- package/src/types/simplify-union.ts +11 -0
- package/src/utils/array-grouping.spec.ts +29 -0
- package/src/utils/array-grouping.ts +25 -0
- package/src/utils/array.ts +21 -0
- package/src/utils/assert.ts +13 -0
- package/src/utils/attrs-match.ts +20 -0
- package/src/utils/can-use-regex-lookbehind.ts +12 -0
- package/src/utils/clsx.spec.ts +14 -0
- package/src/utils/clsx.ts +14 -0
- package/src/utils/collect-children.ts +21 -0
- package/src/utils/collect-nodes.ts +37 -0
- package/src/utils/combine-event-handlers.spec.ts +27 -0
- package/src/utils/combine-event-handlers.ts +27 -0
- package/src/utils/contains-inline-node.ts +17 -0
- package/src/utils/deep-equals.spec.ts +26 -0
- package/src/utils/deep-equals.ts +29 -0
- package/src/utils/default-block-at.ts +15 -0
- package/src/utils/editor-content.spec.ts +47 -0
- package/src/utils/editor-content.ts +77 -0
- package/src/utils/env.ts +6 -0
- package/src/utils/find-parent-node-of-type.ts +29 -0
- package/src/utils/find-parent-node.spec.ts +68 -0
- package/src/utils/find-parent-node.ts +55 -0
- package/src/utils/get-custom-selection.ts +19 -0
- package/src/utils/get-dom-api.ts +56 -0
- package/src/utils/get-id.spec.ts +14 -0
- package/src/utils/get-id.ts +13 -0
- package/src/utils/get-mark-type.ts +20 -0
- package/src/utils/get-node-type.ts +20 -0
- package/src/utils/get-node-types.ts +19 -0
- package/src/utils/includes-mark.ts +18 -0
- package/src/utils/is-at-block-start.ts +26 -0
- package/src/utils/is-in-code-block.ts +18 -0
- package/src/utils/is-mark-absent.spec.ts +53 -0
- package/src/utils/is-mark-absent.ts +42 -0
- package/src/utils/is-mark-active.ts +27 -0
- package/src/utils/is-node-active.ts +25 -0
- package/src/utils/is-subset.spec.ts +12 -0
- package/src/utils/is-subset.ts +11 -0
- package/src/utils/maybe-run.spec.ts +39 -0
- package/src/utils/maybe-run.ts +11 -0
- package/src/utils/merge-objects.spec.ts +30 -0
- package/src/utils/merge-objects.ts +11 -0
- package/src/utils/merge-specs.ts +35 -0
- package/src/utils/object-equal.spec.ts +26 -0
- package/src/utils/object-equal.ts +28 -0
- package/src/utils/output-spec.test.ts +95 -0
- package/src/utils/output-spec.ts +130 -0
- package/src/utils/parse.spec.ts +46 -0
- package/src/utils/parse.ts +321 -0
- package/src/utils/remove-undefined-values.spec.ts +15 -0
- package/src/utils/remove-undefined-values.ts +9 -0
- package/src/utils/set-selection-around.ts +11 -0
- package/src/utils/type-assertion.ts +91 -0
- package/src/utils/unicode.spec.ts +10 -0
- package/src/utils/unicode.ts +4 -0
- package/src/utils/with-skip-code-block.ts +15 -0
- package/dist/editor-OUH5V8BA.d.ts +0 -754
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { toggleMark as baseToggleMark } from '@prosekit/pm/commands'
|
|
2
|
+
import type {
|
|
3
|
+
Attrs,
|
|
4
|
+
MarkType,
|
|
5
|
+
} from '@prosekit/pm/model'
|
|
6
|
+
import type { Command } from '@prosekit/pm/state'
|
|
7
|
+
|
|
8
|
+
import type { CommandCreator } from '../types/extension-command'
|
|
9
|
+
import { getMarkType } from '../utils/get-mark-type'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface ToggleMarkOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The mark type to toggle.
|
|
17
|
+
*/
|
|
18
|
+
type: string | MarkType
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The optional attributes to set on the mark.
|
|
22
|
+
*/
|
|
23
|
+
attrs?: Attrs | null
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Controls whether, when part of the selected range has the mark
|
|
27
|
+
* already and part doesn't, the mark is removed (`true`) or added
|
|
28
|
+
* (`false`).
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
removeWhenPresent?: boolean
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether the command should act on the content of inline nodes marked as
|
|
36
|
+
* [atoms](https://prosemirror.net/docs/ref/#model.NodeSpec.atom) that are
|
|
37
|
+
* completely covered by a selection range.
|
|
38
|
+
*
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
enterInlineAtoms?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns a command that toggles the given mark with the given attributes.
|
|
46
|
+
*
|
|
47
|
+
* @param options
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export function toggleMark({
|
|
52
|
+
type,
|
|
53
|
+
attrs,
|
|
54
|
+
removeWhenPresent = false,
|
|
55
|
+
enterInlineAtoms = true,
|
|
56
|
+
}: ToggleMarkOptions): Command {
|
|
57
|
+
return (state, dispatch, view) => {
|
|
58
|
+
return baseToggleMark(getMarkType(state.schema, type), attrs, {
|
|
59
|
+
removeWhenPresent,
|
|
60
|
+
enterInlineAtoms,
|
|
61
|
+
})(state, dispatch, view)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
toggleMark satisfies CommandCreator
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { setBlockType } from '@prosekit/pm/commands'
|
|
2
|
+
import type {
|
|
3
|
+
Attrs,
|
|
4
|
+
NodeType,
|
|
5
|
+
} from '@prosekit/pm/model'
|
|
6
|
+
import type { Command } from '@prosekit/pm/state'
|
|
7
|
+
|
|
8
|
+
import { getNodeType } from '../utils/get-node-type'
|
|
9
|
+
import { isNodeActive } from '../utils/is-node-active'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface ToggleNodeOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The type of the node to toggle.
|
|
17
|
+
*/
|
|
18
|
+
type: string | NodeType
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The attributes of the node to toggle.
|
|
22
|
+
*/
|
|
23
|
+
attrs?: Attrs | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns a command that set the selected textblocks to the given node type
|
|
28
|
+
* with the given attributes.
|
|
29
|
+
*
|
|
30
|
+
* @param options
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export function toggleNode({ type, attrs }: ToggleNodeOptions): Command {
|
|
35
|
+
return (state, dispatch, view) => {
|
|
36
|
+
if (isNodeActive(state, type, attrs)) {
|
|
37
|
+
const defaultType = state.schema.topNodeType.contentMatch.defaultType
|
|
38
|
+
if (!defaultType) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
return setBlockType(defaultType)(state, dispatch, view)
|
|
42
|
+
} else {
|
|
43
|
+
const nodeType = getNodeType(state.schema, type)
|
|
44
|
+
return setBlockType(nodeType, attrs)(state, dispatch, view)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
|
|
9
|
+
describe('toggle-wrap', () => {
|
|
10
|
+
it('adds the node wrapping the selection', () => {
|
|
11
|
+
const { editor, n } = setupTest()
|
|
12
|
+
|
|
13
|
+
const doc1 = n.doc(n.paragraph('<a>hello'))
|
|
14
|
+
const doc2 = n.doc(n.blockquote(n.paragraph('<a>hello')))
|
|
15
|
+
|
|
16
|
+
editor.set(doc1)
|
|
17
|
+
expect(editor.state.doc.toJSON()).toEqual(doc1.toJSON())
|
|
18
|
+
|
|
19
|
+
editor.commands.toggleWrap({ type: 'blockquote' })
|
|
20
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('lift the wrapped node', () => {
|
|
24
|
+
const { editor, n } = setupTest()
|
|
25
|
+
|
|
26
|
+
const doc1 = n.doc(n.blockquote(n.paragraph('<a>hello')))
|
|
27
|
+
const doc2 = n.doc(n.paragraph('<a>hello'))
|
|
28
|
+
|
|
29
|
+
editor.set(doc1)
|
|
30
|
+
expect(editor.state.doc.toJSON()).toEqual(doc1.toJSON())
|
|
31
|
+
|
|
32
|
+
editor.commands.toggleWrap({ type: 'paragraph' })
|
|
33
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { lift } from '@prosekit/pm/commands'
|
|
2
|
+
import type {
|
|
3
|
+
Attrs,
|
|
4
|
+
NodeType,
|
|
5
|
+
} from '@prosekit/pm/model'
|
|
6
|
+
import type { Command } from '@prosekit/pm/state'
|
|
7
|
+
|
|
8
|
+
import { isNodeActive } from '../utils/is-node-active'
|
|
9
|
+
|
|
10
|
+
import { wrap } from './wrap'
|
|
11
|
+
|
|
12
|
+
export interface ToggleWrapOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The type of the node to toggle.
|
|
15
|
+
*/
|
|
16
|
+
type: string | NodeType
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The attributes of the node to toggle.
|
|
20
|
+
*/
|
|
21
|
+
attrs?: Attrs | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Toggle between wrapping an inactive node with the provided node type, and
|
|
26
|
+
* lifting it up into its parent.
|
|
27
|
+
*
|
|
28
|
+
* @param options
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export function toggleWrap(options: ToggleWrapOptions): Command {
|
|
33
|
+
const { type, attrs } = options
|
|
34
|
+
|
|
35
|
+
return (state, dispatch) => {
|
|
36
|
+
if (isNodeActive(state, type, attrs)) {
|
|
37
|
+
return lift(state, dispatch)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return wrap({ type, attrs })(state, dispatch)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
|
|
9
|
+
describe('unsetBlockType', () => {
|
|
10
|
+
it('can unset a single block', () => {
|
|
11
|
+
const { editor, n } = setupTest()
|
|
12
|
+
|
|
13
|
+
const doc1 = n.doc(n.heading('<a>1'), n.heading('2'))
|
|
14
|
+
const doc2 = n.doc(n.paragraph('<a>1'), n.heading('2'))
|
|
15
|
+
|
|
16
|
+
editor.set(doc1)
|
|
17
|
+
expect(editor.state.doc.toJSON()).toEqual(doc1.toJSON())
|
|
18
|
+
|
|
19
|
+
editor.commands.unsetBlockType()
|
|
20
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('can unset multiple blocks', () => {
|
|
24
|
+
const { editor, n } = setupTest()
|
|
25
|
+
|
|
26
|
+
const doc1 = n.doc(
|
|
27
|
+
n.heading('0'),
|
|
28
|
+
n.heading('<a>1'),
|
|
29
|
+
n.heading('2'),
|
|
30
|
+
n.heading('<b>3'),
|
|
31
|
+
n.heading('4'),
|
|
32
|
+
n.heading('5'),
|
|
33
|
+
)
|
|
34
|
+
const doc2 = n.doc(
|
|
35
|
+
n.heading('0'),
|
|
36
|
+
n.paragraph('<a>1'),
|
|
37
|
+
n.paragraph('2'),
|
|
38
|
+
n.paragraph('<b>3'),
|
|
39
|
+
n.heading('4'),
|
|
40
|
+
n.heading('5'),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
editor.set(doc1)
|
|
44
|
+
expect(editor.state.doc.toJSON()).toEqual(doc1.toJSON())
|
|
45
|
+
|
|
46
|
+
editor.commands.unsetBlockType()
|
|
47
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
48
|
+
})
|
|
49
|
+
})
|