Skip to content

Commit

Permalink
fix: Image block DOM clutter, keyboard handling, and copy/paste (#456)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
matthewlipski committed Jan 3, 2024
1 parent 3747dac commit f56abc8
Show file tree
Hide file tree
Showing 18 changed files with 149 additions and 86 deletions.
121 changes: 97 additions & 24 deletions packages/core/src/api/exporters/copyExtension.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
import { Extension } from "@tiptap/core";
import { Plugin } from "prosemirror-state";
import { NodeSelection, Plugin } from "prosemirror-state";
import { Node } from "prosemirror-model";

import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
import { createExternalHTMLExporter } from "./html/externalHTMLExporter";
import { createInternalHTMLSerializer } from "./html/internalHTMLSerializer";
import { cleanHTMLToMarkdown } from "./markdown/markdownExporter";
import { EditorView } from "prosemirror-view";

function selectedFragmentToHTML<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
>(
view: EditorView,
editor: BlockNoteEditor<BSchema, I, S>
): {
internalHTML: string;
externalHTML: string;
plainText: string;
} {
const selectedFragment = view.state.selection.content().content;

const internalHTMLSerializer = createInternalHTMLSerializer(
view.state.schema,
editor
);
const internalHTML =
internalHTMLSerializer.serializeProseMirrorFragment(selectedFragment);

const externalHTMLExporter = createExternalHTMLExporter(
view.state.schema,
editor
);
const externalHTML =
externalHTMLExporter.exportProseMirrorFragment(selectedFragment);

const plainText = cleanHTMLToMarkdown(externalHTML);

return { internalHTML, externalHTML, plainText };
}

export const createCopyToClipboardExtension = <
BSchema extends BlockSchema,
Expand All @@ -17,46 +52,84 @@ export const createCopyToClipboardExtension = <
Extension.create<{ editor: BlockNoteEditor<BSchema, I, S> }, undefined>({
name: "copyToClipboard",
addProseMirrorPlugins() {
const tiptap = this.editor;
const schema = this.editor.schema;
return [
new Plugin({
props: {
handleDOMEvents: {
copy(_view, event) {
copy(view, event) {
// Stops the default browser copy behaviour.
event.preventDefault();
event.clipboardData!.clearData();

const selectedFragment =
tiptap.state.selection.content().content;

const internalHTMLSerializer = createInternalHTMLSerializer(
schema,
editor
);
const internalHTML =
internalHTMLSerializer.serializeProseMirrorFragment(
selectedFragment
);

const externalHTMLExporter = createExternalHTMLExporter(
schema,
editor
);
const externalHTML =
externalHTMLExporter.exportProseMirrorFragment(
selectedFragment
// Checks if a `blockContent` node is being copied and expands
// the selection to the parent `blockContainer` node. This is
// for the use-case in which only a block without content is
// selected, e.g. an image block.
if (
"node" in view.state.selection &&
(view.state.selection.node as Node).type.spec.group ===
"blockContent"
) {
view.dispatch(
view.state.tr.setSelection(
new NodeSelection(
view.state.doc.resolve(view.state.selection.from - 1)
)
)
);
}

const plainText = cleanHTMLToMarkdown(externalHTML);
const { internalHTML, externalHTML, plainText } =
selectedFragmentToHTML(view, editor);

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

// Prevent default PM handler to be called
return true;
},
// This is for the use-case in which only a block without content
// is selected, e.g. an image block, and dragged (not using the
// drag handle).
dragstart(view, event) {
// Checks if a `NodeSelection` is active.
if (!("node" in view.state.selection)) {
return;
}

// Checks if a `blockContent` node is being dragged.
if (
(view.state.selection.node as Node).type.spec.group !==
"blockContent"
) {
return;
}

// Expands the selection to the parent `blockContainer` node.
view.dispatch(
view.state.tr.setSelection(
new NodeSelection(
view.state.doc.resolve(view.state.selection.from - 1)
)
)
);

// Stops the default browser drag start behaviour.
event.preventDefault();
event.dataTransfer!.clearData();

const { internalHTML, externalHTML, plainText } =
selectedFragmentToHTML(view, editor);

// TODO: Writing to other MIME types not working in Safari for
// some reason.
event.dataTransfer!.setData("blocknote/html", internalHTML);
event.dataTransfer!.setData("text/html", externalHTML);
event.dataTransfer!.setData("text/plain", plainText);

// Prevent default PM handler to be called
return true;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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-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>
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
<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>

2 comments on commit f56abc8

@vercel
Copy link

@vercel vercel bot commented on f56abc8 Jan 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on f56abc8 Jan 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.