Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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%";

// 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