@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.
- package/dist/editor-CfkZ4TNU.d.ts +748 -0
- package/dist/editor-CfkZ4TNU.d.ts.map +1 -0
- package/dist/{editor-DbMrpnmL.js → editor-CizSwUN8.js} +102 -192
- 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 +782 -757
- package/dist/prosekit-core.d.ts.map +1 -0
- package/dist/prosekit-core.js +30 -45
- package/dist/prosekit-core.js.map +1 -0
- package/package.json +14 -11
- 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-CjVyjJqw.d.ts +0 -739
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Extension } from '../types/extension'
|
|
2
|
+
import type { Priority } from '../types/priority'
|
|
3
|
+
|
|
4
|
+
import { union } from './union'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return an new extension with the given priority.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Priority, withPriority } from 'prosekit/core'
|
|
12
|
+
*
|
|
13
|
+
* const extension = withPriority(defineMyExtension(), Priority.high)
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export function withPriority<T extends Extension>(
|
|
19
|
+
extension: T,
|
|
20
|
+
priority: Priority,
|
|
21
|
+
): T {
|
|
22
|
+
const result = union(extension)
|
|
23
|
+
result.priority = priority
|
|
24
|
+
return result as T
|
|
25
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all ProseKit errors.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export class ProseKitError extends Error {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export class EditorNotFoundError extends ProseKitError {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(
|
|
14
|
+
'Unable to find editor. Pass it as an argument or call this function inside a ProseKit component.',
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export class DOMDocumentNotFoundError extends ProseKitError {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(
|
|
25
|
+
'Unable to find browser Document. When not in the browser environment, you need to pass a DOM Document.',
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DOMSerializer,
|
|
3
|
+
type DOMOutputSpec,
|
|
4
|
+
type Mark,
|
|
5
|
+
type ProseMirrorNode,
|
|
6
|
+
type Schema,
|
|
7
|
+
} from '@prosekit/pm/model'
|
|
8
|
+
import {
|
|
9
|
+
PluginKey,
|
|
10
|
+
ProseMirrorPlugin,
|
|
11
|
+
} from '@prosekit/pm/state'
|
|
12
|
+
|
|
13
|
+
import { defineFacet } from '../facets/facet'
|
|
14
|
+
import { defineFacetPayload } from '../facets/facet-extension'
|
|
15
|
+
import type { AnyFunction } from '../types/any-function'
|
|
16
|
+
import type { PlainExtension } from '../types/extension'
|
|
17
|
+
import { isNotNullish } from '../utils/type-assertion'
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
pluginFacet,
|
|
21
|
+
type PluginPayload,
|
|
22
|
+
} from './plugin'
|
|
23
|
+
|
|
24
|
+
type SerializeFragmentFunction = typeof DOMSerializer.prototype.serializeFragment
|
|
25
|
+
type SerializeNodeFunction = typeof DOMSerializer.prototype.serializeNode
|
|
26
|
+
type NodesFromSchemaFunction = typeof DOMSerializer.nodesFromSchema
|
|
27
|
+
type MarksFromSchemaFunction = typeof DOMSerializer.marksFromSchema
|
|
28
|
+
|
|
29
|
+
type FunctionWrapper<T extends AnyFunction> = (fn: T) => T
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export interface ClipboardSerializerOptions {
|
|
35
|
+
serializeFragmentWrapper?: FunctionWrapper<SerializeFragmentFunction>
|
|
36
|
+
serializeNodeWrapper?: FunctionWrapper<SerializeNodeFunction>
|
|
37
|
+
nodesFromSchemaWrapper?: FunctionWrapper<NodesFromSchemaFunction>
|
|
38
|
+
marksFromSchemaWrapper?: FunctionWrapper<MarksFromSchemaFunction>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mergeWrappers<T extends AnyFunction>(wrappers: Array<FunctionWrapper<T> | undefined | null>): FunctionWrapper<T> {
|
|
42
|
+
return (fn: T) => wrappers.filter(isNotNullish).reduce((fn, wrapper) => wrapper(fn), fn)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function wrapFunction<T extends AnyFunction>(fn: T, wrapper?: FunctionWrapper<T>): T {
|
|
46
|
+
return wrapper ? wrapper(fn) : fn
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class CustomDOMSerializer extends DOMSerializer {
|
|
50
|
+
constructor(
|
|
51
|
+
nodes: Record<string, (node: ProseMirrorNode) => DOMOutputSpec>,
|
|
52
|
+
marks: Record<string, (mark: Mark, inline: boolean) => DOMOutputSpec>,
|
|
53
|
+
private serializeFragmentWrapper?: FunctionWrapper<SerializeFragmentFunction>,
|
|
54
|
+
private serializeNodeWrapper?: FunctionWrapper<SerializeNodeFunction>,
|
|
55
|
+
) {
|
|
56
|
+
super(nodes, marks)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override serializeFragment(...args: Parameters<SerializeFragmentFunction>): ReturnType<SerializeFragmentFunction> {
|
|
60
|
+
const fn: SerializeFragmentFunction = (...args) => super.serializeFragment(...args)
|
|
61
|
+
return wrapFunction(fn, this.serializeFragmentWrapper)(...args)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override serializeNode(...args: Parameters<SerializeNodeFunction>): ReturnType<SerializeNodeFunction> {
|
|
65
|
+
const fn: SerializeNodeFunction = (...args) => super.serializeNode(...args)
|
|
66
|
+
return wrapFunction(fn, this.serializeNodeWrapper)(...args)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createCustomDOMSerializer(schema: Schema, options: ClipboardSerializerOptions) {
|
|
71
|
+
const nodesFromSchema: NodesFromSchemaFunction = (...args) => DOMSerializer.nodesFromSchema(...args)
|
|
72
|
+
const marksFromSchema: MarksFromSchemaFunction = (...args) => DOMSerializer.marksFromSchema(...args)
|
|
73
|
+
const nodes = wrapFunction(nodesFromSchema, options.nodesFromSchemaWrapper)(schema)
|
|
74
|
+
const marks = wrapFunction(marksFromSchema, options.marksFromSchemaWrapper)(schema)
|
|
75
|
+
return new CustomDOMSerializer(nodes, marks, options.serializeFragmentWrapper, options.serializeNodeWrapper)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const clipboardSerializerFacet = defineFacet<ClipboardSerializerOptions, PluginPayload>({
|
|
79
|
+
reducer: (inputs: ClipboardSerializerOptions[]): PluginPayload => {
|
|
80
|
+
const options: ClipboardSerializerOptions = {
|
|
81
|
+
serializeFragmentWrapper: mergeWrappers(inputs.map((input) => input.serializeFragmentWrapper)),
|
|
82
|
+
serializeNodeWrapper: mergeWrappers(inputs.map((input) => input.serializeNodeWrapper)),
|
|
83
|
+
nodesFromSchemaWrapper: mergeWrappers(inputs.map((input) => input.nodesFromSchemaWrapper)),
|
|
84
|
+
marksFromSchemaWrapper: mergeWrappers(inputs.map((input) => input.marksFromSchemaWrapper)),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return ({ schema }) => {
|
|
88
|
+
const clipboardSerializer = createCustomDOMSerializer(schema, options)
|
|
89
|
+
|
|
90
|
+
return [
|
|
91
|
+
new ProseMirrorPlugin({
|
|
92
|
+
key: new PluginKey('prosekit-clipboard-serializer'),
|
|
93
|
+
props: { clipboardSerializer },
|
|
94
|
+
}),
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
singleton: true,
|
|
99
|
+
parent: pluginFacet,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export function defineClipboardSerializer(options: ClipboardSerializerOptions): PlainExtension {
|
|
106
|
+
return defineFacetPayload(clipboardSerializerFacet, [options]) as PlainExtension
|
|
107
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addMark,
|
|
3
|
+
type AddMarkOptions,
|
|
4
|
+
} from '../commands/add-mark'
|
|
5
|
+
import {
|
|
6
|
+
insertDefaultBlock,
|
|
7
|
+
type InsertDefaultBlockOptions,
|
|
8
|
+
} from '../commands/insert-default-block'
|
|
9
|
+
import {
|
|
10
|
+
insertNode,
|
|
11
|
+
type InsertNodeOptions,
|
|
12
|
+
} from '../commands/insert-node'
|
|
13
|
+
import {
|
|
14
|
+
insertText,
|
|
15
|
+
type InsertTextOptions,
|
|
16
|
+
} from '../commands/insert-text'
|
|
17
|
+
import {
|
|
18
|
+
removeMark,
|
|
19
|
+
type RemoveMarkOptions,
|
|
20
|
+
} from '../commands/remove-mark'
|
|
21
|
+
import {
|
|
22
|
+
removeNode,
|
|
23
|
+
type RemoveNodeOptions,
|
|
24
|
+
} from '../commands/remove-node'
|
|
25
|
+
import { selectAll } from '../commands/select-all'
|
|
26
|
+
import {
|
|
27
|
+
setBlockType,
|
|
28
|
+
type SetBlockTypeOptions,
|
|
29
|
+
} from '../commands/set-block-type'
|
|
30
|
+
import {
|
|
31
|
+
setNodeAttrs,
|
|
32
|
+
type SetNodeAttrsOptions,
|
|
33
|
+
} from '../commands/set-node-attrs'
|
|
34
|
+
import {
|
|
35
|
+
toggleWrap,
|
|
36
|
+
type ToggleWrapOptions,
|
|
37
|
+
} from '../commands/toggle-wrap'
|
|
38
|
+
import {
|
|
39
|
+
unsetBlockType,
|
|
40
|
+
type UnsetBlockTypeOptions,
|
|
41
|
+
} from '../commands/unset-block-type'
|
|
42
|
+
import {
|
|
43
|
+
unsetMark,
|
|
44
|
+
type UnsetMarkOptions,
|
|
45
|
+
} from '../commands/unset-mark'
|
|
46
|
+
import {
|
|
47
|
+
wrap,
|
|
48
|
+
type WrapOptions,
|
|
49
|
+
} from '../commands/wrap'
|
|
50
|
+
import { commandFacet } from '../facets/command'
|
|
51
|
+
import { defineFacetPayload } from '../facets/facet-extension'
|
|
52
|
+
import type { Extension } from '../types/extension'
|
|
53
|
+
import type { CommandCreator } from '../types/extension-command'
|
|
54
|
+
|
|
55
|
+
export function defineCommands<
|
|
56
|
+
T extends Record<string, CommandCreator> = Record<string, CommandCreator>,
|
|
57
|
+
>(
|
|
58
|
+
commands: T,
|
|
59
|
+
): Extension<{
|
|
60
|
+
Commands: { [K in keyof T]: Parameters<T[K]> }
|
|
61
|
+
}> {
|
|
62
|
+
return defineFacetPayload(commandFacet, [commands]) as Extension<{
|
|
63
|
+
Commands: { [K in keyof T]: Parameters<T[K]> }
|
|
64
|
+
}>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
export type BaseCommandsExtension = Extension<{
|
|
71
|
+
Commands: {
|
|
72
|
+
insertText: [options: InsertTextOptions]
|
|
73
|
+
insertNode: [options: InsertNodeOptions]
|
|
74
|
+
removeNode: [options: RemoveNodeOptions]
|
|
75
|
+
wrap: [options: WrapOptions]
|
|
76
|
+
toggleWrap: [options: ToggleWrapOptions]
|
|
77
|
+
setBlockType: [options: SetBlockTypeOptions]
|
|
78
|
+
setNodeAttrs: [options: SetNodeAttrsOptions]
|
|
79
|
+
insertDefaultBlock: [options?: InsertDefaultBlockOptions]
|
|
80
|
+
selectAll: []
|
|
81
|
+
addMark: [options: AddMarkOptions]
|
|
82
|
+
removeMark: [options: RemoveMarkOptions]
|
|
83
|
+
unsetBlockType: [options?: UnsetBlockTypeOptions]
|
|
84
|
+
unsetMark: [options?: UnsetMarkOptions]
|
|
85
|
+
}
|
|
86
|
+
}>
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add some base commands
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export function defineBaseCommands(): BaseCommandsExtension {
|
|
94
|
+
return defineCommands({
|
|
95
|
+
insertText,
|
|
96
|
+
|
|
97
|
+
insertNode,
|
|
98
|
+
|
|
99
|
+
removeNode,
|
|
100
|
+
|
|
101
|
+
wrap,
|
|
102
|
+
|
|
103
|
+
toggleWrap,
|
|
104
|
+
|
|
105
|
+
setBlockType,
|
|
106
|
+
|
|
107
|
+
setNodeAttrs,
|
|
108
|
+
|
|
109
|
+
insertDefaultBlock,
|
|
110
|
+
|
|
111
|
+
selectAll,
|
|
112
|
+
|
|
113
|
+
addMark,
|
|
114
|
+
|
|
115
|
+
removeMark,
|
|
116
|
+
|
|
117
|
+
unsetBlockType,
|
|
118
|
+
|
|
119
|
+
unsetMark,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { createEditor } from '../editor/editor'
|
|
8
|
+
import { defineTestExtension } from '../testing'
|
|
9
|
+
|
|
10
|
+
import type { DefaultStateOptions } from './default-state'
|
|
11
|
+
|
|
12
|
+
describe('defineDefaultState', () => {
|
|
13
|
+
const docJSON = {
|
|
14
|
+
type: 'doc',
|
|
15
|
+
content: [
|
|
16
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'docJSON' }] },
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
const docHTMLString = '<p>docHTMLString</p>'
|
|
20
|
+
const docHTMLElement = document.createElement('p')
|
|
21
|
+
docHTMLElement.innerHTML = 'docHTMLElement'
|
|
22
|
+
|
|
23
|
+
it('can set the default document', () => {
|
|
24
|
+
const run = (options: DefaultStateOptions): string => {
|
|
25
|
+
const extension = defineTestExtension()
|
|
26
|
+
const editor = createEditor({ extension, ...options })
|
|
27
|
+
return editor.state.doc.toString()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
expect(run({ defaultDoc: docJSON })).toContain('docJSON')
|
|
31
|
+
expect(run({ defaultHTML: docHTMLString })).toContain('docHTMLString')
|
|
32
|
+
expect(run({ defaultHTML: docHTMLElement })).toContain('docHTMLElement')
|
|
33
|
+
|
|
34
|
+
expect(run({ defaultContent: docJSON })).toContain('docJSON')
|
|
35
|
+
expect(run({ defaultContent: docHTMLString })).toContain('docHTMLString')
|
|
36
|
+
expect(run({ defaultContent: docHTMLElement })).toContain('docHTMLElement')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('can set the default selection', () => {
|
|
40
|
+
const extension = defineTestExtension()
|
|
41
|
+
const editor = createEditor({
|
|
42
|
+
extension,
|
|
43
|
+
defaultContent: docJSON,
|
|
44
|
+
defaultSelection: {
|
|
45
|
+
anchor: 4,
|
|
46
|
+
head: 1,
|
|
47
|
+
type: 'text',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(editor.state.selection.toJSON()).toEqual({
|
|
52
|
+
anchor: 4,
|
|
53
|
+
head: 1,
|
|
54
|
+
type: 'text',
|
|
55
|
+
})
|
|
56
|
+
expect(editor.state.selection.content().content.toString()).toEqual(
|
|
57
|
+
'<paragraph("doc")>',
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Selection,
|
|
3
|
+
type EditorStateConfig,
|
|
4
|
+
} from '@prosekit/pm/state'
|
|
5
|
+
|
|
6
|
+
import { defineFacetPayload } from '../facets/facet-extension'
|
|
7
|
+
import { stateFacet } from '../facets/state'
|
|
8
|
+
import type { PlainExtension } from '../types/extension'
|
|
9
|
+
import type {
|
|
10
|
+
NodeJSON,
|
|
11
|
+
SelectionJSON,
|
|
12
|
+
} from '../types/model'
|
|
13
|
+
import { getEditorContentJSON } from '../utils/editor-content'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export interface DefaultStateOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The starting document to use when creating the editor. It can be a
|
|
21
|
+
* ProseMirror node JSON object, a HTML string, or a HTML element instance.
|
|
22
|
+
*/
|
|
23
|
+
defaultContent?: NodeJSON | string | HTMLElement
|
|
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
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A JSON object representing the starting selection to use when creating the
|
|
43
|
+
* editor. It's only used when `defaultContent` is also provided.
|
|
44
|
+
*/
|
|
45
|
+
defaultSelection?: SelectionJSON
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Define a default state for the editor.
|
|
50
|
+
*
|
|
51
|
+
* @param options
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export function defineDefaultState({
|
|
56
|
+
defaultSelection,
|
|
57
|
+
defaultContent,
|
|
58
|
+
defaultDoc,
|
|
59
|
+
defaultHTML,
|
|
60
|
+
}: DefaultStateOptions): PlainExtension {
|
|
61
|
+
const defaultDocContent = defaultContent || defaultDoc || defaultHTML
|
|
62
|
+
|
|
63
|
+
return defineFacetPayload(stateFacet, [
|
|
64
|
+
({ schema }) => {
|
|
65
|
+
const config: EditorStateConfig = {}
|
|
66
|
+
if (defaultDocContent) {
|
|
67
|
+
const json = getEditorContentJSON(schema, defaultDocContent)
|
|
68
|
+
config.doc = schema.nodeFromJSON(json)
|
|
69
|
+
if (defaultSelection) {
|
|
70
|
+
config.selection = Selection.fromJSON(config.doc, defaultSelection)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return config
|
|
74
|
+
},
|
|
75
|
+
]) as PlainExtension
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Attrs } from '@prosekit/pm/model'
|
|
2
|
+
|
|
3
|
+
import type { Extension } from '../types/extension'
|
|
4
|
+
|
|
5
|
+
import { defineNodeSpec } from './node-spec'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export type DocExtension = Extension<{ Nodes: { doc: Attrs } }>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*
|
|
15
|
+
* @deprecated Use the following import instead:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { defineDoc } from 'prosekit/extensions/doc'
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function defineDoc(): DocExtension {
|
|
22
|
+
console.warn(
|
|
23
|
+
'[prosekit] The `defineDoc` function from `prosekit/core` is deprecated. Use the following import instead: `import { defineDoc } from "prosekit/extensions/doc"`.',
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return defineNodeSpec({
|
|
27
|
+
name: 'doc',
|
|
28
|
+
content: 'block+',
|
|
29
|
+
topNode: true,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { EditorState } from '@prosekit/pm/state'
|
|
2
|
+
import type { EditorView } from '@prosekit/pm/view'
|
|
3
|
+
|
|
4
|
+
import type { PlainExtension } from '../../types/extension'
|
|
5
|
+
|
|
6
|
+
import { defineUpdateHandler } from './plugin-view'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A function that is called when the editor document is changed.
|
|
10
|
+
*
|
|
11
|
+
* @param view - The editor view.
|
|
12
|
+
* @param prevState - The previous editor state.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export type DocChangeHandler = (
|
|
17
|
+
view: EditorView,
|
|
18
|
+
prevState: EditorState,
|
|
19
|
+
) => void
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers a event handler that is called when the editor document is changed.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export function defineDocChangeHandler(
|
|
27
|
+
handler: DocChangeHandler,
|
|
28
|
+
): PlainExtension {
|
|
29
|
+
return defineUpdateHandler((view, prevState) => {
|
|
30
|
+
if (!view.state.doc.eq(prevState.doc)) {
|
|
31
|
+
handler(view, prevState)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
vi,
|
|
6
|
+
} from 'vitest'
|
|
7
|
+
|
|
8
|
+
import { createEditor } from '../../editor/editor'
|
|
9
|
+
import { defineTestExtension } from '../../testing'
|
|
10
|
+
|
|
11
|
+
import { defineDOMEventHandler } from './dom-event'
|
|
12
|
+
|
|
13
|
+
describe('defineDOMEventHandler', () => {
|
|
14
|
+
it('should register and unregister event handlers dynamically', () => {
|
|
15
|
+
const div = document.body.appendChild(document.createElement('div'))
|
|
16
|
+
const handleFocus = vi.fn()
|
|
17
|
+
const handleBlur = vi.fn()
|
|
18
|
+
|
|
19
|
+
const extension = defineTestExtension()
|
|
20
|
+
const editor = createEditor({ extension })
|
|
21
|
+
editor.mount(div)
|
|
22
|
+
|
|
23
|
+
expect(handleFocus).toHaveBeenCalledTimes(0)
|
|
24
|
+
expect(handleBlur).toHaveBeenCalledTimes(0)
|
|
25
|
+
|
|
26
|
+
const disposeFocus = editor.use(defineFocusEventHandler(handleFocus))
|
|
27
|
+
const disposeBlur = editor.use(defineBlurEventHandler(handleBlur))
|
|
28
|
+
expect(handleFocus).toHaveBeenCalledTimes(0)
|
|
29
|
+
expect(handleBlur).toHaveBeenCalledTimes(0)
|
|
30
|
+
|
|
31
|
+
editor.focus()
|
|
32
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
33
|
+
expect(handleBlur).toHaveBeenCalledTimes(0)
|
|
34
|
+
|
|
35
|
+
editor.blur()
|
|
36
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
37
|
+
expect(handleBlur).toHaveBeenCalledTimes(1)
|
|
38
|
+
|
|
39
|
+
disposeFocus()
|
|
40
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
41
|
+
expect(handleBlur).toHaveBeenCalledTimes(1)
|
|
42
|
+
|
|
43
|
+
editor.focus()
|
|
44
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
45
|
+
expect(handleBlur).toHaveBeenCalledTimes(1)
|
|
46
|
+
|
|
47
|
+
editor.blur()
|
|
48
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
49
|
+
expect(handleBlur).toHaveBeenCalledTimes(2)
|
|
50
|
+
|
|
51
|
+
disposeBlur()
|
|
52
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
53
|
+
expect(handleBlur).toHaveBeenCalledTimes(2)
|
|
54
|
+
|
|
55
|
+
editor.focus()
|
|
56
|
+
editor.blur()
|
|
57
|
+
editor.focus()
|
|
58
|
+
editor.blur()
|
|
59
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
60
|
+
expect(handleBlur).toHaveBeenCalledTimes(2)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function defineFocusEventHandler(handler: VoidFunction) {
|
|
65
|
+
return defineDOMEventHandler('focus', handler)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function defineBlurEventHandler(handler: VoidFunction) {
|
|
69
|
+
return defineDOMEventHandler('blur', handler)
|
|
70
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PluginKey,
|
|
3
|
+
ProseMirrorPlugin,
|
|
4
|
+
} from '@prosekit/pm/state'
|
|
5
|
+
import type {
|
|
6
|
+
DOMEventMap,
|
|
7
|
+
EditorView,
|
|
8
|
+
} from '@prosekit/pm/view'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defineFacet,
|
|
12
|
+
type Facet,
|
|
13
|
+
} from '../../facets/facet'
|
|
14
|
+
import { defineFacetPayload } from '../../facets/facet-extension'
|
|
15
|
+
import type { PlainExtension } from '../../types/extension'
|
|
16
|
+
import type { Setter } from '../../types/setter'
|
|
17
|
+
import { groupEntries } from '../../utils/array-grouping'
|
|
18
|
+
import { combineEventHandlers } from '../../utils/combine-event-handlers'
|
|
19
|
+
import {
|
|
20
|
+
pluginFacet,
|
|
21
|
+
type PluginPayload,
|
|
22
|
+
} from '../plugin'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A function to handle the events fired on the editable DOM element. Returns
|
|
26
|
+
* `true` to indicate that it handled the given event. When returning `true`,
|
|
27
|
+
* you are responsible for calling `event.preventDefault()` yourself (or not, if
|
|
28
|
+
* you want to allow the default behavior).
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export type DOMEventHandler<Event extends keyof DOMEventMap = string> = (
|
|
33
|
+
view: EditorView,
|
|
34
|
+
event: DOMEventMap[Event],
|
|
35
|
+
) => boolean | void
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export function defineDomEventFacetPayload(
|
|
41
|
+
...payloads: DOMEventPayload[]
|
|
42
|
+
): PlainExtension {
|
|
43
|
+
return defineFacetPayload<DOMEventPayload>(
|
|
44
|
+
domEventFacet,
|
|
45
|
+
payloads,
|
|
46
|
+
) as PlainExtension
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a new event handler for the given event type.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export function defineDOMEventHandler<Event extends keyof DOMEventMap = string>(
|
|
55
|
+
event: Event,
|
|
56
|
+
handler: DOMEventHandler<Event>,
|
|
57
|
+
): PlainExtension {
|
|
58
|
+
return defineDomEventFacetPayload([
|
|
59
|
+
event as string,
|
|
60
|
+
handler as DOMEventHandler,
|
|
61
|
+
])
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
type DOMEventPayload = [event: string, handler: DOMEventHandler]
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
const domEventFacet: Facet<DOMEventPayload, PluginPayload> = defineFacet(
|
|
73
|
+
{
|
|
74
|
+
reduce: () => {
|
|
75
|
+
const setHandlersMap: Record<string, Setter<DOMEventHandler[]>> = {}
|
|
76
|
+
const combinedHandlerMap: Record<string, DOMEventHandler> = {}
|
|
77
|
+
|
|
78
|
+
let plugin: ProseMirrorPlugin | undefined
|
|
79
|
+
|
|
80
|
+
const update = (payloads: DOMEventPayload[]): void => {
|
|
81
|
+
let hasNewEvent = false
|
|
82
|
+
|
|
83
|
+
for (const [event] of payloads) {
|
|
84
|
+
if (!setHandlersMap[event]) {
|
|
85
|
+
hasNewEvent = true
|
|
86
|
+
const [setHandlers, combinedHandler] = combineEventHandlers<DOMEventHandler>()
|
|
87
|
+
setHandlersMap[event] = setHandlers
|
|
88
|
+
const e: DOMEventHandler = (view, eventObject) => {
|
|
89
|
+
return combinedHandler(view, eventObject)
|
|
90
|
+
}
|
|
91
|
+
combinedHandlerMap[event] = e
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const map: Record<string, DOMEventHandler[] | undefined> = groupEntries<DOMEventMap>(payloads)
|
|
96
|
+
for (const [event, setHandlers] of Object.entries(setHandlersMap)) {
|
|
97
|
+
const handlers = map[event] ?? []
|
|
98
|
+
setHandlers(handlers)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (hasNewEvent) {
|
|
102
|
+
plugin = new ProseMirrorPlugin({
|
|
103
|
+
key: new PluginKey('prosekit-dom-event-handler'),
|
|
104
|
+
props: { handleDOMEvents: combinedHandlerMap },
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return function reducer(inputs) {
|
|
110
|
+
update(inputs)
|
|
111
|
+
return plugin ?? []
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
parent: pluginFacet,
|
|
115
|
+
singleton: true,
|
|
116
|
+
},
|
|
117
|
+
)
|