Skip to content

fix: Loading indicator when dropping/pasting files #1069

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

Merged
merged 9 commits into from
Sep 18, 2024
Merged
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
47 changes: 30 additions & 17 deletions packages/core/src/api/parsers/handleFileInsertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,28 +106,22 @@ export async function handleFileInsertion<

const file = items[i].getAsFile();
if (file) {
const updateData = await editor.uploadFile(file);
const fileBlock = {
type: fileBlockType,
props: {
name: file.name,
},
} as PartialBlock<BSchema, I, S>;

const fileBlock =
typeof updateData === "string"
? ({
type: fileBlockType,
props: {
name: file.name,
url: updateData,
},
} as PartialBlock<BSchema, I, S>)
: { type: fileBlockType, ...updateData };
let insertedBlockId: string | undefined = undefined;

if (event.type === "paste") {
editor.insertBlocks(
insertedBlockId = editor.insertBlocks(
[fileBlock],
editor.getTextCursorPosition().block,
"after"
);
}

if (event.type === "drop") {
)[0].id;
} else if (event.type === "drop") {
const coords = {
left: (event as DragEvent).clientX,
top: (event as DragEvent).clientY,
Expand All @@ -143,8 +137,27 @@ export async function handleFileInsertion<
pos.pos
);

editor.insertBlocks([fileBlock], blockInfo.id, "after");
insertedBlockId = editor.insertBlocks(
[fileBlock],
blockInfo.id,
"after"
)[0].id;
} else {
return;
}

const updateData = await editor.uploadFile(file, insertedBlockId);

const updatedFileBlock =
typeof updateData === "string"
? ({
props: {
url: updateData,
},
} as PartialBlock<BSchema, I, S>)
: { ...updateData };

editor.updateBlock(insertedBlockId, updatedFileBlock);
}
}
}
70 changes: 23 additions & 47 deletions packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
import { defaultProps } from "../defaultProps";

import {
createAddFileButton,
createDefaultFilePreview,
createFigureWithCaption,
createFileAndCaptionWrapper,
createFileBlockWrapper,
createLinkWithCaption,
parseFigureElement,
} from "../FileBlockContent/fileBlockHelpers";
Expand Down Expand Up @@ -50,51 +49,28 @@ export const audioRender = (
block: BlockFromConfig<typeof audioBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

if (block.props.url === "") {
const fileBlockAudioIcon = document.createElement("div");
fileBlockAudioIcon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 16.0001H5.88889L11.1834 20.3319C11.2727 20.405 11.3846 20.4449 11.5 20.4449C11.7761 20.4449 12 20.2211 12 19.9449V4.05519C12 3.93977 11.9601 3.8279 11.887 3.73857C11.7121 3.52485 11.3971 3.49335 11.1834 3.66821L5.88889 8.00007H2C1.44772 8.00007 1 8.44778 1 9.00007V15.0001C1 15.5524 1.44772 16.0001 2 16.0001ZM23 12C23 15.292 21.5539 18.2463 19.2622 20.2622L17.8445 18.8444C19.7758 17.1937 21 14.7398 21 12C21 9.26016 19.7758 6.80629 17.8445 5.15557L19.2622 3.73779C21.5539 5.75368 23 8.70795 23 12ZM18 12C18 10.0883 17.106 8.38548 15.7133 7.28673L14.2842 8.71584C15.3213 9.43855 16 10.64 16 12C16 13.36 15.3213 14.5614 14.2842 15.2841L15.7133 16.7132C17.106 15.6145 18 13.9116 18 12Z"></path></svg>';
const addAudioButton = createAddFileButton(
block,
editor,
editor.dictionary.file_blocks.audio.add_button_text,
fileBlockAudioIcon.firstElementChild as HTMLElement
);
wrapper.appendChild(addAudioButton.dom);

return {
dom: wrapper,
destroy: () => {
addAudioButton?.destroy?.();
},
};
} else if (!block.props.showPreview) {
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);

return {
dom: element.dom,
};
} else {
const audio = document.createElement("audio");
audio.className = "bn-audio";
editor.resolveFileUrl(block.props.url).then((downloadUrl) => {
audio.src = downloadUrl;
});
audio.controls = true;
audio.contentEditable = "false";
audio.draggable = false;

const element = createFileAndCaptionWrapper(block, audio);
wrapper.appendChild(element.dom);

return {
dom: wrapper,
};
}
const icon = document.createElement("div");
icon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 16.0001H5.88889L11.1834 20.3319C11.2727 20.405 11.3846 20.4449 11.5 20.4449C11.7761 20.4449 12 20.2211 12 19.9449V4.05519C12 3.93977 11.9601 3.8279 11.887 3.73857C11.7121 3.52485 11.3971 3.49335 11.1834 3.66821L5.88889 8.00007H2C1.44772 8.00007 1 8.44778 1 9.00007V15.0001C1 15.5524 1.44772 16.0001 2 16.0001ZM23 12C23 15.292 21.5539 18.2463 19.2622 20.2622L17.8445 18.8444C19.7758 17.1937 21 14.7398 21 12C21 9.26016 19.7758 6.80629 17.8445 5.15557L19.2622 3.73779C21.5539 5.75368 23 8.70795 23 12ZM18 12C18 10.0883 17.106 8.38548 15.7133 7.28673L14.2842 8.71584C15.3213 9.43855 16 10.64 16 12C16 13.36 15.3213 14.5614 14.2842 15.2841L15.7133 16.7132C17.106 15.6145 18 13.9116 18 12Z"></path></svg>';

