Skip to content

Commit 571af73

Browse files
committed
Updated resizable file wrappers
1 parent 28d808b commit 571af73

File tree

16 files changed

+173
-146
lines changed

16 files changed

+173
-146
lines changed

examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { FileBlockConfig } from "@blocknote/core";
22
import {
33
createReactBlockSpec,
44
ReactCustomBlockRenderProps,
5-
ResizableFileBlockWrapper,
5+
ResizableFileWithCaption,
66
} from "@blocknote/react";
77

88
import { RiFilePdfFill } from "react-icons/ri";
@@ -14,17 +14,15 @@ export const PDFPreview = (
1414
ReactCustomBlockRenderProps<FileBlockConfig, any, any>,
1515
"contentRef"
1616
>,
17-
) => {
18-
return (
19-
<embed
20-
type={"application/pdf"}
21-
src={props.block.props.url}
22-
contentEditable={false}
23-
draggable={false}
24-
onClick={() => props.editor.setTextCursorPosition(props.block)}
25-
/>
26-
);
27-
};
17+
) => (
18+
<embed
19+
type={"application/pdf"}
20+
src={props.block.props.url}
21+
contentEditable={false}
22+
draggable={false}
23+
onClick={() => props.editor.setTextCursorPosition(props.block)}
24+
/>
25+
);
2826

