@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,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
+ })