const audio = document.createElement("audio");
audio.className = "bn-audio";
editor.resolveFileUrl(block.props.url).then((downloadUrl) => {
audio.src = downloadUrl;
});
audio.controls = true;
audio.contentEditable = "false";
audio.draggable = false;

const element = createFileAndCaptionWrapper(block, audio);

return createFileBlockWrapper(
block,
editor,
element,
editor.dictionary.file_blocks.audio.add_button_text,
icon.firstElementChild as HTMLElement
);
};

export const audioParse = (
Expand Down
26 changes: 4 additions & 22 deletions packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
} from "../../schema";
import { defaultProps } from "../defaultProps";
import {
createAddFileButton,
createDefaultFilePreview,
createFileAndCaptionWrapper,
createFileBlockWrapper,
createLinkWithCaption,
parseEmbedElement,
parseFigureElement,
Expand Down Expand Up @@ -42,28 +42,10 @@ export const fileRender = (
block: BlockFromConfig<typeof fileBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
// Wrapper element to set the file alignment, contains both file/file
// upload dashboard and caption.
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);

if (block.props.url === "") {
const addFileButton = createAddFileButton(block, editor);
wrapper.appendChild(addFileButton.dom);

return {
dom: wrapper,
destroy: addFileButton.destroy,
};
} else {
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);
wrapper.appendChild(element.dom);

return {
dom: wrapper,
};
}
return createFileBlockWrapper(block, editor, element);
};

export const fileParse = (element: HTMLElement) => {
Expand Down
73 changes: 72 additions & 1 deletion packages/core/src/blocks/FileBlockContent/fileBlockHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,76 @@
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { BlockFromConfig, FileBlockConfig } from "../../schema";
import {
BlockFromConfig,
BlockSchemaWithBlock,
FileBlockConfig,
} from "../../schema";

export const createFileBlockWrapper = (
block: BlockFromConfig<FileBlockConfig, any, any>,
editor: BlockNoteEditor<
BlockSchemaWithBlock<FileBlockConfig["type"], FileBlockConfig>,
any,
any
>,
// TODO: Maybe make optional for default preview
element: { dom: HTMLElement; destroy?: () => void },
buttonText?: string,
buttonIcon?: HTMLElement
) => {
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

if (block.props.url === "") {
const addFileButton = createAddFileButton(
block,
editor,
buttonText,
buttonIcon
);
wrapper.appendChild(addFileButton.dom);

const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";

const destroyUploadStartHandler = editor.onUploadStart((blockId) => {
if (blockId === block.id) {
wrapper.removeChild(addFileButton.dom);
wrapper.appendChild(loading);
}
});
const destroyUploadEndHandler = editor.onUploadEnd((blockId) => {
if (blockId === block.id) {
wrapper.removeChild(loading);
wrapper.appendChild(addFileButton.dom);
}
});

return {
dom: wrapper,
destroy: () => {
addFileButton.destroy?.();
destroyUploadStartHandler();
destroyUploadEndHandler();
},
};
} else if (block.props.showPreview === false) {
// TODO: Not using the wrapper element here?
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);

return {
dom: element.dom,
};
} else {
wrapper.appendChild(element.dom);

return {
dom: wrapper,
destroy: element.destroy,
};
}
};

