Skip to content

Commit f56abc8

Browse files
fix: Image block DOM clutter, keyboard handling, and copy/paste (#456)
* Reduced image block DOM clutter and fixed key handling * Improved block without inline content copying and dragging * Added comments and edited image copy/paste test (now tests new behavior) * Updated snapshot
1 parent 3747dac commit f56abc8

File tree

18 files changed

+149
-86
lines changed

18 files changed

+149
-86
lines changed

packages/core/src/api/exporters/copyExtension.ts

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,46 @@
11
import { Extension } from "@tiptap/core";
2-
import { Plugin } from "prosemirror-state";
2+
import { NodeSelection, Plugin } from "prosemirror-state";
3+
import { Node } from "prosemirror-model";
34

45
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
56
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
67
import { createExternalHTMLExporter } from "./html/externalHTMLExporter";
78
import { createInternalHTMLSerializer } from "./html/internalHTMLSerializer";
89
import { cleanHTMLToMarkdown } from "./markdown/markdownExporter";
10+
import { EditorView } from "prosemirror-view";
11+
12+
function selectedFragmentToHTML<
13+
BSchema extends BlockSchema,
14+
I extends InlineContentSchema,
15+
S extends StyleSchema
16+
>(
17+
view: EditorView,
18+
editor: BlockNoteEditor<BSchema, I, S>
19+
): {
20+
internalHTML: string;
21+
externalHTML: string;
22+
plainText: string;
23+
} {
24+
const selectedFragment = view.state.selection.content().content;
25+
26+
const internalHTMLSerializer = createInternalHTMLSerializer(
27+
view.state.schema,
28+
editor
29+
);
30+
const internalHTML =
31+
internalHTMLSerializer.serializeProseMirrorFragment(selectedFragment);
32+
33+
const externalHTMLExporter = createExternalHTMLExporter(
34+
view.state.schema,
35+
editor
36+
);
37+
const externalHTML =
38+
externalHTMLExporter.exportProseMirrorFragment(selectedFragment);
39+
40+
const plainText = cleanHTMLToMarkdown(externalHTML);
41+
42+
return { internalHTML, externalHTML, plainText };
43+
}
944

1045
export const createCopyToClipboardExtension = <
1146
BSchema extends BlockSchema,
@@ -17,46 +52,84 @@ export const createCopyToClipboardExtension = <
1752
Extension.create<{ editor: BlockNoteEditor<BSchema, I, S> }, undefined>({
1853
name: "copyToClipboard",
1954
addProseMirrorPlugins() {
20-
const tiptap = this.editor;
21-
const schema = this.editor.schema;
2255
return [
2356
new Plugin({
2457
props: {
2558
handleDOMEvents: {
26-
copy(_view, event) {
59+
copy(view, event) {
2760
// Stops the default browser copy behaviour.
2861
event.preventDefault();
2962
event.clipboardData!.clearData();
3063

31-
const selectedFragment =
32-
tiptap.state.selection.content().content;
33-
34-
const internalHTMLSerializer = createInternalHTMLSerializer(
35-
schema,
36-
editor
37-
);
38-
const internalHTML =
39-
internalHTMLSerializer.serializeProseMirrorFragment(
40-
selectedFragment
41-
);
42-
43-
const externalHTMLExporter = createExternalHTMLExporter(
44-
schema,
45-
editor
46-
);
47-
const externalHTML =
48-
externalHTMLExporter.exportProseMirrorFragment(
49-
selectedFragment
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 ===
71+
"blockContent"
72+
) {
73+
view.dispatch(
74+
view.state.tr.setSelection(
75+
new NodeSelection(
76+
view.state.doc.resolve(view.state.selection.from - 1)
77+
)
78+
)
5079
);
80+
}
5181

52-
const plainText = cleanHTMLToMarkdown(externalHTML);
82+
const { internalHTML, externalHTML, plainText } =
83+
selectedFragmentToHTML(view, editor);
5384

5485
// TODO: Writing to other MIME types not working in Safari for
5586
// some reason.
5687
event.clipboardData!.setData("blocknote/html", internalHTML);
5788
event.clipboardData!.setData("text/html", externalHTML);
5889
event.clipboardData!.setData("text/plain", plainText);
5990

91+
// Prevent default PM handler to be called
92+
return true;
93+
},
94+
// This is for the use-case in which only a block without content
95+
// is selected, e.g. an image block, and dragged (not using the
96+
// drag handle).
97+
dragstart(view, event) {
98+
// Checks if a `NodeSelection` is active.
99+
if (!("node" in view.state.selection)) {
100+
return;
101+
}
102+
103+
// Checks if a `blockContent` node is being dragged.
104+
if (
105+
(view.state.selection.node as Node).type.spec.group !==
106+
"blockContent"
107+
) {
108+
return;
109+
}
110+
111+
// Expands the selection to the parent `blockContainer` node.
112+
view.dispatch(
113+
view.state.tr.setSelection(
114+
new NodeSelection(
115+
view.state.doc.resolve(view.state.selection.from - 1)
116+
)
117+
)
118+
);
119+
120+
// Stops the default browser drag start behaviour.
121+
event.preventDefault();
122+
event.dataTransfer!.clearData();
123+
124+
const { internalHTML, externalHTML, plainText } =
125+
selectedFragmentToHTML(view, editor);
126+
127+
// TODO: Writing to other MIME types not working in Safari for
128+
// some reason.
129+
event.dataTransfer!.setData("blocknote/html", internalHTML);
130+
event.dataTransfer!.setData("text/html", externalHTML);
131+
event.dataTransfer!.setData("text/plain", plainText);
132+
60133
// Prevent default PM handler to be called
61134
return true;
62135
},
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper" style="display: none;"><div class="bn-image-wrapper" style="display: none;"><img class="bn-image" src="" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption"></p></div></div></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div></div></div></div></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div><div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="2"><div class="bn-block" data-node-type="blockContainer" data-id="2"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div><div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="2"><div class="bn-block" data-node-type="blockContainer" data-id="2"><div class="bn-block-content" data-content-type="image" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div></div></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div>
1+
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="simpleImage" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="simpleImage" data-url="exampleURL" data-caption="Caption" data-width="256"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div></div></div></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper" style="display: none;"><div class="bn-image-wrapper" style="display: none;"><img class="bn-image" src="" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption"></p></div></div>
1+
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="simpleImage"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper" style="display: none;"><div class="bn-image-wrapper" style="display: none;"><img class="bn-image" src="" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption"></p></div></div></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="simpleImage"><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div></div></div></div></div></div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-add-image-button" style="display: none;"><div class="bn-add-image-button-icon"></div><p class="bn-add-image-button-text"></p></div><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"><div class="bn-image-resize-handle" style="left: 4px;"></div><div class="bn-image-resize-handle" style="right: 4px;"></div></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div>
1+
<div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div><div class="bn-image-block-content-wrapper" style="align-items: flex-start;"><div class="bn-image-and-caption-wrapper"><div class="bn-image-wrapper"><img class="bn-image" src="exampleURL" alt="placeholder" draggable="false" style="width: 0px;"></div><p class="bn-image-caption" style="padding: 4px;"></p></div></div>

0 commit comments

Comments
 (0)