@prosekit/core 0.8.2 → 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-DbMrpnmL.js → editor-CizSwUN8.js} +102 -192
  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 +782 -757
  10. package/dist/prosekit-core.d.ts.map +1 -0
  11. package/dist/prosekit-core.js +30 -45
  12. package/dist/prosekit-core.js.map +1 -0
  13. package/package.json +14 -11
  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-CjVyjJqw.d.ts +0 -739
@@ -0,0 +1,224 @@
1
+ import type {
2
+ DOMOutputSpec,
3
+ Schema,
4
+ TagParseRule,
5
+ } from '@prosekit/pm/model'
6
+ import formatHTML from 'diffable-html'
7
+ import {
8
+ describe,
9
+ expect,
10
+ it,
11
+ } from 'vitest'
12
+
13
+ import { union } from '../editor/union'
14
+ import {
15
+ defineDoc,
16
+ defineParagraph,
17
+ defineText,
18
+ setupTestFromExtension,
19
+ } from '../testing'
20
+
21
+ import { defineHistory } from './history'
22
+ import { defineBaseKeymap } from './keymap-base'
23
+ import {
24
+ defineNodeAttr,
25
+ defineNodeSpec,
26
+ } from './node-spec'
27
+
28
+ describe('defineNodeSpec', () => {
29
+ it('can merge node specs', () => {
30
+ const toDOM1 = (): DOMOutputSpec => ['p', { 'data-ext1': '' }]
31
+ const toDOM2 = (): DOMOutputSpec => ['p', { 'data-ext2': '' }]
32
+ const parseDOM1: TagParseRule = { tag: 'p[data-ext1]' }
33
+ const parseDOM2: TagParseRule = { tag: 'p[data-ext2]' }
34
+ const toDebugString2 = () => 'ext2'
35
+ const leafText1 = () => 'text'
36
+
37
+ const ext1 = defineNodeSpec({
38
+ name: 'paragraph',
39
+ content: 'text*',
40
+ leafText: leafText1,
41
+ parseDOM: [parseDOM1],
42
+ toDOM: toDOM1,
43
+ attrs: {
44
+ foo: { default: undefined },
45
+ bar: { default: 'bar' },
46
+ baz: { default: 'baz:1 ' },
47
+ },
48
+ })
49
+ const ext2 = defineNodeSpec({
50
+ name: 'paragraph',
51
+ group: 'block',
52
+ leafText: undefined,
53
+ parseDOM: [parseDOM2],
54
+ toDOM: toDOM2,
55
+ toDebugString: toDebugString2,
56
+ attrs: {
57
+ foo: { default: 'foo' },
58
+ bar: { default: undefined },
59
+ baz: { default: 'baz:2' },
60
+ },
61
+ })
62
+
63
+ const extension = union(ext1, ext2, defineDoc(), defineText())
64
+ const schema = extension.schema
65
+ expect(schema).toBeTruthy()
66
+ expect(schema?.spec.nodes.get('paragraph')).toEqual({
67
+ group: 'block',
68
+ content: 'text*',
69
+ leafText: leafText1,
70
+ parseDOM: [parseDOM1, parseDOM2],
71
+ toDOM: toDOM2,
72
+ toDebugString: toDebugString2,
73
+ attrs: {
74
+ foo: { default: 'foo' },
75
+ bar: { default: 'bar' },
76
+ baz: { default: 'baz:2' },
77
+ },
78
+ })
79
+ })
80
+
81
+ it('can reuse schema', () => {
82
+ const ext1 = union(defineDoc(), defineText(), defineParagraph())
83
+ const ext2 = union(defineBaseKeymap())
84
+ const ext3 = union(ext1, ext2)
85
+
86
+ const schema1 = ext1.schema
87
+ const schema2 = ext2.schema
88
+ const schema3 = ext3.schema
89
+
90
+ expect(schema1).toBeTruthy()
91
+ expect(schema2).toEqual(null)
92
+ expect(schema3).toBeTruthy()
93
+
94
+ expect(schema3 === schema1).toBe(true)
95
+
96
+ expect(formatSchema(schema3)).toMatchInlineSnapshot(`
97
+ {
98
+ "marks": "",
99
+ "nodes": "doc, paragraph, text",
100
+ "topNode": "doc",
101
+ }
102
+ `)
103
+
104
+ const ext4 = defineCodeBlockSpec()
105
+ const ext5 = union(ext3, ext4)
106
+ const schema5 = ext5.schema
107
+ expect(schema5).toBeTruthy()
108
+
109
+ expect(formatSchema(schema5)).toMatchInlineSnapshot(`
110
+ {
111
+ "marks": "",
112
+ "nodes": "codeBlock, doc, paragraph, text",
113
+ "topNode": "doc",
114
+ }
115
+ `)
116
+
117
+ expect(schema5 !== schema1).toBe(true)
118
+
119
+ const ext6 = union(ext5, defineHistory())
120
+ const schema6 = ext6.schema
121
+ expect(schema6).toBeTruthy()
122
+
123
+ expect(schema6 === schema5).toBe(true)
124
+ })
125
+ })
126
+
127
+ describe('defineNodeAttr', () => {
128
+ it('can add a new attribute', () => {
129
+ const textColorExt = defineNodeAttr({
130
+ type: 'paragraph',
131
+ attr: 'textColor',
132
+ default: 'black',
133
+ toDOM: (value) => ['style', `color: ${value}`],
134
+ parseDOM: (node: HTMLElement) => node.style.color,
135
+ })
136
+ const backgroundColorExt = defineNodeAttr({
137
+ type: 'paragraph',
138
+ attr: 'backgroundColor',
139
+ default: 'white',
140
+ toDOM: (value) => ['style', `background-color: ${value}`],
141
+ parseDOM: (node: HTMLElement) => node.style.backgroundColor,
142
+ })
143
+ const nodeIdExt = defineNodeAttr<'paragraph', 'nodeId', string | null>({
144
+ type: 'paragraph',
145
+ attr: 'nodeId',
146
+ default: null,
147
+ toDOM: (value) => (value ? ['data-node-id', value] : null),
148
+ parseDOM: (node: HTMLElement) => node.dataset.nodeId ?? null,
149
+ })
150
+ const extension = union(
151
+ defineDoc(),
152
+ defineText(),
153
+ defineParagraph(),
154
+ textColorExt,
155
+ backgroundColorExt,
156
+ nodeIdExt,
157
+ )
158
+ const { editor } = setupTestFromExtension(extension)
159
+
160
+ expect(Object.keys(editor.schema.nodes.paragraph.spec.attrs || {}))
161
+ .toMatchInlineSnapshot(`
162
+ [
163
+ "textColor",
164
+ "backgroundColor",
165
+ "nodeId",
166
+ ]
167
+ `)
168
+
169
+ editor.setContent(
170
+ '<p data-node-id="123" style="background-color:blue;color:red;">Hello</p>',
171
+ )
172
+
173
+ const json1 = editor.getDocJSON()
174
+ const html1 = editor.getDocHTML()
175
+ expect(json1).toEqual({
176
+ type: 'doc',
177
+ content: [
178
+ {
179
+ type: 'paragraph',
180
+ attrs: { nodeId: '123', textColor: 'red', backgroundColor: 'blue' },
181
+ content: [{ type: 'text', text: 'Hello' }],
182
+ },
183
+ ],
184
+ })
185
+ expect(formatHTML(html1)).toMatchInlineSnapshot(`
186
+ "
187
+ <div>
188
+ <p
189
+ data-node-id="123"
190
+ style="background-color: blue; color: red;"
191
+ >
192
+ Hello
193
+ </p>
194
+ </div>
195
+ "
196
+ `)
197
+ editor.setContent(html1)
198
+ const json2 = editor.getDocJSON()
199
+ expect(json2).toEqual(json1)
200
+ })
201
+ })
202
+
203
+ function defineCodeBlockSpec() {
204
+ return defineNodeSpec({
205
+ name: 'codeBlock',
206
+ content: 'text*',
207
+ group: 'block',
208
+ code: true,
209
+ })
210
+ }
211
+
212
+ function formatSchema(schema: Schema | null | undefined) {
213
+ if (!schema) {
214
+ return null
215
+ }
216
+
217
+ const nodes = Object.keys(schema.spec.nodes.toObject())
218
+ const marks = Object.keys(schema.spec.marks.toObject())
219
+ const topNode = schema.spec.topNode
220
+
221
+ nodes.sort()
222
+ marks.sort()
223
+ return { nodes: nodes.join(', '), marks: marks.join(', '), topNode }
224
+ }
@@ -0,0 +1,199 @@
1
+ import type {
2
+ AttributeSpec,
3
+ NodeSpec,
4
+ SchemaSpec,
5
+ } from '@prosekit/pm/model'
6
+ import clone from 'just-clone'
7
+ import OrderedMap from 'orderedmap'
8
+
9
+ import { defineFacet } from '../facets/facet'
10
+ import { defineFacetPayload } from '../facets/facet-extension'
11
+ import { schemaSpecFacet } from '../facets/schema-spec'
12
+ import type {
13
+ AnyAttrs,
14
+ AttrSpec,
15
+ } from '../types/attrs'
16
+ import type { Extension } from '../types/extension'
17
+ import { groupBy } from '../utils/array-grouping'
18
+ import { assert } from '../utils/assert'
19
+ import { mergeSpecs } from '../utils/merge-specs'
20
+ import {
21
+ wrapOutputSpecAttrs,
22
+ wrapTagParseRuleAttrs,
23
+ } from '../utils/output-spec'
24
+ import { isNotNullish } from '../utils/type-assertion'
25
+
26
+ /**
27
+ * @public
28
+ */
29
+ export interface NodeSpecOptions<
30
+ NodeName extends string = string,
31
+ Attrs extends AnyAttrs = AnyAttrs,
32
+ > extends NodeSpec {
33
+ /**
34
+ * The name of the node type.
35
+ */
36
+ name: NodeName
37
+
38
+ /**
39
+ * Whether this is the top-level node type. Only one node type can be the
40
+ * top-level node type in a schema.
41
+ */
42
+ topNode?: boolean
43
+
44
+ /**
45
+ * The attributes that nodes of this type get.
46
+ */
47
+ attrs?: {
48
+ [key in keyof Attrs]: AttrSpec<Attrs[key]>
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @public
54
+ */
55
+ export interface NodeAttrOptions<
56
+ NodeName extends string = string,
57
+ AttrName extends string = string,
58
+ AttrType = any,
59
+ > extends AttrSpec<AttrType> {
60
+ /**
61
+ * The name of the node type.
62
+ */
63
+ type: NodeName
64
+
65
+ /**
66
+ * The name of the attribute.
67
+ */
68
+ attr: AttrName
69
+
70
+ /**
71
+ * Whether the attribute should be kept when the node is split. Set it to
72
+ * `true` if you want to inherit the attribute from the previous node when
73
+ * splitting the node by pressing `Enter`.
74
+ *
75
+ * @default undefined
76
+ */
77
+ splittable?: boolean
78
+
79
+ /**
80
+ * Returns the attribute key and value to be set on the HTML element.
81
+ *
82
+ * If the returned `key` is `"style"`, the value is a string of CSS properties and will
83
+ * be prepended to the existing `style` attribute on the DOM node.
84
+ *
85
+ * @param value - The value of the attribute of current ProseMirror node.
86
+ */
87
+ toDOM?: (value: AttrType) => [key: string, value: string] | null | undefined
88
+
89
+ /**
90
+ * Parses the attribute value from the DOM.
91
+ */
92
+ parseDOM?: (node: HTMLElement) => AttrType
93
+ }
94
+
95
+ /**
96
+ * Defines a node type.
97
+ *
98
+ * @public
99
+ */
100
+ export function defineNodeSpec<
101
+ Node extends string,
102
+ Attrs extends AnyAttrs = AnyAttrs,
103
+ >(
104
+ options: NodeSpecOptions<Node, Attrs>,
105
+ ): Extension<{
106
+ Nodes: { [K in Node]: Attrs }
107
+ }> {
108
+ const payload: NodeSpecPayload = [options, undefined]
109
+ return defineFacetPayload(nodeSpecFacet, [payload]) as Extension<{
110
+ Nodes: any
111
+ }>
112
+ }
113
+
114
+ /**
115
+ * Defines an attribute for a node type.
116
+ *
117
+ * @public
118
+ */
119
+ export function defineNodeAttr<
120
+ NodeType extends string = string,
121
+ AttrName extends string = string,
122
+ AttrType = any,
123
+ >(
124
+ options: NodeAttrOptions<NodeType, AttrName, AttrType>,
125
+ ): Extension<{
126
+ Nodes: { [K in NodeType]: { [K in AttrName]: AttrType } }
127
+ }> {
128
+ const payload: NodeSpecPayload = [undefined, options]
129
+ return defineFacetPayload(nodeSpecFacet, [payload]) as Extension<{
130
+ Nodes: any
131
+ }>
132
+ }
133
+
134
+ type NodeSpecPayload = [
135
+ NodeSpecOptions | undefined,
136
+ NodeAttrOptions | undefined,
137
+ ]
138
+
139
+ const nodeSpecFacet = defineFacet<NodeSpecPayload, SchemaSpec>({
140
+ reducer: (payloads: NodeSpecPayload[]): SchemaSpec => {
141
+ let specs = OrderedMap.from<NodeSpec>({})
142
+ let topNodeName: string | undefined = undefined
143
+
144
+ const specPayloads = payloads.map((input) => input[0]).filter(isNotNullish)
145
+ const attrPayloads = payloads.map((input) => input[1]).filter(isNotNullish)
146
+
147
+ for (const { name, topNode, ...spec } of specPayloads) {
148
+ if (topNode) {
149
+ topNodeName = name
150
+ }
151
+
152
+ const prevSpec = specs.get(name)
153
+ if (prevSpec) {
154
+ specs = specs.update(name, mergeSpecs(prevSpec, spec))
155
+ } else {
156
+ // The latest spec has the highest priority, so we put it at the start
157
+ // of the map.
158
+ specs = specs.addToStart(name, spec)
159
+ }
160
+ }
161
+
162
+ const groupedAttrs = groupBy(attrPayloads, (payload) => payload.type)
163
+
164
+ for (const [type, attrs] of Object.entries(groupedAttrs)) {
165
+ if (!attrs) continue
166
+
167
+ const maybeSpec = specs.get(type)
168
+ assert(maybeSpec, `Node type ${type} must be defined`)
169
+
170
+ const spec = clone(maybeSpec)
171
+
172
+ if (!spec.attrs) {
173
+ spec.attrs = {}
174
+ }
175
+
176
+ for (const attr of attrs) {
177
+ spec.attrs[attr.attr] = {
178
+ default: attr.default as unknown,
179
+ validate: attr.validate,
180
+ splittable: attr.splittable,
181
+ } as AttributeSpec
182
+ }
183
+
184
+ if (spec.toDOM) {
185
+ spec.toDOM = wrapOutputSpecAttrs(spec.toDOM, attrs)
186
+ }
187
+
188
+ if (spec.parseDOM) {
189
+ spec.parseDOM = spec.parseDOM.map((rule) => wrapTagParseRuleAttrs(rule, attrs))
190
+ }
191
+
192
+ specs = specs.update(type, spec)
193
+ }
194
+
195
+ return { nodes: specs, topNode: topNodeName }
196
+ },
197
+ parent: schemaSpecFacet,
198
+ singleton: true,
199
+ })
@@ -0,0 +1,85 @@
1
+ import {
2
+ PluginKey,
3
+ ProseMirrorPlugin,
4
+ } from '@prosekit/pm/state'
5
+ import type { NodeViewConstructor } from '@prosekit/pm/view'
6
+
7
+ import { defineFacet } from '../facets/facet'
8
+ import { defineFacetPayload } from '../facets/facet-extension'
9
+ import type { Extension } from '../types/extension'
10
+ import { isNotNullish } from '../utils/type-assertion'
11
+
12
+ import {
13
+ pluginFacet,
14
+ type PluginPayload,
15
+ } from './plugin'
16
+
17
+ /**
18
+ * @internal
19
+ */
20
+ export type NodeViewFactoryOptions<T> = {
21
+ group: string
22
+ factory: (args: T) => NodeViewConstructor
23
+ }
24
+
25
+ /**
26
+ * @internal
27
+ */
28
+ export type NodeViewComponentOptions<T> = {
29
+ group: string
30
+ name: string
31
+ args: T
32
+ }
33
+
34
+ type NodeViewFactoryInput = [
35
+ NodeViewFactoryOptions<any> | null,
36
+ NodeViewComponentOptions<any> | null,
37
+ ]
38
+
39
+ /**
40
+ * @internal
41
+ */
42
+ export function defineNodeViewFactory<T>(
43
+ options: NodeViewFactoryOptions<T>,
44
+ ): Extension {
45
+ const input: NodeViewFactoryInput = [options, null]
46
+ return defineFacetPayload(nodeViewFactoryFacet, [input])
47
+ }
48
+
49
+ /**
50
+ * @internal
51
+ */
52
+ export function defineNodeViewComponent<T>(
53
+ options: NodeViewComponentOptions<T>,
54
+ ): Extension {
55
+ const input: NodeViewFactoryInput = [null, options]
56
+ return defineFacetPayload(nodeViewFactoryFacet, [input])
57
+ }
58
+
59
+ const isServer = typeof window === 'undefined'
60
+
61
+ const nodeViewFactoryFacet = defineFacet<NodeViewFactoryInput, PluginPayload>({
62
+ reducer: (inputs: NodeViewFactoryInput[]): PluginPayload => {
63
+ // Don't register node views on the server
64
+ if (isServer) return []
65
+
66
+ const nodeViews: { [nodeName: string]: NodeViewConstructor } = {}
67
+
68
+ const factories = inputs.map((x) => x[0]).filter(isNotNullish)
69
+ const options = inputs.map((x) => x[1]).filter(isNotNullish)
70
+
71
+ for (const { group, name, args } of options) {
72
+ const factory = factories.find((factory) => factory.group === group)
73
+ if (!factory) continue
74
+ nodeViews[name] = factory.factory(args)
75
+ }
76
+
77
+ return () => [
78
+ new ProseMirrorPlugin({
79
+ key: new PluginKey('prosekit-node-view-effect'),
80
+ props: { nodeViews },
81
+ }),
82
+ ]
83
+ },
84
+ parent: pluginFacet,
85
+ })
@@ -0,0 +1,43 @@
1
+ import {
2
+ PluginKey,
3
+ ProseMirrorPlugin,
4
+ } from '@prosekit/pm/state'
5
+ import type { NodeViewConstructor } from '@prosekit/pm/view'
6
+
7
+ import { defineFacet } from '../facets/facet'
8
+ import { defineFacetPayload } from '../facets/facet-extension'
9
+ import type { Extension } from '../types/extension'
10
+
11
+ import {
12
+ pluginFacet,
13
+ type PluginPayload,
14
+ } from './plugin'
15
+
16
+ export interface NodeViewOptions {
17
+ name: string
18
+ constructor: NodeViewConstructor
19
+ }
20
+
21
+ export function defineNodeView(options: NodeViewOptions): Extension {
22
+ return defineFacetPayload(nodeViewFacet, [options])
23
+ }
24
+
25
+ const nodeViewFacet = defineFacet<NodeViewOptions, PluginPayload>({
26
+ reducer: (inputs: NodeViewOptions[]): PluginPayload => {
27
+ const nodeViews: { [nodeName: string]: NodeViewConstructor } = {}
28
+
29
+ for (const input of inputs) {
30
+ if (!nodeViews[input.name]) {
31
+ nodeViews[input.name] = input.constructor
32
+ }
33
+ }
34
+
35
+ return () => [
36
+ new ProseMirrorPlugin({
37
+ key: new PluginKey('prosekit-node-view'),
38
+ props: { nodeViews },
39
+ }),
40
+ ]
41
+ },
42
+ parent: pluginFacet,
43
+ })
@@ -0,0 +1,61 @@
1
+ import type { Attrs } from '@prosekit/pm/model'
2
+
3
+ import { withPriority } from '../editor/with-priority'
4
+ import type { Extension } from '../types/extension'
5
+ import { Priority } from '../types/priority'
6
+
7
+ import { defineNodeSpec } from './node-spec'
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ type ParagraphSpecExtension = Extension<{
13
+ Nodes: {
14
+ paragraph: Attrs
15
+ }
16
+ }>
17
+
18
+ /**
19
+ * Defines a paragraph node spec.
20
+ */
21
+ function defineParagraphSpec(): ParagraphSpecExtension {
22
+ return defineNodeSpec({
23
+ name: 'paragraph',
24
+ content: 'inline*',
25
+ group: 'block',
26
+ parseDOM: [{ tag: 'p' }],
27
+ toDOM() {
28
+ return ['p', 0]
29
+ },
30
+ })
31
+ }
32
+
33
+ /**
34
+ * @internal
35
+ *
36
+ * @deprecated Use the following import instead:
37
+ *
38
+ * ```ts
39
+ * import type { ParagraphExtension } from 'prosekit/extensions/paragraph'
40
+ * ```
41
+ */
42
+ export type ParagraphExtension = ParagraphSpecExtension
43
+
44
+ /**
45
+ * @public
46
+ *
47
+ * Defines a paragraph node spec as the highest priority, because it should be the default block node for most cases.
48
+ *
49
+ * @deprecated Use the following import instead:
50
+ *
51
+ * ```ts
52
+ * import { defineParagraph } from 'prosekit/extensions/paragraph'
53
+ * ```
54
+ */
55
+ export function defineParagraph(): ParagraphExtension {
56
+ console.warn(
57
+ '[prosekit] The `defineParagraph` function from `prosekit/core` is deprecated. Use the following import instead: `import { defineParagraph } from "prosekit/extensions/paragraph"`.',
58
+ )
59
+
60
+ return withPriority(defineParagraphSpec(), Priority.highest)
61
+ }
@@ -0,0 +1,91 @@
1
+ import type { Schema } from '@prosekit/pm/model'
2
+ import {
3
+ Plugin,
4
+ type ProseMirrorPlugin,
5
+ } from '@prosekit/pm/state'
6
+
7
+ import { ProseKitError } from '../error'
8
+ import {
9
+ defineFacet,
10
+ type Facet,
11
+ } from '../facets/facet'
12
+ import { defineFacetPayload } from '../facets/facet-extension'
13
+ import {
14
+ stateFacet,
15
+ type StatePayload,
16
+ } from '../facets/state'
17
+ import type { PlainExtension } from '../types/extension'
18
+
19
+ /**
20
+ * Adds a ProseMirror plugin to the editor.
21
+ *
22
+ * @param plugin - The ProseMirror plugin to add, or an array of plugins, or a
23
+ * function that returns one or multiple plugins.
24
+ *
25
+ * @public
26
+ */
27
+ export function definePlugin(
28
+ plugin:
29
+ | Plugin
30
+ | Plugin[]
31
+ | ((context: { schema: Schema }) => Plugin | Plugin[]),
32
+ ): PlainExtension {
33
+ if (
34
+ plugin instanceof Plugin
35
+ || (Array.isArray(plugin) && plugin.every((p) => p instanceof Plugin))
36
+ ) {
37
+ return definePluginPayload(() => plugin)
38
+ }
39
+
40
+ if (typeof plugin === 'function') {
41
+ return definePluginPayload(plugin)
42
+ }
43
+
44
+ throw new TypeError('Invalid plugin')
45
+ }
46
+
47
+ function definePluginPayload(payload: PluginPayload): PlainExtension {
48
+ return defineFacetPayload(pluginFacet, [payload]) as PlainExtension
49
+ }
50
+
51
+ /**
52
+ * @internal
53
+ */
54
+ export type PluginPayload =
55
+ | Plugin
56
+ | Plugin[]
57
+ | ((context: { schema: Schema }) => Plugin | Plugin[])
58
+
59
+ /**
60
+ * @internal
61
+ */
62
+ export const pluginFacet: Facet<PluginPayload, StatePayload> = defineFacet({
63
+ reducer: (payloads): StatePayload => {
64
+ return ({ schema }) => {
65
+ const plugins: ProseMirrorPlugin[] = []
66
+
67
+ for (const payload of payloads) {
68
+ if (payload instanceof Plugin) {
69
+ plugins.push(payload)
70
+ } else if (
71
+ Array.isArray(payload)
72
+ && payload.every((p) => p instanceof Plugin)
73
+ ) {
74
+ plugins.push(...payload)
75
+ } else if (typeof payload === 'function') {
76
+ plugins.push(...[payload({ schema })].flat())
77
+ } else {
78
+ throw new ProseKitError('Invalid plugin')
79
+ }
80
+ }
81
+
82
+ // In ProseMirror, the plugins at the beginning have a higher priority.
83
+ // However, in ProseKit, the extensions at the end have a higher priority
84
+ // because we want to easily override the default behaviors by appending
85
+ // new extensions. Therefore, we need to reverse plugins here.
86
+ plugins.reverse()
87
+ return { plugins }
88
+ }
89
+ },
90
+ parent: stateFacet,
91
+ })