@prosekit/core 0.8.7 → 0.9.0
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-M9OimMiI.d.ts → editor-4lgGc3CY.d.ts} +20 -60
- package/dist/editor-4lgGc3CY.d.ts.map +1 -0
- package/dist/{editor-B0L9BgMi.js → editor-DGNUXn-u.js} +98 -115
- package/dist/editor-DGNUXn-u.js.map +1 -0
- package/dist/prosekit-core-test.d.ts +1 -1
- package/dist/prosekit-core-test.js +1 -1
- package/dist/prosekit-core.d.ts +81 -180
- package/dist/prosekit-core.d.ts.map +1 -1
- package/dist/prosekit-core.js +316 -403
- package/dist/prosekit-core.js.map +1 -1
- package/package.json +8 -10
- package/src/commands/select-all.ts +3 -8
- package/src/commands/select-block.spec.ts +83 -0
- package/src/commands/select-block.ts +59 -0
- package/src/commands/wrap.ts +1 -6
- package/src/editor/action.ts +1 -11
- package/src/editor/editor.ts +20 -32
- package/src/extensions/command.ts +4 -0
- package/src/extensions/default-state.spec.ts +0 -4
- package/src/extensions/default-state.ts +4 -24
- package/src/extensions/events/dom-event.ts +1 -4
- package/src/extensions/events/editor-event.ts +1 -1
- package/src/extensions/history.ts +1 -1
- package/src/extensions/keymap-base.spec.ts +98 -0
- package/src/extensions/keymap-base.ts +37 -13
- package/src/extensions/keymap.spec.ts +9 -5
- package/src/extensions/keymap.ts +13 -56
- package/src/extensions/mark-spec.ts +32 -29
- package/src/extensions/node-spec.ts +28 -19
- package/src/extensions/plugin.ts +1 -2
- package/src/facets/command.ts +8 -2
- package/src/facets/state.spec.ts +6 -6
- package/src/facets/state.ts +1 -2
- package/src/index.ts +3 -23
- package/src/types/extension-command.ts +0 -7
- package/src/types/extension.ts +0 -16
- package/src/utils/array-grouping.spec.ts +1 -11
- package/src/utils/array-grouping.ts +1 -14
- package/src/utils/array.ts +0 -4
- package/src/utils/combine-event-handlers.ts +4 -6
- package/src/utils/editor-content.ts +3 -3
- package/src/utils/output-spec.ts +11 -0
- package/src/utils/parse.ts +9 -9
- package/dist/editor-B0L9BgMi.js.map +0 -1
- package/dist/editor-M9OimMiI.d.ts.map +0 -1
- package/src/extensions/doc.ts +0 -31
- package/src/extensions/paragraph.ts +0 -61
- package/src/extensions/text.ts +0 -34
- package/src/types/base-node-view-options.ts +0 -33
- package/src/types/object-entries.ts +0 -13
- package/src/utils/collect-children.ts +0 -21
- package/src/utils/collect-nodes.ts +0 -37
- package/src/utils/deep-equals.spec.ts +0 -26
- package/src/utils/deep-equals.ts +0 -29
- package/src/utils/get-id.spec.ts +0 -14
- package/src/utils/get-id.ts +0 -13
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosekit/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Core features for ProseKit",
|
|
7
7
|
"author": {
|
|
@@ -40,24 +40,22 @@
|
|
|
40
40
|
"src"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ocavue/utils": "^
|
|
43
|
+
"@ocavue/utils": "^1.2.0",
|
|
44
44
|
"clsx": "^2.1.1",
|
|
45
|
-
"just-clone": "^6.2.0",
|
|
46
|
-
"just-map-values": "^3.2.0",
|
|
47
45
|
"orderedmap": "^2.1.1",
|
|
48
46
|
"prosemirror-splittable": "^0.1.1",
|
|
49
|
-
"type-fest": "^5.
|
|
50
|
-
"@prosekit/pm": "^0.1.
|
|
47
|
+
"type-fest": "^5.3.1",
|
|
48
|
+
"@prosekit/pm": "^0.1.15"
|
|
51
49
|
},
|
|
52
50
|
"devDependencies": {
|
|
53
51
|
"@types/diffable-html": "^5.0.2",
|
|
54
52
|
"diffable-html": "^6.0.1",
|
|
55
|
-
"tsdown": "^0.
|
|
53
|
+
"tsdown": "^0.17.0",
|
|
56
54
|
"typescript": "~5.9.3",
|
|
57
|
-
"vitest": "^4.0.
|
|
55
|
+
"vitest": "^4.0.15",
|
|
58
56
|
"vitest-browser-commands": "^0.2.0",
|
|
59
|
-
"@prosekit/config-
|
|
60
|
-
"@prosekit/config-
|
|
57
|
+
"@prosekit/config-vitest": "0.0.0",
|
|
58
|
+
"@prosekit/config-tsdown": "0.0.0"
|
|
61
59
|
},
|
|
62
60
|
"publishConfig": {
|
|
63
61
|
"dev": {}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type Command,
|
|
4
|
-
} from '@prosekit/pm/state'
|
|
1
|
+
import { selectAll as selectAllCommand } from '@prosekit/pm/commands'
|
|
2
|
+
import type { Command } from '@prosekit/pm/state'
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Returns a command that selects the whole document.
|
|
@@ -9,8 +7,5 @@ import {
|
|
|
9
7
|
* @public
|
|
10
8
|
*/
|
|
11
9
|
export function selectAll(): Command {
|
|
12
|
-
return
|
|
13
|
-
dispatch?.(state.tr.setSelection(new AllSelection(state.doc)))
|
|
14
|
-
return true
|
|
15
|
-
}
|
|
10
|
+
return selectAllCommand
|
|
16
11
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
|
|
9
|
+
import { selectBlock } from './select-block'
|
|
10
|
+
|
|
11
|
+
describe('selectBlock', () => {
|
|
12
|
+
it('should expand the text selection to cover the start of the paragraph', () => {
|
|
13
|
+
const { editor, n, getSelectionString } = setup()
|
|
14
|
+
editor.set(n.doc(n.paragraph('Hello <a>world<b>')))
|
|
15
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("world")>"`)
|
|
16
|
+
editor.exec(selectBlock())
|
|
17
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("Hello world")>"`)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should expand the text selection to cover the end of the paragraph', () => {
|
|
21
|
+
const { editor, n, getSelectionString } = setup()
|
|
22
|
+
editor.set(n.doc(n.paragraph('<a>Hello<b> world')))
|
|
23
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("Hello")>"`)
|
|
24
|
+
editor.exec(selectBlock())
|
|
25
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("Hello world")>"`)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should expand the text selection to include other marks', () => {
|
|
29
|
+
const { editor, n, m, getSelectionString } = setup()
|
|
30
|
+
editor.set(n.doc(n.paragraph(
|
|
31
|
+
m.bold('Bold'),
|
|
32
|
+
' ',
|
|
33
|
+
m.italic('Italic<a>'),
|
|
34
|
+
' ',
|
|
35
|
+
m.bold('Bold'),
|
|
36
|
+
)))
|
|
37
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<>"`)
|
|
38
|
+
editor.exec(selectBlock())
|
|
39
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph(bold("Bold"), " ", italic("Italic"), " ", bold("Bold"))>"`)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should expand the text selection to include multiple blocks', () => {
|
|
43
|
+
const { editor, n, getSelectionString } = setup()
|
|
44
|
+
editor.set(n.doc(
|
|
45
|
+
n.paragraph('Hello<a>'),
|
|
46
|
+
n.paragraph('World'),
|
|
47
|
+
n.paragraph('<b>!'),
|
|
48
|
+
))
|
|
49
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph, paragraph("World"), paragraph>"`)
|
|
50
|
+
editor.exec(selectBlock())
|
|
51
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("Hello"), paragraph("World"), paragraph("!")>"`)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('is able to expand the text selection pointing to different depths', () => {
|
|
55
|
+
const { editor, n, getSelectionString } = setup()
|
|
56
|
+
editor.set(n.doc(
|
|
57
|
+
n.paragraph('Hello<a>'),
|
|
58
|
+
n.blockquote([
|
|
59
|
+
n.paragraph('Item 1'),
|
|
60
|
+
n.blockquote([
|
|
61
|
+
n.paragraph('Sub<b> item 1.1'),
|
|
62
|
+
]),
|
|
63
|
+
]),
|
|
64
|
+
n.blockquote([
|
|
65
|
+
n.paragraph('Item 2'),
|
|
66
|
+
]),
|
|
67
|
+
))
|
|
68
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph, blockquote(paragraph("Item 1"), blockquote(paragraph("Sub")))>"`)
|
|
69
|
+
editor.exec(selectBlock())
|
|
70
|
+
expect(getSelectionString()).toMatchInlineSnapshot(`"<paragraph("Hello"), blockquote(paragraph("Item 1"), blockquote(paragraph("Sub item 1.1")))>"`)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function setup() {
|
|
75
|
+
const { editor, n, m } = setupTest()
|
|
76
|
+
|
|
77
|
+
const getSelectionString = () => {
|
|
78
|
+
const fragment = editor.state.selection.content().content
|
|
79
|
+
return fragment.toString()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { editor, n, m, getSelectionString }
|
|
83
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TextSelection,
|
|
3
|
+
type Command,
|
|
4
|
+
} from '@prosekit/pm/state'
|
|
5
|
+
|
|
6
|
+
import { isTextSelection } from '../utils/type-assertion'
|
|
7
|
+
|
|
8
|
+
// Based on https://github.com/ProseMirror/prosemirror-commands/blob/1.7.1/src/commands.ts#L507-L521
|
|
9
|
+
function getTextblockEndpoint(selection: TextSelection, side: number): number | undefined {
|
|
10
|
+
const $pos = side < 0 ? selection.$from : selection.$to
|
|
11
|
+
let depth = $pos.depth
|
|
12
|
+
while ($pos.node(depth).isInline) {
|
|
13
|
+
if (!depth) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
depth--
|
|
17
|
+
}
|
|
18
|
+
if (!$pos.node(depth).isTextblock) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
return side < 0 ? $pos.start(depth) : $pos.end(depth)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const selectBlockCommand: Command = (state, dispatch) => {
|
|
28
|
+
const { selection } = state
|
|
29
|
+
if (!isTextSelection(selection)) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const expectedFrom = getTextblockEndpoint(selection, -1)
|
|
34
|
+
const expectedTo = getTextblockEndpoint(selection, 1)
|
|
35
|
+
if (expectedFrom == null || expectedTo == null) {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (selection.from <= expectedFrom && selection.to >= expectedTo) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (dispatch) {
|
|
44
|
+
const newSelection = TextSelection.create(state.doc, expectedFrom, expectedTo)
|
|
45
|
+
dispatch(state.tr.setSelection(newSelection))
|
|
46
|
+
}
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns a command to expand the text selection to cover the current block
|
|
52
|
+
* node. If the text selection spans multiple blocks, it will select all
|
|
53
|
+
* blocks in the selection.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export function selectBlock(): Command {
|
|
58
|
+
return selectBlockCommand
|
|
59
|
+
}
|
package/src/commands/wrap.ts
CHANGED
|
@@ -16,11 +16,6 @@ export interface WrapOptions {
|
|
|
16
16
|
*/
|
|
17
17
|
type: NodeType | string
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* @deprecated Use `nodeSpec` instead.
|
|
21
|
-
*/
|
|
22
|
-
nodeType?: NodeType
|
|
23
|
-
|
|
24
19
|
/**
|
|
25
20
|
* Optional attributes to apply to the node.
|
|
26
21
|
*/
|
|
@@ -40,7 +35,7 @@ export function wrap(options: WrapOptions): Command {
|
|
|
40
35
|
const range = $from.blockRange($to)
|
|
41
36
|
if (!range) return false
|
|
42
37
|
|
|
43
|
-
const nodeType = getNodeType(state.schema, options.
|
|
38
|
+
const nodeType = getNodeType(state.schema, options.type)
|
|
44
39
|
const wrapping = findWrapping(range, nodeType, options.attrs)
|
|
45
40
|
if (!wrapping) return false
|
|
46
41
|
|
package/src/editor/action.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapValues } from '@ocavue/utils'
|
|
1
2
|
import type {
|
|
2
3
|
Attrs,
|
|
3
4
|
Mark,
|
|
@@ -7,7 +8,6 @@ import type {
|
|
|
7
8
|
Schema,
|
|
8
9
|
} from '@prosekit/pm/model'
|
|
9
10
|
import type { EditorState } from '@prosekit/pm/state'
|
|
10
|
-
import mapValues from 'just-map-values'
|
|
11
11
|
|
|
12
12
|
import { ProseKitError } from '../error'
|
|
13
13
|
import type { AnyAttrs } from '../types/attrs'
|
|
@@ -79,16 +79,6 @@ export interface MarkAction<Attrs extends AnyAttrs = AnyAttrs> {
|
|
|
79
79
|
isActive: (attrs?: Attrs) => boolean
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
83
|
-
* @deprecated Use type {@link NodeAction} instead.
|
|
84
|
-
*/
|
|
85
|
-
export type NodeBuilder = NodeAction
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @deprecated Use type {@link MarkAction} instead.
|
|
89
|
-
*/
|
|
90
|
-
export type MarkBuilder = MarkAction
|
|
91
|
-
|
|
92
82
|
/**
|
|
93
83
|
* @internal
|
|
94
84
|
*/
|
package/src/editor/editor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDeepEqual } from '@ocavue/utils'
|
|
1
2
|
import type {
|
|
2
3
|
ProseMirrorNode,
|
|
3
4
|
Schema,
|
|
@@ -40,7 +41,6 @@ import type {
|
|
|
40
41
|
SelectionJSON,
|
|
41
42
|
} from '../types/model'
|
|
42
43
|
import { assert } from '../utils/assert'
|
|
43
|
-
import { deepEquals } from '../utils/deep-equals'
|
|
44
44
|
import {
|
|
45
45
|
getEditorContentDoc,
|
|
46
46
|
getEditorSelection,
|
|
@@ -70,25 +70,9 @@ export interface EditorOptions<E extends Extension> {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* The starting document to use when creating the editor. It can be a
|
|
73
|
-
* ProseMirror node JSON object,
|
|
73
|
+
* ProseMirror node JSON object, an HTML string, or a DOM element instance.
|
|
74
74
|
*/
|
|
75
|
-
defaultContent?: NodeJSON | string |
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* A JSON object representing the starting document to use when creating the
|
|
79
|
-
* editor.
|
|
80
|
-
*
|
|
81
|
-
* @deprecated Use `defaultContent` instead.
|
|
82
|
-
*/
|
|
83
|
-
defaultDoc?: NodeJSON
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* A HTML element or a HTML string representing the starting document to use
|
|
87
|
-
* when creating the editor.
|
|
88
|
-
*
|
|
89
|
-
* @deprecated Use `defaultContent` instead.
|
|
90
|
-
*/
|
|
91
|
-
defaultHTML?: string | HTMLElement
|
|
75
|
+
defaultContent?: NodeJSON | string | Element
|
|
92
76
|
|
|
93
77
|
/**
|
|
94
78
|
* A JSON object representing the starting selection to use when creating the
|
|
@@ -108,7 +92,7 @@ export interface getDocHTMLOptions extends DOMDocumentOptions {}
|
|
|
108
92
|
export function setupEditorExtension<E extends Extension>(
|
|
109
93
|
options: EditorOptions<E>,
|
|
110
94
|
): E {
|
|
111
|
-
if (options.defaultContent
|
|
95
|
+
if (options.defaultContent) {
|
|
112
96
|
return union(
|
|
113
97
|
options.extension,
|
|
114
98
|
defineDefaultState(options),
|
|
@@ -176,7 +160,7 @@ export class EditorInstance {
|
|
|
176
160
|
return this.getState().doc
|
|
177
161
|
}
|
|
178
162
|
|
|
179
|
-
private getProp<PropName extends keyof EditorProps>(propName: PropName): EditorProps[PropName]
|
|
163
|
+
private getProp<PropName extends keyof EditorProps>(propName: PropName): Partial<EditorProps>[PropName] {
|
|
180
164
|
return this.view?.someProp(propName) ?? this.directEditorProps[propName]
|
|
181
165
|
}
|
|
182
166
|
|
|
@@ -197,7 +181,7 @@ export class EditorInstance {
|
|
|
197
181
|
}
|
|
198
182
|
|
|
199
183
|
public setContent(
|
|
200
|
-
content: NodeJSON | string |
|
|
184
|
+
content: NodeJSON | string | Element | ProseMirrorNode,
|
|
201
185
|
selection?: SelectionJSON | Selection | 'start' | 'end',
|
|
202
186
|
): void {
|
|
203
187
|
const doc = getEditorContentDoc(this.schema, content)
|
|
@@ -226,7 +210,7 @@ export class EditorInstance {
|
|
|
226
210
|
}
|
|
227
211
|
|
|
228
212
|
/**
|
|
229
|
-
* Return
|
|
213
|
+
* Return an HTML string representing the editor's current document.
|
|
230
214
|
*/
|
|
231
215
|
public getDocHTML = (options?: getDocHTMLOptions): string => {
|
|
232
216
|
const serializer = this.getProp('clipboardSerializer')
|
|
@@ -264,14 +248,14 @@ export class EditorInstance {
|
|
|
264
248
|
const newPayload = this.tree.getRootOutput()
|
|
265
249
|
const newPlugins = [...(newPayload?.state?.plugins ?? [])]
|
|
266
250
|
|
|
267
|
-
if (!
|
|
251
|
+
if (!isDeepEqual(oldPlugins, newPlugins)) {
|
|
268
252
|
const state = view.state.reconfigure({ plugins: newPlugins })
|
|
269
253
|
view.updateState(state)
|
|
270
254
|
}
|
|
271
255
|
|
|
272
256
|
if (
|
|
273
257
|
newPayload?.commands
|
|
274
|
-
&& !
|
|
258
|
+
&& !isDeepEqual(oldPayload?.commands, newPayload?.commands)
|
|
275
259
|
) {
|
|
276
260
|
const commands = newPayload.commands
|
|
277
261
|
const names = Object.keys(commands)
|
|
@@ -306,6 +290,9 @@ export class EditorInstance {
|
|
|
306
290
|
|
|
307
291
|
public mount(place: HTMLElement): void {
|
|
308
292
|
if (this.view) {
|
|
293
|
+
// If the editor is already mounted to the same DOM element, do nothing
|
|
294
|
+
if (this.view.dom === place) return
|
|
295
|
+
// If the editor is already mounted to a different element, throw an error
|
|
309
296
|
throw new ProseKitError('Editor is already mounted')
|
|
310
297
|
}
|
|
311
298
|
this.view = new EditorView({ mount: place }, this.directEditorProps)
|
|
@@ -374,7 +361,6 @@ export class EditorInstance {
|
|
|
374
361
|
return this.canExec(command)
|
|
375
362
|
}
|
|
376
363
|
|
|
377
|
-
action.canApply = canExec
|
|
378
364
|
action.canExec = canExec
|
|
379
365
|
|
|
380
366
|
this.commands[name] = action as CommandAction
|
|
@@ -437,12 +423,14 @@ export class Editor<E extends Extension = any> {
|
|
|
437
423
|
}
|
|
438
424
|
|
|
439
425
|
/**
|
|
440
|
-
* Mount the editor to the given HTML element.
|
|
441
|
-
*
|
|
426
|
+
* Mount the editor to the given HTML element. Pass `null` or `undefined` to
|
|
427
|
+
* unmount the editor. When an element is passed, this method returns a
|
|
428
|
+
* function to unmount the editor.
|
|
442
429
|
*/
|
|
443
|
-
mount = (place: HTMLElement | null | undefined): void => {
|
|
430
|
+
mount = (place: HTMLElement | null | undefined): void | VoidFunction => {
|
|
444
431
|
if (place) {
|
|
445
432
|
this.instance.mount(place)
|
|
433
|
+
return this.unmount
|
|
446
434
|
} else {
|
|
447
435
|
this.instance.unmount()
|
|
448
436
|
}
|
|
@@ -496,7 +484,7 @@ export class Editor<E extends Extension = any> {
|
|
|
496
484
|
* - A ProseMirror node instance
|
|
497
485
|
* - A ProseMirror node JSON object
|
|
498
486
|
* - An HTML string
|
|
499
|
-
* -
|
|
487
|
+
* - A DOM element instance
|
|
500
488
|
* @param selection - Optional. Specifies the new selection. It can be one of the following:
|
|
501
489
|
* - A ProseMirror selection instance
|
|
502
490
|
* - A ProseMirror selection JSON object
|
|
@@ -504,7 +492,7 @@ export class Editor<E extends Extension = any> {
|
|
|
504
492
|
* - The string "end" (to set selection at the end)
|
|
505
493
|
*/
|
|
506
494
|
setContent = (
|
|
507
|
-
content: ProseMirrorNode | NodeJSON | string |
|
|
495
|
+
content: ProseMirrorNode | NodeJSON | string | Element,
|
|
508
496
|
selection?: SelectionJSON | Selection | 'start' | 'end',
|
|
509
497
|
): void => {
|
|
510
498
|
return this.instance.setContent(content, selection)
|
|
@@ -518,7 +506,7 @@ export class Editor<E extends Extension = any> {
|
|
|
518
506
|
}
|
|
519
507
|
|
|
520
508
|
/**
|
|
521
|
-
* Return
|
|
509
|
+
* Return an HTML string representing the editor's current document.
|
|
522
510
|
*/
|
|
523
511
|
public getDocHTML = (options?: getDocHTMLOptions): string => {
|
|
524
512
|
return this.instance.getDocHTML(options)
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type RemoveNodeOptions,
|
|
24
24
|
} from '../commands/remove-node'
|
|
25
25
|
import { selectAll } from '../commands/select-all'
|
|
26
|
+
import { selectBlock } from '../commands/select-block'
|
|
26
27
|
import {
|
|
27
28
|
setBlockType,
|
|
28
29
|
type SetBlockTypeOptions,
|
|
@@ -78,6 +79,7 @@ export type BaseCommandsExtension = Extension<{
|
|
|
78
79
|
setNodeAttrs: [options: SetNodeAttrsOptions]
|
|
79
80
|
insertDefaultBlock: [options?: InsertDefaultBlockOptions]
|
|
80
81
|
selectAll: []
|
|
82
|
+
selectBlock: []
|
|
81
83
|
addMark: [options: AddMarkOptions]
|
|
82
84
|
removeMark: [options: RemoveMarkOptions]
|
|
83
85
|
unsetBlockType: [options?: UnsetBlockTypeOptions]
|
|
@@ -110,6 +112,8 @@ export function defineBaseCommands(): BaseCommandsExtension {
|
|
|
110
112
|
|
|
111
113
|
selectAll,
|
|
112
114
|
|
|
115
|
+
selectBlock,
|
|
116
|
+
|
|
113
117
|
addMark,
|
|
114
118
|
|
|
115
119
|
removeMark,
|
|
@@ -27,10 +27,6 @@ describe('defineDefaultState', () => {
|
|
|
27
27
|
return editor.state.doc.toString()
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
expect(run({ defaultDoc: docJSON })).toContain('docJSON')
|
|
31
|
-
expect(run({ defaultHTML: docHTMLString })).toContain('docHTMLString')
|
|
32
|
-
expect(run({ defaultHTML: docHTMLElement })).toContain('docHTMLElement')
|
|
33
|
-
|
|
34
30
|
expect(run({ defaultContent: docJSON })).toContain('docJSON')
|
|
35
31
|
expect(run({ defaultContent: docHTMLString })).toContain('docHTMLString')
|
|
36
32
|
expect(run({ defaultContent: docHTMLElement })).toContain('docHTMLElement')
|
|
@@ -18,25 +18,9 @@ import { getEditorContentJSON } from '../utils/editor-content'
|
|
|
18
18
|
export interface DefaultStateOptions {
|
|
19
19
|
/**
|
|
20
20
|
* The starting document to use when creating the editor. It can be a
|
|
21
|
-
* ProseMirror node JSON object,
|
|
21
|
+
* ProseMirror node JSON object, an HTML string, or a DOM element instance.
|
|
22
22
|
*/
|
|
23
|
-
defaultContent?: NodeJSON | string |
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* A JSON object representing the starting document to use when creating the
|
|
27
|
-
* editor.
|
|
28
|
-
*
|
|
29
|
-
* @deprecated Use `defaultContent` instead.
|
|
30
|
-
*/
|
|
31
|
-
defaultDoc?: NodeJSON
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A HTML element or a HTML string representing the starting document to use
|
|
35
|
-
* when creating the editor.
|
|
36
|
-
*
|
|
37
|
-
* @deprecated Use `defaultContent` instead.
|
|
38
|
-
*/
|
|
39
|
-
defaultHTML?: string | HTMLElement
|
|
23
|
+
defaultContent?: NodeJSON | string | Element
|
|
40
24
|
|
|
41
25
|
/**
|
|
42
26
|
* A JSON object representing the starting selection to use when creating the
|
|
@@ -55,16 +39,12 @@ export interface DefaultStateOptions {
|
|
|
55
39
|
export function defineDefaultState({
|
|
56
40
|
defaultSelection,
|
|
57
41
|
defaultContent,
|
|
58
|
-
defaultDoc,
|
|
59
|
-
defaultHTML,
|
|
60
42
|
}: DefaultStateOptions): PlainExtension {
|
|
61
|
-
const defaultDocContent = defaultContent || defaultDoc || defaultHTML
|
|
62
|
-
|
|
63
43
|
return defineFacetPayload(stateFacet, [
|
|
64
44
|
({ schema }) => {
|
|
65
45
|
const config: EditorStateConfig = {}
|
|
66
|
-
if (
|
|
67
|
-
const json = getEditorContentJSON(schema,
|
|
46
|
+
if (defaultContent) {
|
|
47
|
+
const json = getEditorContentJSON(schema, defaultContent)
|
|
68
48
|
config.doc = schema.nodeFromJSON(json)
|
|
69
49
|
if (defaultSelection) {
|
|
70
50
|
config.selection = Selection.fromJSON(config.doc, defaultSelection)
|
|
@@ -85,10 +85,7 @@ const domEventFacet: Facet<DOMEventPayload, PluginPayload> = defineFacet(
|
|
|
85
85
|
hasNewEvent = true
|
|
86
86
|
const [setHandlers, combinedHandler] = combineEventHandlers<DOMEventHandler>()
|
|
87
87
|
setHandlersMap[event] = setHandlers
|
|
88
|
-
|
|
89
|
-
return combinedHandler(view, eventObject)
|
|
90
|
-
}
|
|
91
|
-
combinedHandlerMap[event] = e
|
|
88
|
+
combinedHandlerMap[event] = combinedHandler
|
|
92
89
|
}
|
|
93
90
|
}
|
|
94
91
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ObjectEntries } from '@ocavue/utils'
|
|
1
2
|
import type {
|
|
2
3
|
Node,
|
|
3
4
|
Slice,
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
} from '../../facets/facet'
|
|
15
16
|
import { defineFacetPayload } from '../../facets/facet-extension'
|
|
16
17
|
import type { PlainExtension } from '../../types/extension'
|
|
17
|
-
import type { ObjectEntries } from '../../types/object-entries'
|
|
18
18
|
import { groupEntries } from '../../utils/array-grouping'
|
|
19
19
|
import { combineEventHandlers } from '../../utils/combine-event-handlers'
|
|
20
20
|
import {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
import { keyboard } from 'vitest-browser-commands/playwright'
|
|
7
|
+
|
|
8
|
+
import { union } from '../editor/union'
|
|
9
|
+
import type { TestEditor } from '../test'
|
|
10
|
+
import {
|
|
11
|
+
defineDoc,
|
|
12
|
+
defineParagraph,
|
|
13
|
+
defineText,
|
|
14
|
+
setupTestFromExtension,
|
|
15
|
+
} from '../testing'
|
|
16
|
+
import type { SelectionJSON } from '../types/model'
|
|
17
|
+
|
|
18
|
+
import { defineBaseKeymap } from './keymap-base'
|
|
19
|
+
|
|
20
|
+
describe('Mod-a', () => {
|
|
21
|
+
it('can select the block for the first Mod-a press', async () => {
|
|
22
|
+
const { editor, n } = setupTestFromExtension(union(
|
|
23
|
+
defineDoc(),
|
|
24
|
+
defineText(),
|
|
25
|
+
defineParagraph(),
|
|
26
|
+
defineBaseKeymap(),
|
|
27
|
+
))
|
|
28
|
+
|
|
29
|
+
editor.set(n.doc(n.paragraph('Fo<a>o<b>'), n.paragraph('Bar')))
|
|
30
|
+
await keyboard.press('ControlOrMeta+a')
|
|
31
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
|
|
32
|
+
await keyboard.press('ControlOrMeta+a')
|
|
33
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('can select the entire document if the current textblock is already selected', async () => {
|
|
37
|
+
const { editor, n } = setupTestFromExtension(union(
|
|
38
|
+
defineDoc(),
|
|
39
|
+
defineText(),
|
|
40
|
+
defineParagraph(),
|
|
41
|
+
defineBaseKeymap(),
|
|
42
|
+
))
|
|
43
|
+
|
|
44
|
+
editor.set(n.doc(n.paragraph('<a>Foo<b>'), n.paragraph('Bar')))
|
|
45
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
|
|
46
|
+
await keyboard.press('ControlOrMeta+a')
|
|
47
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('can select the entire document if multiple textblocks are already selected', async () => {
|
|
51
|
+
const { editor, n } = setupTestFromExtension(union(
|
|
52
|
+
defineDoc(),
|
|
53
|
+
defineText(),
|
|
54
|
+
defineParagraph(),
|
|
55
|
+
defineBaseKeymap(),
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
editor.set(n.doc(n.paragraph('<a>Foo'), n.paragraph('Bar<b>'), n.paragraph('Baz')))
|
|
59
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo"), paragraph("Bar")>"`)
|
|
60
|
+
await keyboard.press('ControlOrMeta+a')
|
|
61
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar"), paragraph("Baz")>"`)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('can select the entire document if the current textblock is empty', async () => {
|
|
65
|
+
const { editor, n } = setupTestFromExtension(union(
|
|
66
|
+
defineDoc(),
|
|
67
|
+
defineText(),
|
|
68
|
+
defineParagraph(),
|
|
69
|
+
defineBaseKeymap(),
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
editor.set(n.doc(n.paragraph('Foo'), n.paragraph('<a>'), n.paragraph('Bar')))
|
|
73
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <>"`)
|
|
74
|
+
await keyboard.press('ControlOrMeta+a')
|
|
75
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph, paragraph("Bar")>"`)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('can select the entire document directly if `preferBlockSelection` is false', async () => {
|
|
79
|
+
const { editor, n } = setupTestFromExtension(union(
|
|
80
|
+
defineDoc(),
|
|
81
|
+
defineText(),
|
|
82
|
+
defineParagraph(),
|
|
83
|
+
defineBaseKeymap({ preferBlockSelection: false }),
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
editor.set(n.doc(n.paragraph('<a>Foo<b>'), n.paragraph('Bar')))
|
|
87
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"text: <paragraph("Foo")>"`)
|
|
88
|
+
await keyboard.press('ControlOrMeta+a')
|
|
89
|
+
expect(inspectSelection(editor)).toMatchInlineSnapshot(`"all: <paragraph("Foo"), paragraph("Bar")>"`)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
function inspectSelection(editor: TestEditor) {
|
|
94
|
+
const selection = editor.state.selection
|
|
95
|
+
const text = selection.content().content.toString()
|
|
96
|
+
const json = selection.toJSON() as SelectionJSON
|
|
97
|
+
return `${json.type}: ${text}`
|
|
98
|
+
}
|