// Default file preview, displaying a file icon and file name.
export const createDefaultFilePreview = (
Expand Down
96 changes: 35 additions & 61 deletions packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import {
PropSchema,
} from "../../schema";
import { defaultProps } from "../defaultProps";

import {
createAddFileButton,
createDefaultFilePreview,
createFigureWithCaption,
createFileAndCaptionWrapper,
createFileBlockWrapper,
createLinkWithCaption,
createResizeHandlesWrapper,
parseFigureElement,
Expand Down Expand Up @@ -56,64 +54,40 @@ export const imageRender = (
block: BlockFromConfig<typeof imageBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

if (block.props.url === "") {
const fileBlockImageIcon = document.createElement("div");
fileBlockImageIcon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 11.1005L7 9.1005L12.5 14.6005L16 11.1005L19 14.1005V5H5V11.1005ZM4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3ZM15.5 10C14.6716 10 14 9.32843 14 8.5C14 7.67157 14.6716 7 15.5 7C16.3284 7 17 7.67157 17 8.5C17 9.32843 16.3284 10 15.5 10Z"></path></svg>';
const addImageButton = createAddFileButton(
block,
editor,
editor.dictionary.file_blocks.image.add_button_text,
fileBlockImageIcon.firstElementChild as HTMLElement
);
wrapper.appendChild(addImageButton.dom);

return {
dom: wrapper,
destroy: () => {
addImageButton?.destroy?.();
},
};
} else if (!block.props.showPreview) {
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);

return {
dom: element.dom,
};
} else {
const image = document.createElement("img");
image.className = "bn-visual-media";
editor.resolveFileUrl(block.props.url).then((downloadUrl) => {
image.src = downloadUrl;
});
image.alt = block.props.name || block.props.caption || "BlockNote image";
image.contentEditable = "false";
image.draggable = false;
image.width = Math.min(
block.props.previewWidth,
editor.domElement.firstElementChild!.clientWidth
);

const file = createResizeHandlesWrapper(
block,
editor,
image,
() => image.width,
(width) => (image.width = width)
);

const element = createFileAndCaptionWrapper(block, file.dom);
wrapper.appendChild(element.dom);

return {
dom: wrapper,
destroy: file.destroy,
};
}
const icon = document.createElement("div");
icon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 11.1005L7 9.1005L12.5 14.6005L16 11.1005L19 14.1005V5H5V11.1005ZM4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3ZM15.5 10C14.6716 10 14 9.32843 14 8.5C14 7.67157 14.6716 7 15.5 7C16.3284 7 17 7.67157 17 8.5C17 9.32843 16.3284 10 15.5 10Z"></path></svg>';

const image = document.createElement("img");
image.className = "bn-visual-media";
editor.resolveFileUrl(block.props.url).then((downloadUrl) => {
image.src = downloadUrl;
});
image.alt = block.props.name || block.props.caption || "BlockNote image";
image.contentEditable = "false";
image.draggable = false;
image.width = Math.min(
block.props.previewWidth,
editor.domElement.firstElementChild!.clientWidth
);

const file = createResizeHandlesWrapper(
block,
editor,
image,
() => image.width,
(width) => (image.width = width)
);

const element = createFileAndCaptionWrapper(block, file.dom);

return createFileBlockWrapper(
block,
editor,
element,
editor.dictionary.file_blocks.image.add_button_text,
icon.firstElementChild as HTMLElement
);
};

export const imageParse = (
Expand Down
Loading
Loading