@blocknote/core 0.15.11 → 0.16.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/blocknote.js +888 -844
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -5
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +2 -2
- package/src/api/clipboard/__snapshots__/childToParent.html +1 -0
- package/src/api/clipboard/__snapshots__/childrenToNextParent.html +1 -0
- package/src/api/clipboard/__snapshots__/childrenToNextParentsChildren.html +1 -0
- package/src/api/clipboard/__snapshots__/image.html +1 -0
- package/src/api/clipboard/__snapshots__/multipleStyledText.html +1 -0
- package/src/api/clipboard/__snapshots__/nestedImage.html +1 -0
- package/src/api/clipboard/__snapshots__/partialChildToParent.html +1 -0
- package/src/api/clipboard/__snapshots__/styledText.html +1 -0
- package/src/api/clipboard/__snapshots__/tableAllCells.html +1 -0
- package/src/api/clipboard/__snapshots__/tableCell.html +1 -0
- package/src/api/clipboard/__snapshots__/tableCellText.html +1 -0
- package/src/api/clipboard/__snapshots__/tableRow.html +1 -0
- package/src/api/clipboard/__snapshots__/unstyledText.html +1 -0
- package/src/api/clipboard/clipboard.test.ts +284 -0
- package/src/api/{parsers → clipboard/fromClipboard}/fileDropExtension.ts +2 -2
- package/src/api/{parsers → clipboard/fromClipboard}/handleFileInsertion.ts +4 -4
- package/src/api/{parsers → clipboard/fromClipboard}/pasteExtension.ts +14 -7
- package/src/api/{exporters → clipboard/toClipboard}/copyExtension.ts +70 -43
- package/src/api/exporters/html/externalHTMLExporter.ts +14 -7
- package/src/api/exporters/html/htmlConversion.test.ts +4 -147
- package/src/api/exporters/html/internalHTMLSerializer.ts +5 -2
- package/src/api/parsers/html/parseHTML.test.ts +3 -6
- package/src/api/parsers/markdown/__snapshots__/pasted/complex.json +319 -0
- package/src/api/parsers/markdown/__snapshots__/pasted/issue-226-1.json +81 -0
- package/src/api/parsers/{html/__snapshots__/paste/parse-deep-nested-content.json → markdown/__snapshots__/pasted/issue-226-2.json} +35 -110
- package/src/api/parsers/markdown/__snapshots__/pasted/nested.json +81 -0
- package/src/api/parsers/markdown/__snapshots__/pasted/non-nested.json +81 -0
- package/src/api/parsers/markdown/__snapshots__/pasted/styled.json +61 -0
- package/src/api/parsers/markdown/parseMarkdown.test.ts +15 -0
- package/src/api/testUtil/paste.ts +46 -0
- package/src/blocks/TableBlockContent/TableBlockContent.ts +0 -1
- package/src/editor/BlockNoteExtensions.ts +3 -3
- package/src/editor/transformPasted.ts +34 -2
- package/src/extensions/SideMenu/SideMenuPlugin.ts +6 -7
- package/src/schema/blocks/createSpec.ts +16 -7
- package/types/src/api/clipboard/clipboard.test.d.ts +1 -0
- package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +6 -0
- package/types/src/api/{parsers → clipboard/fromClipboard}/handleFileInsertion.d.ts +2 -2
- package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +6 -0
- package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +12 -0
- package/types/src/api/exporters/html/externalHTMLExporter.d.ts +1 -0
- package/types/src/api/testUtil/paste.d.ts +2 -0
- package/types/src/editor/transformPasted.d.ts +8 -1
- package/src/api/exporters/html/__snapshots_fragment_edge_cases__/selectionLeavesBlockChildren.html +0 -1
- package/src/api/exporters/html/__snapshots_fragment_edge_cases__/selectionSpansBlocksChildren.html +0 -1
- package/src/api/parsers/html/__snapshots__/paste/parse-google-docs-html.json +0 -476
- package/types/src/api/exporters/copyExtension.d.ts +0 -6
- package/types/src/api/parsers/fileDropExtension.d.ts +0 -6
- package/types/src/api/parsers/pasteExtension.d.ts +0 -6
- /package/src/api/{exporters/html/__snapshots_fragment_edge_cases__/selectionWithinBlockChildren.html → clipboard/__snapshots__/multipleChildren.html} +0 -0
- /package/src/api/{parsers → clipboard/fromClipboard}/acceptedMIMETypes.ts +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/list-test.json → list-test.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-basic-block-types.json → parse-basic-block-types.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-div-with-inline-content.json → parse-div-with-inline-content.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-divs.json → parse-divs.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-fake-image-caption.json → parse-fake-image-caption.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-image-in-paragraph.json → parse-image-in-paragraph.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-mixed-nested-lists.json → parse-mixed-nested-lists.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-nested-lists-with-paragraphs.json → parse-nested-lists-with-paragraphs.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-nested-lists.json → parse-nested-lists.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-notion-html.json → parse-notion-html.json} +0 -0
- /package/src/api/parsers/html/__snapshots__/{paste/parse-two-divs.json → parse-two-divs.json} +0 -0
- /package/types/src/api/{parsers → clipboard/fromClipboard}/acceptedMIMETypes.d.ts +0 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
2
|
import { Node } from "prosemirror-model";
|
|
3
3
|
import { NodeSelection, Plugin } from "prosemirror-state";
|
|
4
|
+
import { CellSelection } from "prosemirror-tables";
|
|
5
|
+
import * as pmView from "prosemirror-view";
|
|
4
6
|
|
|
5
7
|
import { EditorView } from "prosemirror-view";
|
|
6
|
-
import type { BlockNoteEditor } from "
|
|
7
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "
|
|
8
|
-
import { initializeESMDependencies } from "
|
|
9
|
-
import { createExternalHTMLExporter } from "
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async function selectedFragmentToHTML<
|
|
8
|
+
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
|
|
9
|
+
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
|
|
10
|
+
import { initializeESMDependencies } from "../../../util/esmDependencies";
|
|
11
|
+
import { createExternalHTMLExporter } from "../../exporters/html/externalHTMLExporter";
|
|
12
|
+
import { cleanHTMLToMarkdown } from "../../exporters/markdown/markdownExporter";
|
|
13
|
+
|
|
14
|
+
export async function selectedFragmentToHTML<
|
|
14
15
|
BSchema extends BlockSchema,
|
|
15
16
|
I extends InlineContentSchema,
|
|
16
17
|
S extends StyleSchema
|
|
@@ -18,20 +19,61 @@ async function selectedFragmentToHTML<
|
|
|
18
19
|
view: EditorView,
|
|
19
20
|
editor: BlockNoteEditor<BSchema, I, S>
|
|
20
21
|
): Promise<{
|
|
21
|
-
|
|
22
|
+
clipboardHTML: string;
|
|
22
23
|
externalHTML: string;
|
|
23
|
-
|
|
24
|
+
markdown: string;
|
|
24
25
|
}> {
|
|
25
|
-
|
|
26
|
+
// Checks if a `blockContent` node is being copied and expands
|
|
27
|
+
// the selection to the parent `blockContainer` node. This is
|
|
28
|
+
// for the use-case in which only a block without content is
|
|
29
|
+
// selected, e.g. an image block.
|
|
30
|
+
if (
|
|
31
|
+
"node" in view.state.selection &&
|
|
32
|
+
(view.state.selection.node as Node).type.spec.group === "blockContent"
|
|
33
|
+
) {
|
|
34
|
+
editor.dispatch(
|
|
35
|
+
editor._tiptapEditor.state.tr.setSelection(
|
|
36
|
+
new NodeSelection(view.state.doc.resolve(view.state.selection.from - 1))
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
// Uses default ProseMirror clipboard serialization.
|
|
42
|
+
const clipboardHTML: string = (pmView as any).__serializeForClipboard(
|
|
43
|
+
view,
|
|
44
|
+
view.state.selection.content()
|
|
45
|
+
).dom.innerHTML;
|
|
46
|
+
|
|
47
|
+
let selectedFragment = view.state.selection.content().content;
|
|
48
|
+
|
|
49
|
+
// Checks whether block ancestry should be included when creating external
|
|
50
|
+
// HTML. If the selection is within a block content node, the block ancestry
|
|
51
|
+
// is excluded as we only care about the inline content.
|
|
52
|
+
let isWithinBlockContent = false;
|
|
53
|
+
const isWithinTable = view.state.selection instanceof CellSelection;
|
|
54
|
+
if (!isWithinTable) {
|
|
55
|
+
const fragmentWithoutParents = view.state.doc.slice(
|
|
56
|
+
view.state.selection.from,
|
|
57
|
+
view.state.selection.to,
|
|
58
|
+
false
|
|
59
|
+
).content;
|
|
60
|
+
|
|
61
|
+
const children = [];
|
|
62
|
+
for (let i = 0; i < fragmentWithoutParents.childCount; i++) {
|
|
63
|
+
children.push(fragmentWithoutParents.child(i));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isWithinBlockContent =
|
|
67
|
+
children.find(
|
|
68
|
+
(child) =>
|
|
69
|
+
child.type.name === "blockContainer" ||
|
|
70
|
+
child.type.name === "blockGroup" ||
|
|
71
|
+
child.type.spec.group === "blockContent"
|
|
72
|
+
) === undefined;
|
|
73
|
+
if (isWithinBlockContent) {
|
|
74
|
+
selectedFragment = fragmentWithoutParents;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
35
77
|
|
|
36
78
|
await initializeESMDependencies();
|
|
37
79
|
const externalHTMLExporter = createExternalHTMLExporter(
|
|
@@ -40,12 +82,12 @@ async function selectedFragmentToHTML<
|
|
|
40
82
|
);
|
|
41
83
|
const externalHTML = externalHTMLExporter.exportProseMirrorFragment(
|
|
42
84
|
selectedFragment,
|
|
43
|
-
{}
|
|
85
|
+
{ simplifyBlocks: !isWithinBlockContent && !isWithinTable }
|
|
44
86
|
);
|
|
45
87
|
|
|
46
|
-
const
|
|
88
|
+
const markdown = cleanHTMLToMarkdown(externalHTML);
|
|
47
89
|
|
|
48
|
-
return {
|
|
90
|
+
return { clipboardHTML, externalHTML, markdown };
|
|
49
91
|
}
|
|
50
92
|
|
|
51
93
|
const copyToClipboard = <
|
|
@@ -61,30 +103,15 @@ const copyToClipboard = <
|
|
|
61
103
|
event.preventDefault();
|
|
62
104
|
event.clipboardData!.clearData();
|
|
63
105
|
|
|
64
|
-
// Checks if a `blockContent` node is being copied and expands
|
|
65
|
-
// the selection to the parent `blockContainer` node. This is
|
|
66
|
-
// for the use-case in which only a block without content is
|
|
67
|
-
// selected, e.g. an image block.
|
|
68
|
-
if (
|
|
69
|
-
"node" in view.state.selection &&
|
|
70
|
-
(view.state.selection.node as Node).type.spec.group === "blockContent"
|
|
71
|
-
) {
|
|
72
|
-
editor.dispatch(
|
|
73
|
-
editor._tiptapEditor.state.tr.setSelection(
|
|
74
|
-
new NodeSelection(view.state.doc.resolve(view.state.selection.from - 1))
|
|
75
|
-
)
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
106
|
(async () => {
|
|
80
|
-
const {
|
|
107
|
+
const { clipboardHTML, externalHTML, markdown } =
|
|
81
108
|
await selectedFragmentToHTML(view, editor);
|
|
82
109
|
|
|
83
110
|
// TODO: Writing to other MIME types not working in Safari for
|
|
84
111
|
// some reason.
|
|
85
|
-
event.clipboardData!.setData("blocknote/html",
|
|
112
|
+
event.clipboardData!.setData("blocknote/html", clipboardHTML);
|
|
86
113
|
event.clipboardData!.setData("text/html", externalHTML);
|
|
87
|
-
event.clipboardData!.setData("text/plain",
|
|
114
|
+
event.clipboardData!.setData("text/plain", markdown);
|
|
88
115
|
})();
|
|
89
116
|
};
|
|
90
117
|
|
|
@@ -144,14 +171,14 @@ export const createCopyToClipboardExtension = <
|
|
|
144
171
|
event.dataTransfer!.clearData();
|
|
145
172
|
|
|
146
173
|
(async () => {
|
|
147
|
-
const {
|
|
174
|
+
const { clipboardHTML, externalHTML, markdown } =
|
|
148
175
|
await selectedFragmentToHTML(view, editor);
|
|
149
176
|
|
|
150
177
|
// TODO: Writing to other MIME types not working in Safari for
|
|
151
178
|
// some reason.
|
|
152
|
-
event.dataTransfer!.setData("blocknote/html",
|
|
179
|
+
event.dataTransfer!.setData("blocknote/html", clipboardHTML);
|
|
153
180
|
event.dataTransfer!.setData("text/html", externalHTML);
|
|
154
|
-
event.dataTransfer!.setData("text/plain",
|
|
181
|
+
event.dataTransfer!.setData("text/plain", markdown);
|
|
155
182
|
})();
|
|
156
183
|
// Prevent default PM handler to be called
|
|
157
184
|
return true;
|
|
@@ -41,7 +41,7 @@ export interface ExternalHTMLExporter<
|
|
|
41
41
|
) => string;
|
|
42
42
|
exportProseMirrorFragment: (
|
|
43
43
|
fragment: Fragment,
|
|
44
|
-
options: { document?: Document }
|
|
44
|
+
options: { document?: Document; simplifyBlocks?: boolean }
|
|
45
45
|
) => string;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -63,14 +63,18 @@ export const createExternalHTMLExporter = <
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// TODO: maybe cache this serializer (default prosemirror serializer is cached)?
|
|
67
|
+
const serializer = new DOMSerializer(
|
|
68
|
+
DOMSerializer.nodesFromSchema(schema),
|
|
69
|
+
DOMSerializer.marksFromSchema(schema)
|
|
70
|
+
) as DOMSerializer & {
|
|
67
71
|
serializeNodeInner: (
|
|
68
72
|
node: Node,
|
|
69
73
|
options: { document?: Document }
|
|
70
74
|
) => HTMLElement;
|
|
71
75
|
exportProseMirrorFragment: (
|
|
72
76
|
fragment: Fragment,
|
|
73
|
-
options: { document?: Document }
|
|
77
|
+
options: { document?: Document; simplifyBlocks?: boolean }
|
|
74
78
|
) => string;
|
|
75
79
|
exportBlocks: (
|
|
76
80
|
blocks: PartialBlock<BSchema, I, S>[],
|
|
@@ -87,16 +91,19 @@ export const createExternalHTMLExporter = <
|
|
|
87
91
|
// but additionally runs it through the `simplifyBlocks` rehype plugin to
|
|
88
92
|
// convert the internal HTML to external.
|
|
89
93
|
serializer.exportProseMirrorFragment = (fragment, options) => {
|
|
90
|
-
|
|
94
|
+
let externalHTML: any = deps.unified
|
|
91
95
|
.unified()
|
|
92
|
-
.use(deps.rehypeParse.default, { fragment: true })
|
|
93
|
-
|
|
96
|
+
.use(deps.rehypeParse.default, { fragment: true });
|
|
97
|
+
if (options.simplifyBlocks !== false) {
|
|
98
|
+
externalHTML = externalHTML.use(simplifyBlocks, {
|
|
94
99
|
orderedListItemBlockTypes: new Set<string>(["numberedListItem"]),
|
|
95
100
|
unorderedListItemBlockTypes: new Set<string>([
|
|
96
101
|
"bulletListItem",
|
|
97
102
|
"checkListItem",
|
|
98
103
|
]),
|
|
99
|
-
})
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
externalHTML = externalHTML
|
|
100
107
|
.use(deps.rehypeStringify.default)
|
|
101
108
|
.processSync(serializeProseMirrorFragment(fragment, serializer, options));
|
|
102
109
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { TextSelection } from "prosemirror-state";
|
|
2
1
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
-
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
|
|
4
2
|
|
|
5
3
|
import { addIdsToBlocks, partialBlocksToBlocksForTesting } from "../../..";
|
|
6
4
|
import { PartialBlock } from "../../../blocks/defaultBlocks";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
|
|
6
|
+
import { BlockSchema } from "../../../schema";
|
|
7
|
+
import { InlineContentSchema } from "../../../schema";
|
|
8
|
+
import { StyleSchema } from "../../../schema";
|
|
10
9
|
import { initializeESMDependencies } from "../../../util/esmDependencies";
|
|
11
10
|
import { customBlocksTestCases } from "../../testUtil/cases/customBlocks";
|
|
12
11
|
import { customInlineContentTestCases } from "../../testUtil/cases/customInlineContent";
|
|
@@ -106,145 +105,3 @@ describe("Test HTML conversion", () => {
|
|
|
106
105
|
});
|
|
107
106
|
}
|
|
108
107
|
});
|
|
109
|
-
|
|
110
|
-
// Fragments created from ProseMirror selections don't always conform to the
|
|
111
|
-
// schema. This is because ProseMirror preserves the full ancestry of selected
|
|
112
|
-
// nodes, but not the siblings of ancestor nodes. These tests are to verify that
|
|
113
|
-
// Fragments like this are exported to HTML properly, as they can't be created
|
|
114
|
-
// from Block objects like all the other test cases (Block object conversions
|
|
115
|
-
// always conform to the schema).
|
|
116
|
-
describe("Test ProseMirror fragment edge case conversion", () => {
|
|
117
|
-
let editor: BlockNoteEditor;
|
|
118
|
-
const div = document.createElement("div");
|
|
119
|
-
beforeEach(() => {
|
|
120
|
-
editor = BlockNoteEditor.create();
|
|
121
|
-
editor.mount(div);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
afterEach(() => {
|
|
125
|
-
editor.mount(undefined);
|
|
126
|
-
editor._tiptapEditor.destroy();
|
|
127
|
-
editor = undefined as any;
|
|
128
|
-
|
|
129
|
-
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// When the selection starts in a nested block, the Fragment from it omits the
|
|
133
|
-
// `blockContent` node of the parent `blockContainer` if it's not also
|
|
134
|
-
// included in the selection. In the schema, `blockContainer` nodes should
|
|
135
|
-
// contain a single `blockContent` node, so this edge case needs to be tested.
|
|
136
|
-
describe("No block content", () => {
|
|
137
|
-
const blocks: PartialBlock[] = [
|
|
138
|
-
{
|
|
139
|
-
type: "paragraph",
|
|
140
|
-
content: "Paragraph 1",
|
|
141
|
-
children: [
|
|
142
|
-
{
|
|
143
|
-
type: "paragraph",
|
|
144
|
-
content: "Nested Paragraph 1",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
type: "paragraph",
|
|
148
|
-
content: "Nested Paragraph 2",
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
type: "paragraph",
|
|
152
|
-
content: "Nested Paragraph 3",
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
type: "paragraph",
|
|
158
|
-
content: "Paragraph 2",
|
|
159
|
-
children: [
|
|
160
|
-
{
|
|
161
|
-
type: "paragraph",
|
|
162
|
-
content: "Nested Paragraph 1",
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: "paragraph",
|
|
166
|
-
content: "Nested Paragraph 2",
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
type: "paragraph",
|
|
170
|
-
content: "Nested Paragraph 3",
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
},
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
beforeEach(() => {
|
|
177
|
-
editor.replaceBlocks(editor.document, blocks);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("Selection within a block's children", async () => {
|
|
181
|
-
// Selection starts and ends within the first block's children.
|
|
182
|
-
editor.dispatch(
|
|
183
|
-
editor._tiptapEditor.state.tr.setSelection(
|
|
184
|
-
TextSelection.create(editor._tiptapEditor.state.doc, 18, 80)
|
|
185
|
-
)
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
const copiedFragment =
|
|
189
|
-
editor._tiptapEditor.state.selection.content().content;
|
|
190
|
-
|
|
191
|
-
await initializeESMDependencies();
|
|
192
|
-
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
|
|
193
|
-
const externalHTML = exporter.exportProseMirrorFragment(
|
|
194
|
-
copiedFragment,
|
|
195
|
-
{}
|
|
196
|
-
);
|
|
197
|
-
expect(externalHTML).toMatchFileSnapshot(
|
|
198
|
-
"./__snapshots_fragment_edge_cases__/" +
|
|
199
|
-
"selectionWithinBlockChildren.html"
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("Selection leaves a block's children", async () => {
|
|
204
|
-
// Selection starts and ends within the first block's children and ends
|
|
205
|
-
// outside, at a shallower nesting level in the second block.
|
|
206
|
-
editor.dispatch(
|
|
207
|
-
editor._tiptapEditor.state.tr.setSelection(
|
|
208
|
-
TextSelection.create(editor._tiptapEditor.state.doc, 18, 97)
|
|
209
|
-
)
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
const copiedFragment =
|
|
213
|
-
editor._tiptapEditor.state.selection.content().content;
|
|
214
|
-
|
|
215
|
-
await initializeESMDependencies();
|
|
216
|
-
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
|
|
217
|
-
const externalHTML = exporter.exportProseMirrorFragment(
|
|
218
|
-
copiedFragment,
|
|
219
|
-
{}
|
|
220
|
-
);
|
|
221
|
-
expect(externalHTML).toMatchFileSnapshot(
|
|
222
|
-
"./__snapshots_fragment_edge_cases__/" +
|
|
223
|
-
"selectionLeavesBlockChildren.html"
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("Selection spans multiple blocks' children", async () => {
|
|
228
|
-
// Selection starts and ends within the first block's children and ends
|
|
229
|
-
// within the second block's children.
|
|
230
|
-
editor.dispatch(
|
|
231
|
-
editor._tiptapEditor.state.tr.setSelection(
|
|
232
|
-
TextSelection.create(editor._tiptapEditor.state.doc, 18, 163)
|
|
233
|
-
)
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
const copiedFragment =
|
|
237
|
-
editor._tiptapEditor.state.selection.content().content;
|
|
238
|
-
await initializeESMDependencies();
|
|
239
|
-
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
|
|
240
|
-
const externalHTML = exporter.exportProseMirrorFragment(
|
|
241
|
-
copiedFragment,
|
|
242
|
-
{}
|
|
243
|
-
);
|
|
244
|
-
expect(externalHTML).toMatchFileSnapshot(
|
|
245
|
-
"./__snapshots_fragment_edge_cases__/" +
|
|
246
|
-
"selectionSpansBlocksChildren.html"
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
});
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
serializeNodeInner,
|
|
8
8
|
serializeProseMirrorFragment,
|
|
9
9
|
} from "./util/sharedHTMLConversion";
|
|
10
|
-
|
|
11
10
|
// Used to serialize BlockNote blocks and ProseMirror nodes to HTML without
|
|
12
11
|
// losing data. Blocks are exported using the `toInternalHTML` method in their
|
|
13
12
|
// `blockSpec`.
|
|
@@ -48,7 +47,11 @@ export const createInternalHTMLSerializer = <
|
|
|
48
47
|
schema: Schema,
|
|
49
48
|
editor: BlockNoteEditor<BSchema, I, S>
|
|
50
49
|
): InternalHTMLSerializer<BSchema, I, S> => {
|
|
51
|
-
|
|
50
|
+
// TODO: maybe cache this serializer (default prosemirror serializer is cached)?
|
|
51
|
+
const serializer = new DOMSerializer(
|
|
52
|
+
DOMSerializer.nodesFromSchema(schema),
|
|
53
|
+
DOMSerializer.marksFromSchema(schema)
|
|
54
|
+
) as DOMSerializer & {
|
|
52
55
|
serializeNodeInner: (
|
|
53
56
|
node: Node,
|
|
54
57
|
options: { document?: Document }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as pmView from "prosemirror-view";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
import { BlockNoteEditor } from "../../..";
|
|
3
4
|
import { nestedListsToBlockNoteStructure } from "./util/nestedLists";
|
|
@@ -6,16 +7,12 @@ async function parseHTMLAndCompareSnapshots(
|
|
|
6
7
|
html: string,
|
|
7
8
|
snapshotName: string
|
|
8
9
|
) {
|
|
9
|
-
// use a dynamic import because we want to access
|
|
10
|
-
// __parseFromClipboard which is not exposed in types
|
|
11
|
-
const view: any = await import("prosemirror-view");
|
|
12
|
-
|
|
13
10
|
const editor = BlockNoteEditor.create();
|
|
14
11
|
const div = document.createElement("div");
|
|
15
12
|
editor.mount(div);
|
|
16
13
|
const blocks = await editor.tryParseHTMLToBlocks(html);
|
|
17
14
|
|
|
18
|
-
const snapshotPath = "./__snapshots__/
|
|
15
|
+
const snapshotPath = "./__snapshots__/" + snapshotName + ".json";
|
|
19
16
|
expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot(
|
|
20
17
|
snapshotPath
|
|
21
18
|
);
|
|
@@ -34,7 +31,7 @@ async function parseHTMLAndCompareSnapshots(
|
|
|
34
31
|
(window as any).__TEST_OPTIONS.mockID = 0; // reset id counter
|
|
35
32
|
const htmlNode = nestedListsToBlockNoteStructure(html);
|
|
36
33
|
|
|
37
|
-
const slice =
|
|
34
|
+
const slice = (pmView as any).__parseFromClipboard(
|
|
38
35
|
editor.prosemirrorView,
|
|
39
36
|
"",
|
|
40
37
|
htmlNode.innerHTML,
|