Skip to content

fix: Updated resizable file wrappers #1914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FileBlockConfig } from "@blocknote/core";
import {
createReactBlockSpec,
ReactCustomBlockRenderProps,
ResizableFileBlockWrapper,
ResizableFileWithCaption,
} from "@blocknote/react";

import { RiFilePdfFill } from "react-icons/ri";
Expand All @@ -14,17 +14,15 @@ export const PDFPreview = (
ReactCustomBlockRenderProps<FileBlockConfig, any, any>,
"contentRef"
>,
) => {
return (
<embed
type={"application/pdf"}
src={props.block.props.url}
contentEditable={false}
draggable={false}
onClick={() => props.editor.setTextCursorPosition(props.block)}
/>
);
};
) => (
<embed
type={"application/pdf"}
src={props.block.props.url}
contentEditable={false}
draggable={false}
onClick={() => props.editor.setTextCursorPosition(props.block)}
/>
);

export const PDF = createReactBlockSpec(
{
Expand Down Expand Up @@ -52,13 +50,13 @@ export const PDF = createReactBlockSpec(
},
{
render: (props) => (
<ResizableFileBlockWrapper
<ResizableFileWithCaption
{...(props as any)}
bbuttonText={"Add PDF"}
buttonText={"Add PDF"}
buttonIcon={<RiFilePdfFill size={24} />}
>
<PDFPreview {...(props as any)} />
</ResizableFileBlockWrapper>
</ResizableFileWithCaption>
),
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { defaultProps } from "../defaultProps.js";

import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
import { createFileBlockWrapper } from "../FileBlockContent/helpers/render/createFileBlockWrapper.js";
import { createFileWithCaption } from "../FileBlockContent/helpers/render/createFileWithCaption.js";
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
import { createLinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js";
import { parseAudioElement } from "./parseAudioElement.js";
Expand Down Expand Up @@ -65,10 +65,10 @@ export const audioRender = (
audio.contentEditable = "false";
audio.draggable = false;

return createFileBlockWrapper(
return createFileWithCaption(
block,
editor,
{ dom: audio },
audio,
editor.dictionary.file_blocks.audio.add_button_text,
icon.firstElementChild as HTMLElement,
);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { defaultProps } from "../defaultProps.js";
import { parseEmbedElement } from "./helpers/parse/parseEmbedElement.js";
import { parseFigureElement } from "./helpers/parse/parseFigureElement.js";
import { createFileBlockWrapper } from "./helpers/render/createFileBlockWrapper.js";
import { createFileWithCaption } from "./helpers/render/createFileWithCaption.js";
import { createLinkWithCaption } from "./helpers/toExternalHTML/createLinkWithCaption.js";

export const filePropSchema = {
Expand Down Expand Up @@ -38,7 +38,7 @@ export const fileRender = (
block: BlockFromConfig<typeof fileBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>,
) => {
return createFileBlockWrapper(block, editor);
return createFileWithCaption(block, editor);
};

export const fileParse = (element: HTMLElement) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ import {
import { createAddFileButton } from "./createAddFileButton.js";
import { createFileNameWithIcon } from "./createFileNameWithIcon.js";

export const createFileBlockWrapper = (
export const createFileWithCaption = (
block: BlockFromConfig<FileBlockConfig, any, any>,
editor: BlockNoteEditor<
BlockSchemaWithBlock<FileBlockConfig["type"], FileBlockConfig>,
any,
any
>,
element?: { dom: HTMLElement; destroy?: () => void },
element?: HTMLElement,
buttonText?: string,
buttonIcon?: HTMLElement,
) => {
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";
const fileWithCaption = document.createElement("div");
fileWithCaption.className = "bn-file-with-caption";

const file = document.createElement("div");
file.className = "bn-file";
fileWithCaption.appendChild(file);

// Show the add file button if the file has not been uploaded yet. Change to
// show a loader if a file upload for the block begins.
Expand All @@ -30,50 +34,49 @@ export const createFileBlockWrapper = (
buttonText,
buttonIcon,
);
wrapper.appendChild(addFileButton.dom);

const destroyUploadStartHandler = editor.onUploadStart((blockId) => {
if (blockId === block.id) {
wrapper.removeChild(addFileButton.dom);

const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";
wrapper.appendChild(loading);
addFileButton.dom.replaceWith(loading);
}
});

return {
dom: wrapper,
dom: addFileButton.dom,
destroy: () => {
destroyUploadStartHandler();
addFileButton.destroy();
addFileButton.destroy?.();
},
};
}

const ret: { dom: HTMLElement; destroy?: () => void } = { dom: wrapper };
const ret: { dom: HTMLElement; destroy?: () => void } = {
dom: fileWithCaption,
};

// Show the file preview, or the file name and icon.
if (block.props.showPreview === false || !element) {
// Show file name and icon.
const fileNameWithIcon = createFileNameWithIcon(block);
wrapper.appendChild(fileNameWithIcon.dom);
file.appendChild(fileNameWithIcon.dom);

ret.destroy = () => {
fileNameWithIcon.destroy?.();
};
} else {
// Show file preview.
wrapper.appendChild(element.dom);
file.appendChild(element);
}

// Show the caption if there is one.
if (block.props.caption) {
const caption = document.createElement("p");
caption.className = "bn-file-caption";
caption.textContent = block.props.caption;
wrapper.appendChild(caption);
fileWithCaption.appendChild(caption);
}

return ret;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
import { BlockFromConfig, FileBlockConfig } from "../../../../schema/index.js";
import { createFileBlockWrapper } from "./createFileBlockWrapper.js";
import { createFileWithCaption } from "./createFileWithCaption.js";

export const createResizableFileBlockWrapper = (
export const createResizableFileWithCaption = (
block: BlockFromConfig<FileBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>,
element: { dom: HTMLElement; destroy?: () => void },
resizeHandlesContainerElement: HTMLElement,
element: HTMLElement,
buttonText: string,
buttonIcon: HTMLElement,
): { dom: HTMLElement; destroy: () => void } => {
const { dom, destroy } = createFileBlockWrapper(
const { dom, destroy } = createFileWithCaption(
block,
editor,
element,
buttonText,
buttonIcon,
);
const wrapper = dom;
const fileWithCaption = dom;
if (block.props.url && block.props.showPreview) {
if (block.props.previewWidth) {
wrapper.style.width = `${block.props.previewWidth}px`;
fileWithCaption.style.width = `${block.props.previewWidth}px`;
} else {
wrapper.style.width = "fit-content";
fileWithCaption.style.width = "fit-content";
}
}
const file = fileWithCaption.querySelector(".bn-file") as HTMLElement;

const leftResizeHandle = document.createElement("div");
leftResizeHandle.className = "bn-resize-handle";
Expand All @@ -33,6 +33,15 @@ export const createResizableFileBlockWrapper = (
rightResizeHandle.className = "bn-resize-handle";
rightResizeHandle.style.right = "4px";

// This element ensures `mousemove` and `mouseup` events are captured while
// resizing when the cursor is over the wrapper content. This is because
// embeds are treated as separate HTML documents, so if the content is an
// embed, the events will only fire within that document.
const eventCaptureElement = document.createElement("div");
eventCaptureElement.style.position = "absolute";
eventCaptureElement.style.height = "100%";
eventCaptureElement.style.width = "100%";
Comment on lines +42 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

I think for this to work properly it depends on the parent being relative. Can we force the parent to have position relative


// Temporary parameters set when the user begins resizing the element, used to
// calculate the new width of the element.
let resizeParams:
Expand All @@ -50,11 +59,11 @@ export const createResizableFileBlockWrapper = (
if (!resizeParams) {
if (
!editor.isEditable &&
resizeHandlesContainerElement.contains(leftResizeHandle) &&
resizeHandlesContainerElement.contains(rightResizeHandle)
file.contains(leftResizeHandle) &&
file.contains(rightResizeHandle)
) {
resizeHandlesContainerElement.removeChild(leftResizeHandle);
resizeHandlesContainerElement.removeChild(rightResizeHandle);
file.removeChild(leftResizeHandle);
file.removeChild(rightResizeHandle);
}

return;
Expand Down Expand Up @@ -95,21 +104,21 @@ export const createResizableFileBlockWrapper = (
Math.max(newWidth, minWidth),
editor.domElement?.firstElementChild?.clientWidth || Number.MAX_VALUE,
);
wrapper.style.width = `${width}px`;
fileWithCaption.style.width = `${width}px`;
};
// Stops mouse movements from resizing the element and updates the block's
// `width` prop to the new value.
const windowMouseUpHandler = (event: MouseEvent) => {
// Hides the drag handles if the cursor is no longer over the element.
if (
(!event.target ||
!wrapper.contains(event.target as Node) ||
!file.contains(event.target as Node) ||
!editor.isEditable) &&
resizeHandlesContainerElement.contains(leftResizeHandle) &&
resizeHandlesContainerElement.contains(rightResizeHandle)
file.contains(leftResizeHandle) &&
file.contains(rightResizeHandle)
) {
resizeHandlesContainerElement.removeChild(leftResizeHandle);
resizeHandlesContainerElement.removeChild(rightResizeHandle);
file.removeChild(leftResizeHandle);
file.removeChild(rightResizeHandle);
}

if (!resizeParams) {
Expand All @@ -118,6 +127,10 @@ export const createResizableFileBlockWrapper = (

resizeParams = undefined;

if (file.contains(eventCaptureElement)) {
file.removeChild(eventCaptureElement);
}

editor.updateBlock(block, {
props: {
previewWidth: width,
Expand All @@ -127,15 +140,20 @@ export const createResizableFileBlockWrapper = (

// Shows the resize handles when hovering over the wrapper with the cursor.
const wrapperMouseEnterHandler = () => {
if (resizeParams) {
return;
}

if (editor.isEditable) {
resizeHandlesContainerElement.appendChild(leftResizeHandle);
resizeHandlesContainerElement.appendChild(rightResizeHandle);
file.appendChild(leftResizeHandle);
file.appendChild(rightResizeHandle);
}
};
// Hides the resize handles when the cursor leaves the wrapper, unless the
// cursor moves to one of the resize handles.
const wrapperMouseLeaveHandler = (event: MouseEvent) => {
if (
resizeParams ||
event.relatedTarget === leftResizeHandle ||
event.relatedTarget === rightResizeHandle
) {
Expand All @@ -148,11 +166,11 @@ export const createResizableFileBlockWrapper = (

if (
editor.isEditable &&
resizeHandlesContainerElement.contains(leftResizeHandle) &&
resizeHandlesContainerElement.contains(rightResizeHandle)
file.contains(leftResizeHandle) &&
file.contains(rightResizeHandle)
) {
resizeHandlesContainerElement.removeChild(leftResizeHandle);
resizeHandlesContainerElement.removeChild(rightResizeHandle);
file.removeChild(leftResizeHandle);
file.removeChild(rightResizeHandle);
}
};

Expand All @@ -161,26 +179,34 @@ export const createResizableFileBlockWrapper = (
const leftResizeHandleMouseDownHandler = (event: MouseEvent) => {
event.preventDefault();

if (!file.contains(eventCaptureElement)) {
file.appendChild(eventCaptureElement);
}

resizeParams = {
handleUsed: "left",
initialWidth: wrapper.clientWidth,
initialWidth: fileWithCaption.clientWidth,
initialClientX: event.clientX,
};
};
const rightResizeHandleMouseDownHandler = (event: MouseEvent) => {
event.preventDefault();

if (!file.contains(eventCaptureElement)) {
file.appendChild(eventCaptureElement);
}

resizeParams = {
handleUsed: "right",
initialWidth: wrapper.clientWidth,
initialWidth: fileWithCaption.clientWidth,
initialClientX: event.clientX,
};
};

window.addEventListener("mousemove", windowMouseMoveHandler);
window.addEventListener("mouseup", windowMouseUpHandler);
wrapper.addEventListener("mouseenter", wrapperMouseEnterHandler);
wrapper.addEventListener("mouseleave", wrapperMouseLeaveHandler);
fileWithCaption.addEventListener("mouseenter", wrapperMouseEnterHandler);
fileWithCaption.addEventListener("mouseleave", wrapperMouseLeaveHandler);
leftResizeHandle.addEventListener(
"mousedown",
leftResizeHandleMouseDownHandler,
Expand All @@ -191,13 +217,19 @@ export const createResizableFileBlockWrapper = (
);

return {
dom: wrapper,
dom: fileWithCaption,
destroy: () => {
destroy?.();
window.removeEventListener("mousemove", windowMouseMoveHandler);
window.removeEventListener("mouseup", windowMouseUpHandler);
wrapper.removeEventListener("mouseenter", wrapperMouseEnterHandler);
wrapper.removeEventListener("mouseleave", wrapperMouseLeaveHandler);
fileWithCaption.removeEventListener(
"mouseenter",
wrapperMouseEnterHandler,
);
fileWithCaption.removeEventListener(
"mouseleave",
wrapperMouseLeaveHandler,
);
leftResizeHandle.removeEventListener(
"mousedown",
leftResizeHandleMouseDownHandler,
Expand Down
Loading
Loading