2927
export const PDF = createReactBlockSpec(
3028
{
@@ -52,13 +50,13 @@ export const PDF = createReactBlockSpec(
5250
},
5351
{
5452
render: (props) => (
55-
<ResizableFileBlockWrapper
53+
<ResizableFileWithCaption
5654
{...(props as any)}
57-
bbuttonText={"Add PDF"}
55+
buttonText={"Add PDF"}
5856
buttonIcon={<RiFilePdfFill size={24} />}
5957
>
6058
<PDFPreview {...(props as any)} />
61-
</ResizableFileBlockWrapper>
59+
</ResizableFileWithCaption>
6260
),
6361
},
6462
);

packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { defaultProps } from "../defaultProps.js";
1010

1111
import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
12-
import { createFileBlockWrapper } from "../FileBlockContent/helpers/render/createFileBlockWrapper.js";
12+
import { createFileWithCaption } from "../FileBlockContent/helpers/render/createFileWithCaption.js";
1313
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
1414
import { createLinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js";
1515
import { parseAudioElement } from "./parseAudioElement.js";
@@ -65,10 +65,10 @@ export const audioRender = (
6565
audio.contentEditable = "false";
6666
audio.draggable = false;
6767

68-
return createFileBlockWrapper(
68+
return createFileWithCaption(
6969
block,
7070
editor,
71-
{ dom: audio },
71+
audio,
7272
editor.dictionary.file_blocks.audio.add_button_text,
7373
icon.firstElementChild as HTMLElement,
7474
);

packages/core/src/blocks/FileBlockContent/FileBlockContent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { defaultProps } from "../defaultProps.js";
99
import { parseEmbedElement } from "./helpers/parse/parseEmbedElement.js";
1010
import { parseFigureElement } from "./helpers/parse/parseFigureElement.js";
11-
import { createFileBlockWrapper } from "./helpers/render/createFileBlockWrapper.js";
11+
import { createFileWithCaption } from "./helpers/render/createFileWithCaption.js";
1212
import { createLinkWithCaption } from "./helpers/toExternalHTML/createLinkWithCaption.js";
1313

1414
export const filePropSchema = {
@@ -38,7 +38,7 @@ export const fileRender = (
3838
block: BlockFromConfig<typeof fileBlockConfig, any, any>,
3939
editor: BlockNoteEditor<any, any, any>,
4040
) => {
41-
return createFileBlockWrapper(block, editor);
41+
return createFileWithCaption(block, editor);
4242
};
4343

4444
export const fileParse = (element: HTMLElement) => {

packages/core/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.ts renamed to packages/core/src/blocks/FileBlockContent/helpers/render/createFileWithCaption.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@ import {
77
import { createAddFileButton } from "./createAddFileButton.js";
88
import { createFileNameWithIcon } from "./createFileNameWithIcon.js";
99

10-
export const createFileBlockWrapper = (
10+
export const createFileWithCaption = (
1111
block: BlockFromConfig<FileBlockConfig, any, any>,
1212
editor: BlockNoteEditor<
1313
BlockSchemaWithBlock<FileBlockConfig["type"], FileBlockConfig>,
1414
any,
1515
any
1616
>,
17-
element?: { dom: HTMLElement; destroy?: () => void },
17+
element?: HTMLElement,
1818
buttonText?: string,
1919
buttonIcon?: HTMLElement,
2020
) => {
21-
const wrapper = document.createElement("div");
22-
wrapper.className = "bn-file-block-content-wrapper";
21+
const fileWithCaption = document.createElement("div");
22+
fileWithCaption.className = "bn-file-with-caption";
23+
24+
const file = document.createElement("div");
25+
file.className = "bn-file";
26+
fileWithCaption.appendChild(file);
2327

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

3538
const destroyUploadStartHandler = editor.onUploadStart((blockId) => {
3639
if (blockId === block.id) {
37-
wrapper.removeChild(addFileButton.dom);
38-
3940
const loading = document.createElement("div");
4041
loading.className = "bn-file-loading-preview";
4142
loading.textContent = "Loading...";
42-
wrapper.appendChild(loading);
43+
addFileButton.dom.replaceWith(loading);
4344
}
4445
});
4546

4647
return {
47-
dom: wrapper,
48+
dom: addFileButton.dom,
4849
destroy: () => {
4950
destroyUploadStartHandler();
50-
addFileButton.destroy();
51+
addFileButton.destroy?.();
5152
},
5253
};
5354
}
5455

55-
const ret: { dom: HTMLElement; destroy?: () => void } = { dom: wrapper };
56+
const ret: { dom: HTMLElement; destroy?: () => void } = {
57+
dom: fileWithCaption,
58+
};
5659

5760
// Show the file preview, or the file name and icon.
5861
if (block.props.showPreview === false || !element) {
5962
// Show file name and icon.
6063
const fileNameWithIcon = createFileNameWithIcon(block);
61-
wrapper.appendChild(fileNameWithIcon.dom);
64+
file.appendChild(fileNameWithIcon.dom);
6265

6366
ret.destroy = () => {
6467
fileNameWithIcon.destroy?.();
6568
};
6669
} else {
6770
// Show file preview.
68-
wrapper.appendChild(element.dom);
71+
file.appendChild(element);
6972
}
7073

7174
// Show the caption if there is one.
7275
if (block.props.caption) {
7376
const caption = document.createElement("p");
7477
caption.className = "bn-file-caption";
7578
caption.textContent = block.props.caption;
76-
wrapper.appendChild(caption);
79+
fileWithCaption.appendChild(caption);
7780
}
7881

7982
return ret;
Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
22
import { BlockFromConfig, FileBlockConfig } from "../../../../schema/index.js";
3-
import { createFileBlockWrapper } from "./createFileBlockWrapper.js";
3+
import { createFileWithCaption } from "./createFileWithCaption.js";
44

5-
export const createResizableFileBlockWrapper = (
5+
export const createResizableFileWithCaption = (
66
block: BlockFromConfig<FileBlockConfig, any, any>,
77
editor: BlockNoteEditor<any, any, any>,
8-
element: { dom: HTMLElement; destroy?: () => void },
9-
resizeHandlesContainerElement: HTMLElement,
8+
element: HTMLElement,
109
buttonText: string,
1110
buttonIcon: HTMLElement,
1211
): { dom: HTMLElement; destroy: () => void } => {
13-
const { dom, destroy } = createFileBlockWrapper(
12+
const { dom, destroy } = createFileWithCaption(
1413
block,
1514
editor,
1615
element,
1716
buttonText,
1817
buttonIcon,
1918
);
20-
const wrapper = dom;
19+
const fileWithCaption = dom;
2120
if (block.props.url && block.props.showPreview) {
2221
if (block.props.previewWidth) {
23-
wrapper.style.width = `${block.props.previewWidth}px`;
22+
fileWithCaption.style.width = `${block.props.previewWidth}px`;
2423
} else {
25-
wrapper.style.width = "fit-content";
24+
fileWithCaption.style.width = "fit-content";
2625
}
2726
}
27+
const file = fileWithCaption.querySelector(".bn-file") as HTMLElement;
2828

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

36+
// This element ensures `mousemove` and `mouseup` events are captured while
37+
// resizing when the cursor is over the wrapper content. This is because
38+
// embeds are treated as separate HTML documents, so if the content is an
39+
// embed, the events will only fire within that document.
40+
const eventCaptureElement = document.createElement("div");
41+
eventCaptureElement.style.position = "absolute";
42+
eventCaptureElement.style.height = "100%";
43+
eventCaptureElement.style.width = "100%";
44+
3645
// Temporary parameters set when the user begins resizing the element, used to
3746
// calculate the new width of the element.
3847
let resizeParams:
@@ -50,11 +59,11 @@ export const createResizableFileBlockWrapper = (
5059
if (!resizeParams) {
5160
if (
5261
!editor.isEditable &&
53-
resizeHandlesContainerElement.contains(leftResizeHandle) &&
54-
resizeHandlesContainerElement.contains(rightResizeHandle)
62+
file.contains(leftResizeHandle) &&
63+
file.contains(rightResizeHandle)
5564
) {
56-
resizeHandlesContainerElement.removeChild(leftResizeHandle);
57-
resizeHandlesContainerElement.removeChild(rightResizeHandle);
65+
file.removeChild(leftResizeHandle);
66+
file.removeChild(rightResizeHandle);
5867
}
5968

6069
return;
@@ -95,21 +104,21 @@ export const createResizableFileBlockWrapper = (
95104
Math.max(newWidth, minWidth),
96105
editor.domElement?.firstElementChild?.clientWidth || Number.MAX_VALUE,
97106
);
98-
wrapper.style.width = `${width}px`;
107+
fileWithCaption.style.width = `${width}px`;
99108
};
100109
// Stops mouse movements from resizing the element and updates the block's
101110
// `width` prop to the new value.
102111
const windowMouseUpHandler = (event: MouseEvent) => {
103112
// Hides the drag handles if the cursor is no longer over the element.
104113
if (
105114
(!event.target ||
106-
!wrapper.contains(event.target as Node) ||
115+
!file.contains(event.target as Node) ||
107116
!editor.isEditable) &&
108-
resizeHandlesContainerElement.contains(leftResizeHandle) &&
109-
resizeHandlesContainerElement.contains(rightResizeHandle)
117+
file.contains(leftResizeHandle) &&
118+
file.contains(rightResizeHandle)
110119
) {
111-
resizeHandlesContainerElement.removeChild(leftResizeHandle);
112-
resizeHandlesContainerElement.removeChild(rightResizeHandle);
120+
file.removeChild(leftResizeHandle);
121+
file.removeChild(rightResizeHandle);
113122
}
114123

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

119128
resizeParams = undefined;
120129

130+
if (file.contains(eventCaptureElement)) {
131+
file.removeChild(eventCaptureElement);
132+
}
133+
121134
editor.updateBlock(block, {
122135
props: {
123136
previewWidth: width,
@@ -127,15 +140,20 @@ export const createResizableFileBlockWrapper = (
127140

128141
// Shows the resize handles when hovering over the wrapper with the cursor.
129142
const wrapperMouseEnterHandler = () => {
143+
if (resizeParams) {
144+
return;
145+
}
146+
130147
if (editor.isEditable) {
131-
resizeHandlesContainerElement.appendChild(leftResizeHandle);
132-
resizeHandlesContainerElement.appendChild(rightResizeHandle);
148+
file.appendChild(leftResizeHandle);
149+
file.appendChild(rightResizeHandle);
133150
}
134151
};
135152
// Hides the resize handles when the cursor leaves the wrapper, unless the
136153
// cursor moves to one of the resize handles.
137154
const wrapperMouseLeaveHandler = (event: MouseEvent) => {
138155
if (
156+
resizeParams ||
139157
event.relatedTarget === leftResizeHandle ||
140158
event.relatedTarget === rightResizeHandle
141159
) {
@@ -148,11 +166,11 @@ export const createResizableFileBlockWrapper = (
148166

149167
if (
150168
editor.isEditable &&
151-
resizeHandlesContainerElement.contains(leftResizeHandle) &&
152-
resizeHandlesContainerElement.contains(rightResizeHandle)
169+
file.contains(leftResizeHandle) &&
170+
file.contains(rightResizeHandle)
153171
) {
154-
resizeHandlesContainerElement.removeChild(leftResizeHandle);
155-
resizeHandlesContainerElement.removeChild(rightResizeHandle);
172+
file.removeChild(leftResizeHandle);
173+
file.removeChild(rightResizeHandle);
156174
}
157175
};
158176

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

182+
if (!file.contains(eventCaptureElement)) {
183+
file.appendChild(eventCaptureElement);
184+
}
185+
164186
resizeParams = {
165187
handleUsed: "left",
166-
initialWidth: wrapper.clientWidth,
188+
initialWidth: fileWithCaption.clientWidth,
167189
initialClientX: event.clientX,
168190
};
169191
};
170192
const rightResizeHandleMouseDownHandler = (event: MouseEvent) => {
171193
event.preventDefault();
172194

195+
if (!file.contains(eventCaptureElement)) {
196+
file.appendChild(eventCaptureElement);
197+
}
198+
173199
resizeParams = {
174200
handleUsed: "right",
175-
initialWidth: wrapper.clientWidth,
201+
initialWidth: fileWithCaption.clientWidth,
176202
initialClientX: event.clientX,
177203
};
178204
};
179205

180206
window.addEventListener("mousemove", windowMouseMoveHandler);
181207
window.addEventListener("mouseup", windowMouseUpHandler);
182-
wrapper.addEventListener("mouseenter", wrapperMouseEnterHandler);
183-
wrapper.addEventListener("mouseleave", wrapperMouseLeaveHandler);
208+
fileWithCaption.addEventListener("mouseenter", wrapperMouseEnterHandler);
209+
fileWithCaption.addEventListener("mouseleave", wrapperMouseLeaveHandler);
184210
leftResizeHandle.addEventListener(
185211
"mousedown",
186212
leftResizeHandleMouseDownHandler,
@@ -191,13 +217,19 @@ export const createResizableFileBlockWrapper = (
191217
);
192218

193219
return {
194-
dom: wrapper,
220+
dom: fileWithCaption,
195221
destroy: () => {
196222
destroy?.();
197223
window.removeEventListener("mousemove", windowMouseMoveHandler);
198224
window.removeEventListener("mouseup", windowMouseUpHandler);
199-
wrapper.removeEventListener("mouseenter", wrapperMouseEnterHandler);
200-
wrapper.removeEventListener("mouseleave", wrapperMouseLeaveHandler);
225+
fileWithCaption.removeEventListener(
226+
"mouseenter",
227+
wrapperMouseEnterHandler,
228+
);
229+
fileWithCaption.removeEventListener(
230+
"mouseleave",
231+
wrapperMouseLeaveHandler,
232+
);
201233
leftResizeHandle.removeEventListener(
202234
"mousedown",
203235
leftResizeHandleMouseDownHandler,

0 commit comments

Comments
 (0)