@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.
- package/dist/editor-CfkZ4TNU.d.ts +748 -0
- package/dist/editor-CfkZ4TNU.d.ts.map +1 -0
- package/dist/{editor-DlGlYOp-.js → editor-CizSwUN8.js} +76 -168
- package/dist/editor-CizSwUN8.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 +4 -5
- package/dist/prosekit-core-test.js.map +1 -0
- package/dist/prosekit-core.d.ts +766 -743
- package/dist/prosekit-core.d.ts.map +1 -0
- package/dist/prosekit-core.js +26 -43
- package/dist/prosekit-core.js.map +1 -0
- package/package.json +12 -9
- 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 +89 -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.ts +91 -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.ts +57 -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 +12 -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,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
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Fragment,
|
|
3
|
+
Slice,
|
|
4
|
+
} from '@prosekit/pm/model'
|
|
5
|
+
import type {
|
|
6
|
+
Command,
|
|
7
|
+
Transaction,
|
|
8
|
+
} from '@prosekit/pm/state'
|
|
9
|
+
import { ReplaceAroundStep } from '@prosekit/pm/transform'
|
|
10
|
+
|
|
11
|
+
import type { CommandCreator } from '../types/extension-command'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export interface UnsetBlockTypeOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The start position of the document. By default it will be the start position of current selection.
|
|
19
|
+
*/
|
|
20
|
+
from?: number
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The end position of the document. By default it will be the end position of current selection.
|
|
24
|
+
*/
|
|
25
|
+
to?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns a command that set the type of all textblocks between the given range
|
|
30
|
+
* to the default type (usually `paragraph`).
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export function unsetBlockType(options?: UnsetBlockTypeOptions): Command {
|
|
35
|
+
return (state, dispatch) => {
|
|
36
|
+
const from = options?.from ?? state.selection.from
|
|
37
|
+
const to = options?.to ?? state.selection.to
|
|
38
|
+
if (from > to) return false
|
|
39
|
+
|
|
40
|
+
const tr = state.tr
|
|
41
|
+
if (unsetTextBlockType(tr, from, to)) {
|
|
42
|
+
dispatch?.(tr)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function unsetTextBlockType(
|
|
50
|
+
tr: Transaction,
|
|
51
|
+
from: number,
|
|
52
|
+
to: number,
|
|
53
|
+
): boolean {
|
|
54
|
+
const mapFrom = tr.steps.length
|
|
55
|
+
tr.doc.nodesBetween(from, to, (node, pos, parent, index): boolean => {
|
|
56
|
+
if (!parent || !node.isTextblock) return true
|
|
57
|
+
|
|
58
|
+
const defaultType = parent.contentMatchAt(index).defaultType
|
|
59
|
+
if (
|
|
60
|
+
defaultType
|
|
61
|
+
&& defaultType.isTextblock
|
|
62
|
+
&& node.type !== defaultType
|
|
63
|
+
&& defaultType.validContent(node.content)
|
|
64
|
+
) {
|
|
65
|
+
const mapping = tr.mapping.slice(mapFrom)
|
|
66
|
+
const start = mapping.map(pos, 1)
|
|
67
|
+
const end = mapping.map(pos + node.nodeSize, 1)
|
|
68
|
+
const step = new ReplaceAroundStep(
|
|
69
|
+
start,
|
|
70
|
+
end,
|
|
71
|
+
start + 1,
|
|
72
|
+
end - 1,
|
|
73
|
+
new Slice(Fragment.from(defaultType.create()), 0, 0),
|
|
74
|
+
1,
|
|
75
|
+
true,
|
|
76
|
+
)
|
|
77
|
+
tr.step(step)
|
|
78
|
+
}
|
|
79
|
+
return false
|
|
80
|
+
})
|
|
81
|
+
return tr.steps.length > mapFrom
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
unsetBlockType satisfies CommandCreator
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
|
|
9
|
+
describe('unsetMark', () => {
|
|
10
|
+
it('can unset marks', () => {
|
|
11
|
+
const { editor, m, n } = setupTest()
|
|
12
|
+
|
|
13
|
+
const doc1 = n.doc(
|
|
14
|
+
n.paragraph(
|
|
15
|
+
'<a>',
|
|
16
|
+
'1',
|
|
17
|
+
m.bold('2'),
|
|
18
|
+
m.italic('3'),
|
|
19
|
+
'<b>',
|
|
20
|
+
m.italic('4'),
|
|
21
|
+
m.italic('5'),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const doc2 = n.doc(
|
|
26
|
+
n.paragraph('<a>', '123', '<b>', m.italic('4'), m.italic('5')),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
editor.set(doc1)
|
|
30
|
+
expect(editor.state.doc.toJSON()).toEqual(doc1.toJSON())
|
|
31
|
+
|
|
32
|
+
editor.commands.unsetMark()
|
|
33
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Command } from '@prosekit/pm/state'
|
|
2
|
+
|
|
3
|
+
import type { CommandCreator } from '../types/extension-command'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*
|
|
8
|
+
* Options for {@link unsetMark}.
|
|
9
|
+
*/
|
|
10
|
+
export interface UnsetMarkOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The start position of the document. By default it will be the start position of current selection.
|
|
13
|
+
*/
|
|
14
|
+
from?: number
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The end position of the document. By default it will be the end position of current selection.
|
|
18
|
+
*/
|
|
19
|
+
to?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns a command that removes all marks.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export function unsetMark(options?: UnsetMarkOptions): Command {
|
|
28
|
+
return (state, dispatch) => {
|
|
29
|
+
const from = options?.from ?? state.selection.from
|
|
30
|
+
const to = options?.to ?? state.selection.to
|
|
31
|
+
if (from > to) return false
|
|
32
|
+
|
|
33
|
+
dispatch?.(state.tr.removeMark(from, to))
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
unsetMark satisfies CommandCreator
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Attrs,
|
|
3
|
+
NodeType,
|
|
4
|
+
} from '@prosekit/pm/model'
|
|
5
|
+
import type { Command } from '@prosekit/pm/state'
|
|
6
|
+
import { findWrapping } from '@prosekit/pm/transform'
|
|
7
|
+
|
|
8
|
+
import { getNodeType } from '../utils/get-node-type'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export interface WrapOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The node type to wrap the selected textblock with.
|
|
16
|
+
*/
|
|
17
|
+
type: NodeType | string
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use `nodeSpec` instead.
|
|
21
|
+
*/
|
|
22
|
+
nodeType?: NodeType
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional attributes to apply to the node.
|
|
26
|
+
*/
|
|
27
|
+
attrs?: Attrs | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a command that wraps the selected textblock with the given node type.
|
|
32
|
+
*
|
|
33
|
+
* @param options
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export function wrap(options: WrapOptions): Command {
|
|
38
|
+
return (state, dispatch) => {
|
|
39
|
+
const { $from, $to } = state.selection
|
|
40
|
+
const range = $from.blockRange($to)
|
|
41
|
+
if (!range) return false
|
|
42
|
+
|
|
43
|
+
const nodeType = getNodeType(state.schema, options.nodeType || options.type)
|
|
44
|
+
const wrapping = findWrapping(range, nodeType, options.attrs)
|
|
45
|
+
if (!wrapping) return false
|
|
46
|
+
|
|
47
|
+
dispatch?.(state.tr.wrap(range, wrapping))
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
import { jsonFromNode } from '../utils/parse'
|
|
9
|
+
|
|
10
|
+
import type { NodeChild } from './action'
|
|
11
|
+
|
|
12
|
+
describe('NodeAction', () => {
|
|
13
|
+
const { editor, n } = setupTest()
|
|
14
|
+
|
|
15
|
+
it('can apply node', () => {
|
|
16
|
+
expect(n.heading('foo').toJSON()).toEqual({
|
|
17
|
+
type: 'heading',
|
|
18
|
+
content: [{ text: 'foo', type: 'text' }],
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('can apply node with attrs', () => {
|
|
23
|
+
expect(n.codeBlock({ language: 'javascript' }, 'foo').toJSON()).toEqual({
|
|
24
|
+
type: 'codeBlock',
|
|
25
|
+
attrs: { language: 'javascript' },
|
|
26
|
+
content: [{ text: 'foo', type: 'text' }],
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('can check node activity', () => {
|
|
31
|
+
editor.set(n.doc(n.heading('<a>foo<b>')))
|
|
32
|
+
expect(editor.nodes.heading.isActive()).toBe(true)
|
|
33
|
+
expect(editor.nodes.paragraph.isActive()).toBe(false)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('MarkAction', () => {
|
|
38
|
+
const { editor, m, n } = setupTest()
|
|
39
|
+
|
|
40
|
+
it('can apply mark', () => {
|
|
41
|
+
expect(n.p(m.bold('foo')).toJSON()).toEqual({
|
|
42
|
+
type: 'paragraph',
|
|
43
|
+
content: [{ marks: [{ type: 'bold' }], text: 'foo', type: 'text' }],
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('can apply mark with attrs', () => {
|
|
48
|
+
expect(
|
|
49
|
+
n.p(m.link({ href: 'https://example.com', target: '_blank', rel: 'noopener' }, 'foo')).toJSON(),
|
|
50
|
+
).toEqual({
|
|
51
|
+
type: 'paragraph',
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
marks: [{ type: 'link', attrs: { href: 'https://example.com', target: '_blank', rel: 'noopener' } }],
|
|
55
|
+
text: 'foo',
|
|
56
|
+
type: 'text',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('can apply multiple marks', () => {
|
|
63
|
+
const json = jsonFromNode(n.p(m.bold(m.italic('foo'))))
|
|
64
|
+
const marks = json.content?.[0].marks?.map((mark) => mark.type)
|
|
65
|
+
expect(marks?.sort()).toEqual(['bold', 'italic'])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('can apply the same mark multiple times', () => {
|
|
69
|
+
const href1 = 'https://example.com/1'
|
|
70
|
+
const href2 = 'https://example.com/2'
|
|
71
|
+
const link1 = (node: NodeChild) => m.link({ href: href1 }, node)
|
|
72
|
+
const link2 = (node: NodeChild) => m.link({ href: href2, target: '_blank', rel: 'noopener' }, node)
|
|
73
|
+
|
|
74
|
+
expect(n.p(link1('foo')).toJSON()).toEqual({
|
|
75
|
+
type: 'paragraph',
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
text: 'foo',
|
|
80
|
+
marks: [{ attrs: { href: href1, target: null, rel: null }, type: 'link' }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(n.p(link2(link1('foo'))).toJSON()).toEqual({
|
|
86
|
+
type: 'paragraph',
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: 'text',
|
|
90
|
+
text: 'foo',
|
|
91
|
+
marks: [{ attrs: { href: href2, target: '_blank', rel: 'noopener' }, type: 'link' }],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(n.p(link2(link2(link2('foo')))).toJSON()).toEqual({
|
|
97
|
+
type: 'paragraph',
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: 'foo',
|
|
102
|
+
marks: [{ attrs: { href: href2, target: '_blank', rel: 'noopener' }, type: 'link' }],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('can check mark activity', () => {
|
|
109
|
+
editor.set(n.doc(n.p('<a>foo<b>')))
|
|
110
|
+
expect(editor.marks.bold.isActive()).toBe(false)
|
|
111
|
+
expect(editor.marks.italic.isActive()).toBe(false)
|
|
112
|
+
|
|
113
|
+
editor.set(n.doc(n.p('<a>', m.bold('foo'), '<b>')))
|
|
114
|
+
expect(editor.marks.bold.isActive()).toBe(true)
|
|
115
|
+
expect(editor.marks.italic.isActive()).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('can check mark activity for cross-paragraph selection', () => {
|
|
119
|
+
editor.set(n.doc(n.p('<a>', m.bold('foo')), n.p(m.bold('foo'), '<b>')))
|
|
120
|
+
|
|
121
|
+
expect(editor.marks.bold.isActive()).toBe(true)
|
|
122
|
+
expect(editor.marks.italic.isActive()).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should not set isActive to true when only part of the text is marked', () => {
|
|
126
|
+
editor.set(n.doc(n.p('<a>', 'foo', m.bold('bar'), 'baz', '<b>')))
|
|
127
|
+
expect(editor.marks.bold.isActive()).toBe(false)
|
|
128
|
+
|
|
129
|
+
editor.set(n.doc(n.p('<a>', m.bold('foo'), 'bar', '<b>')))
|
|
130
|
+
expect(editor.marks.bold.isActive()).toBe(false)
|
|
131
|
+
|
|
132
|
+
editor.set(n.doc(n.p('<a>', 'foo', m.bold('bar'), '<b>')))
|
|
133
|
+
expect(editor.marks.bold.isActive()).toBe(false)
|
|
134
|
+
|
|
135
|
+
editor.set(n.doc(n.p('<a>', m.bold('foo', 'bar'), '<b>')))
|
|
136
|
+
expect(editor.marks.bold.isActive()).toBe(true)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should not set isActive to true when multiple empty paragraphs are selected', () => {
|
|
140
|
+
editor.set(n.doc(n.p('<a>'), n.p(''), n.p('<b>')))
|
|
141
|
+
expect(editor.marks.bold.isActive()).toBe(false)
|
|
142
|
+
})
|
|
143
|
+
})
|