@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,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expect,
|
|
3
|
+
test,
|
|
4
|
+
} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { mergeObjects } from './merge-objects'
|
|
7
|
+
|
|
8
|
+
// basic merge
|
|
9
|
+
test('merge simple objects', () => {
|
|
10
|
+
const a = { a: 1 }
|
|
11
|
+
const b = { b: 2 }
|
|
12
|
+
expect(
|
|
13
|
+
mergeObjects<{ a: number; b: number }>(a, b),
|
|
14
|
+
).toEqual({ a: 1, b: 2 })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// undefined values should be removed and overwritten
|
|
18
|
+
test('skip undefined values and override', () => {
|
|
19
|
+
const a = { a: 1, b: undefined as number | undefined }
|
|
20
|
+
const b = { b: 2 }
|
|
21
|
+
expect(
|
|
22
|
+
mergeObjects<{ a: number; b: number | undefined }>(a, b),
|
|
23
|
+
).toEqual({ a: 1, b: 2 })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// null or undefined arguments should be ignored
|
|
27
|
+
test('ignore null and undefined inputs', () => {
|
|
28
|
+
const a = { a: 1 }
|
|
29
|
+
expect(mergeObjects(null, undefined, a)).toEqual({ a: 1 })
|
|
30
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { removeUndefinedValues } from './remove-undefined-values'
|
|
2
|
+
import { isNotNullish } from './type-assertion'
|
|
3
|
+
|
|
4
|
+
export function mergeObjects<T extends object>(
|
|
5
|
+
...objects: Array<Partial<T> | null | undefined>
|
|
6
|
+
): Partial<T> {
|
|
7
|
+
const filteredObjects = objects
|
|
8
|
+
.filter(isNotNullish)
|
|
9
|
+
.map(removeUndefinedValues)
|
|
10
|
+
return Object.assign({}, ...filteredObjects) as Partial<T>
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarkSpec,
|
|
3
|
+
NodeSpec,
|
|
4
|
+
} from '@prosekit/pm/model'
|
|
5
|
+
|
|
6
|
+
import { mergeObjects } from './merge-objects'
|
|
7
|
+
|
|
8
|
+
function mergeSpecs(a: NodeSpec, b: NodeSpec): NodeSpec
|
|
9
|
+
function mergeSpecs(a: MarkSpec, b: MarkSpec): MarkSpec
|
|
10
|
+
function mergeSpecs(
|
|
11
|
+
a: NodeSpec | MarkSpec,
|
|
12
|
+
b: NodeSpec | MarkSpec,
|
|
13
|
+
): NodeSpec | MarkSpec {
|
|
14
|
+
type T = typeof a
|
|
15
|
+
|
|
16
|
+
const attrs: T['attrs'] = {}
|
|
17
|
+
const attrNames = new Set([
|
|
18
|
+
...Object.keys(a.attrs ?? {}),
|
|
19
|
+
...Object.keys(b.attrs ?? {}),
|
|
20
|
+
])
|
|
21
|
+
for (const name of attrNames) {
|
|
22
|
+
const attrSpecA = a.attrs?.[name]
|
|
23
|
+
const attrSpecB = b.attrs?.[name]
|
|
24
|
+
const attrSpecMerged = mergeObjects(attrSpecA, attrSpecB)
|
|
25
|
+
if (attrSpecMerged) {
|
|
26
|
+
attrs[name] = attrSpecMerged
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parseDOM: T['parseDOM'] = [...(a.parseDOM ?? []), ...(b.parseDOM ?? [])]
|
|
31
|
+
|
|
32
|
+
return mergeObjects<T>(a, b, { attrs, parseDOM })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { mergeSpecs }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expect,
|
|
3
|
+
test,
|
|
4
|
+
} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { objectEqual } from './object-equal'
|
|
7
|
+
|
|
8
|
+
test('objects with same keys and values are equal', () => {
|
|
9
|
+
expect(objectEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('different keys result in inequality', () => {
|
|
13
|
+
expect(objectEqual({ a: 1, b: 2 }, { a: 1, c: 2 })).toBe(false)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('nested objects are compared recursively', () => {
|
|
17
|
+
const obj1 = { a: { c: 3 }, b: 2 }
|
|
18
|
+
const obj2 = { a: { c: 3 }, b: 2 }
|
|
19
|
+
expect(objectEqual(obj1, obj2)).toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('nested objects with different values are not equal', () => {
|
|
23
|
+
const obj1 = { a: { c: 3 }, b: 2 }
|
|
24
|
+
const obj2 = { a: { c: 4 }, b: 2 }
|
|
25
|
+
expect(objectEqual(obj1, obj2)).toBe(false)
|
|
26
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function objectEqual<T>(a: T, b: T): boolean {
|
|
2
|
+
if (a === b) {
|
|
3
|
+
return true
|
|
4
|
+
}
|
|
5
|
+
if (typeof a !== 'object' || typeof b !== 'object') {
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
8
|
+
if (a === null || b === null) {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
const aKeys = Object.keys(a) as (keyof T)[]
|
|
15
|
+
const bKeys = Object.keys(b) as (keyof T)[]
|
|
16
|
+
if (aKeys.length !== bKeys.length) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
for (const key of aKeys) {
|
|
20
|
+
if (!bKeys.includes(key)) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
if (!objectEqual(a[key], b[key])) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { DOMOutputSpec } from '@prosekit/pm/model'
|
|
2
|
+
import {
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
it,
|
|
6
|
+
} from 'vitest'
|
|
7
|
+
|
|
8
|
+
import { insertOutputSpecAttrs } from './output-spec'
|
|
9
|
+
|
|
10
|
+
describe('insertOutputSpecAttrs', () => {
|
|
11
|
+
it('should insert attrs into an array without attributes', () => {
|
|
12
|
+
const spec: DOMOutputSpec = ['input']
|
|
13
|
+
const result = insertOutputSpecAttrs(spec, [
|
|
14
|
+
['data-foo', 'foo'],
|
|
15
|
+
['checked', ''],
|
|
16
|
+
['style', 'background-color: red'],
|
|
17
|
+
['style', 'color: blue'],
|
|
18
|
+
])
|
|
19
|
+
expect(result).toMatchInlineSnapshot(`
|
|
20
|
+
[
|
|
21
|
+
"input",
|
|
22
|
+
{
|
|
23
|
+
"checked": "",
|
|
24
|
+
"data-foo": "foo",
|
|
25
|
+
"style": "color: blue; background-color: red",
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
`)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should insert attrs into an array with attributes', () => {
|
|
32
|
+
const spec: DOMOutputSpec = ['input', { type: 'checkbox' }]
|
|
33
|
+
const result = insertOutputSpecAttrs(spec, [
|
|
34
|
+
['data-foo', 'foo'],
|
|
35
|
+
['checked', ''],
|
|
36
|
+
['style', 'background-color: red'],
|
|
37
|
+
['style', 'color: blue'],
|
|
38
|
+
])
|
|
39
|
+
expect(result).toMatchInlineSnapshot(`
|
|
40
|
+
[
|
|
41
|
+
"input",
|
|
42
|
+
{
|
|
43
|
+
"checked": "",
|
|
44
|
+
"data-foo": "foo",
|
|
45
|
+
"style": "color: blue; background-color: red",
|
|
46
|
+
"type": "checkbox",
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
`)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should insert attrs into an element', () => {
|
|
53
|
+
const element = document.createElement('input')
|
|
54
|
+
element.type = 'checkbox'
|
|
55
|
+
|
|
56
|
+
const spec: DOMOutputSpec = element
|
|
57
|
+
const result = insertOutputSpecAttrs(spec, [
|
|
58
|
+
['data-foo', 'foo'],
|
|
59
|
+
['checked', ''],
|
|
60
|
+
['style', 'background-color: red'],
|
|
61
|
+
['style', 'color: blue'],
|
|
62
|
+
])
|
|
63
|
+
expect(result).toMatchInlineSnapshot(`
|
|
64
|
+
<input
|
|
65
|
+
checked=""
|
|
66
|
+
data-foo="foo"
|
|
67
|
+
style="color: blue; background-color: red"
|
|
68
|
+
type="checkbox"
|
|
69
|
+
/>
|
|
70
|
+
`)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should insert attrs into an object ', () => {
|
|
74
|
+
const element = document.createElement('input')
|
|
75
|
+
element.type = 'checkbox'
|
|
76
|
+
|
|
77
|
+
const spec: DOMOutputSpec = { dom: element }
|
|
78
|
+
const result = insertOutputSpecAttrs(spec, [
|
|
79
|
+
['data-foo', 'foo'],
|
|
80
|
+
['checked', ''],
|
|
81
|
+
['style', 'background-color: red'],
|
|
82
|
+
['style', 'color: blue'],
|
|
83
|
+
])
|
|
84
|
+
expect(result).toMatchInlineSnapshot(`
|
|
85
|
+
{
|
|
86
|
+
"dom": <input
|
|
87
|
+
checked=""
|
|
88
|
+
data-foo="foo"
|
|
89
|
+
style="color: blue; background-color: red"
|
|
90
|
+
type="checkbox"
|
|
91
|
+
/>,
|
|
92
|
+
}
|
|
93
|
+
`)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { isElementLike } from '@ocavue/utils'
|
|
2
|
+
import type {
|
|
3
|
+
DOMOutputSpec,
|
|
4
|
+
Mark,
|
|
5
|
+
ProseMirrorNode,
|
|
6
|
+
TagParseRule,
|
|
7
|
+
} from '@prosekit/pm/model'
|
|
8
|
+
|
|
9
|
+
import { isNotNullish } from './type-assertion'
|
|
10
|
+
|
|
11
|
+
interface AttrOptions {
|
|
12
|
+
attr: string
|
|
13
|
+
toDOM?: (value: unknown) => [key: string, value: string] | null | undefined
|
|
14
|
+
parseDOM?: (node: HTMLElement) => unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function wrapOutputSpecAttrs<
|
|
18
|
+
T extends ProseMirrorNode | Mark,
|
|
19
|
+
Args extends readonly unknown[],
|
|
20
|
+
>(
|
|
21
|
+
toDOM: (node: T, ...args: Args) => DOMOutputSpec,
|
|
22
|
+
options: AttrOptions[],
|
|
23
|
+
): (node: T, ...args: Args) => DOMOutputSpec {
|
|
24
|
+
return (node, ...args) => {
|
|
25
|
+
const dom = toDOM(node, ...args)
|
|
26
|
+
const pairs = options
|
|
27
|
+
.map((option) => option.toDOM?.(node.attrs[option.attr]))
|
|
28
|
+
.filter(isNotNullish)
|
|
29
|
+
return insertOutputSpecAttrs(dom, pairs)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function wrapTagParseRuleAttrs(
|
|
34
|
+
rule: TagParseRule,
|
|
35
|
+
options: AttrOptions[],
|
|
36
|
+
): TagParseRule {
|
|
37
|
+
const existingGetAttrs = rule.getAttrs
|
|
38
|
+
const existingAttrs = rule.attrs
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
...rule,
|
|
42
|
+
getAttrs: (dom) => {
|
|
43
|
+
const baseAttrs = existingGetAttrs?.(dom) ?? existingAttrs ?? {}
|
|
44
|
+
|
|
45
|
+
if (baseAttrs === false || !dom || !isElementLike(dom)) {
|
|
46
|
+
return baseAttrs ?? null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const insertedAttrs: Record<string, unknown> = {}
|
|
50
|
+
|
|
51
|
+
for (const option of options) {
|
|
52
|
+
if (option.parseDOM) {
|
|
53
|
+
insertedAttrs[option.attr] = option.parseDOM(dom)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { ...baseAttrs, ...insertedAttrs }
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function insertOutputSpecAttrs(
|
|
63
|
+
dom: DOMOutputSpec,
|
|
64
|
+
attrs: Array<[key: string, value: string]>,
|
|
65
|
+
): DOMOutputSpec {
|
|
66
|
+
if (!dom) {
|
|
67
|
+
return dom
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(dom)) {
|
|
71
|
+
const rest = dom.slice(1) as Array<unknown>
|
|
72
|
+
let oldAttrs: Record<string, unknown>
|
|
73
|
+
|
|
74
|
+
if (rest.length > 0 && (rest[0] == null || typeof rest[0] === 'object')) {
|
|
75
|
+
oldAttrs = rest.shift() as Record<string, unknown>
|
|
76
|
+
} else {
|
|
77
|
+
oldAttrs = {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const newAttrs = setObjectAttributes(oldAttrs, attrs)
|
|
81
|
+
return [dom[0], newAttrs, ...rest]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isElementLike(dom)) {
|
|
85
|
+
return setElementAttributes(dom, attrs)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof dom === 'object' && 'dom' in dom && isElementLike(dom.dom)) {
|
|
89
|
+
return { ...dom, dom: setElementAttributes(dom.dom, attrs) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return dom
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function setObjectAttributes(
|
|
96
|
+
obj: Record<string, unknown>,
|
|
97
|
+
attrs: Array<[key: string, value: string]>,
|
|
98
|
+
): Record<string, unknown> {
|
|
99
|
+
obj = { ...obj }
|
|
100
|
+
for (const [key, value] of attrs) {
|
|
101
|
+
const oldValue = obj[key]
|
|
102
|
+
const newValue = key === 'style'
|
|
103
|
+
? joinStyles(value, typeof oldValue === 'string' ? oldValue : '')
|
|
104
|
+
: value
|
|
105
|
+
obj[key] = newValue
|
|
106
|
+
}
|
|
107
|
+
return obj
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function setElementAttributes(
|
|
111
|
+
element: Element,
|
|
112
|
+
attrs: Array<[key: string, value: string]>,
|
|
113
|
+
): Element {
|
|
114
|
+
element = element.cloneNode(true) as Element
|
|
115
|
+
for (const [key, value] of attrs) {
|
|
116
|
+
const oldValue = element.getAttribute(key)
|
|
117
|
+
const newValue = key === 'style'
|
|
118
|
+
? joinStyles(value, typeof oldValue === 'string' ? oldValue : '')
|
|
119
|
+
: value
|
|
120
|
+
element.setAttribute(key, newValue)
|
|
121
|
+
}
|
|
122
|
+
return element
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function joinStyles(...styles: string[]) {
|
|
126
|
+
return styles
|
|
127
|
+
.map((style) => style.trim().replace(/;$/, ''))
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.join('; ')
|
|
130
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
test,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { createEditor } from '../editor/editor'
|
|
8
|
+
import { defineTestExtension } from '../testing'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
elementFromHTML,
|
|
12
|
+
htmlFromNode,
|
|
13
|
+
nodeFromElement,
|
|
14
|
+
nodeFromHTML,
|
|
15
|
+
} from './parse'
|
|
16
|
+
|
|
17
|
+
describe('parse', () => {
|
|
18
|
+
const extension = defineTestExtension()
|
|
19
|
+
const editor = createEditor({ extension })
|
|
20
|
+
const schema = editor.schema
|
|
21
|
+
const n = editor.nodes
|
|
22
|
+
|
|
23
|
+
const element = document.createElement('div')
|
|
24
|
+
element.innerHTML = '<p>hello</p>'
|
|
25
|
+
|
|
26
|
+
const node = n.doc(n.paragraph(schema.text('hello')))
|
|
27
|
+
|
|
28
|
+
const html = '<p>hello</p>'
|
|
29
|
+
|
|
30
|
+
test('nodeFromElement', () => {
|
|
31
|
+
expect(node.eq(nodeFromElement(element, { schema }))).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('nodeFromHTML', () => {
|
|
35
|
+
expect(nodeFromHTML(html, { schema }).eq(node)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('elementFromHTML', () => {
|
|
39
|
+
expect(elementFromHTML(html).innerHTML).toBe(html)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('htmlFromNode', () => {
|
|
43
|
+
expect(htmlFromNode(node)).toBe('<div><p>hello</p></div>')
|
|
44
|
+
expect(htmlFromNode(node.child(0))).toBe('<p>hello</p>')
|
|
45
|
+
})
|
|
46
|
+
})
|