Skip to content
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
19 changes: 8 additions & 11 deletions docs/app/demo/_components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ export async function resolveUsers(userIds: string[]) {
return HARDCODED_USERS.filter((user) => userIds.includes(user.id));
}

// Uploads a file to tmpfiles.org and returns the URL to the uploaded file.
// "Uploads" a file by encoding it as a base64 data URL. In a real app you'd
// replace this with an upload to your own backend that returns a URL to the
// stored file.
export async function uploadFile(file: File) {
const body = new FormData();
body.append("file", file);

const ret = await fetch("https://tmpfiles.org/api/v1/upload", {
method: "POST",
body: body,
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
return (await ret.json()).data.url.replace(
"tmpfiles.org/",
"tmpfiles.org/dl/",
);
}
2 changes: 1 addition & 1 deletion docs/content/docs/react/components/image-toolbar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type uploadFile = (file: File) => Promise<string>;

`returns:` A `Promise`, which resolves to the URL that the image can be accessed at.

You can use the provided `uploadToTempFilesOrg` function to as a starting point, which uploads files to [tmpfiles.org](https://tmpfiles.org/). However, it's not recommended to use this in a production environment - you should use your own backend:
The example below encodes files as base64 data URLs as a starting point. However, this is only meant for development - in production you should use your own backend:

<Example name="backend/file-uploading" />

Expand Down
15 changes: 13 additions & 2 deletions examples/01-basic/testing/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";

// "Uploads" a file by encoding it as a base64 data URL. In a real app you'd
// replace this with an upload to your own backend that returns a URL to the
// stored file.
async function uploadFile(file: File) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY,
uploadFile,
});

// Renders the editor instance using a React component.
Expand Down
2 changes: 1 addition & 1 deletion examples/02-backend/01-file-uploading/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Upload Files

This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used for File, Image, Video, and Audio blocks.
This example allows users to upload files and use them in the editor. For simplicity, files are encoded as data URLs rather than uploaded to a server, but you'd swap the `uploadFile` function for an upload to your own backend. The uploaded files can be used for File, Image, Video, and Audio blocks.

**Try it out:** Click the "Add Image" button and see there's now an "Upload" tab in the toolbar!

Expand Down
21 changes: 10 additions & 11 deletions examples/02-backend/01-file-uploading/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";

// Uploads a file to tmpfiles.org and returns the URL to the uploaded file.
// "Uploads" a file by encoding it as a base64 data URL. We add a short delay
// first to simulate the latency of a real server upload. In a real app you'd
// replace this with an upload to your own backend that returns a URL to the
// stored file.
async function uploadFile(file: File) {
const body = new FormData();
body.append("file", file);

const ret = await fetch("https://tmpfiles.org/api/v1/upload", {
method: "POST",
body: body,
await new Promise((resolve) => setTimeout(resolve, 1000));
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
return (await ret.json()).data.url.replace(
"tmpfiles.org/",
"tmpfiles.org/dl/",
);
}

export default function App() {
Expand Down
76 changes: 25 additions & 51 deletions examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { FilePanelProps, useBlockNoteEditor } from "@blocknote/react";
import Uppy, { UploadSuccessCallback } from "@uppy/core";
import Uppy, { UploadCompleteCallback } from "@uppy/core";
import "@uppy/core/dist/style.min.css";
import "@uppy/dashboard/dist/style.min.css";
import { Dashboard } from "@uppy/react";
import XHR from "@uppy/xhr-upload";
import { useEffect } from "react";

// Image editor plugin
Expand All @@ -25,58 +24,38 @@ const uppy = new Uppy()
// Instagram Dropbox etc.
.use(Webcam)
.use(ScreenCapture)
.use(ImageEditor)
.use(ImageEditor);

// Uses an XHR upload plugin to upload files to tmpfiles.org.
// You want to replace this with your own upload endpoint or Uppy Companion
// server.
.use(XHR, {
endpoint: "https://tmpfiles.org/api/v1/upload",
getResponseData(text, _resp) {
return {
url: JSON.parse(text).data.url.replace(
"tmpfiles.org/",
"tmpfiles.org/dl/",
),
};
},
});
// No uploader plugin is registered: for this demo we "upload" files with
// BlockNote's dev-only helper, which encodes them as base64 data URLs. In a real
// app you'd add an uploader like `@uppy/xhr-upload` pointing at your own backend
// or Uppy Companion server.

export function UppyFilePanel(props: FilePanelProps) {
const { blockId } = props;
const editor = useBlockNoteEditor();

useEffect(() => {
// Listen for successful tippy uploads, and then update the Block with the
// uploaded URL.
const handler: UploadSuccessCallback<Record<string, unknown>> = (
file,
response,
// Listen for completed Dashboard uploads, then update the Block with the
// uploaded file's URL.
const handler: UploadCompleteCallback<Record<string, unknown>> = async (
result,
) => {
if (!file) {
return;
}

if (file.source === "uploadFile") {
// Didn't originate from Dashboard, should be handled by `uploadFile`
return;
}
if (response.status === 200) {
const updateData = {
for (const file of result.successful) {
editor.updateBlock(blockId, {
props: {
name: file?.name,
url: response.uploadURL,
name: file.name,
url: await uploadFile(file.data as File),
},
};
editor.updateBlock(blockId, updateData);
});

// File should be removed from the Uppy instance after upload.
uppy.removeFile(file.id);
}
};
uppy.on("upload-success", handler);
uppy.on("complete", handler);
return () => {
uppy.off("upload-success", handler);
uppy.off("complete", handler);
};
}, [blockId, editor]);

Expand All @@ -86,19 +65,14 @@ export function UppyFilePanel(props: FilePanelProps) {

// Implementation for the BlockNote `uploadFile` function.
// This function is used when for example, files are dropped into the editor.
// It "uploads" a file by encoding it as a base64 data URL. In a real app you'd
// replace this with an upload to your own backend that returns a URL to the
// stored file.
export async function uploadFile(file: File) {
const id = uppy.addFile({
id: file.name,
name: file.name,
type: file.type,
data: file,
source: "uploadFile",
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});

try {
const result = await uppy.upload();
return result.successful[0].response!.uploadURL!;
} finally {
uppy.removeFile(id);
}
}

This file was deleted.

1 change: 0 additions & 1 deletion packages/core/src/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export * from "./Code/helpers/parse/parsePreCode.js";
export * from "./Code/helpers/render/createCodeBlock.js";
export * from "./Code/helpers/toExternalHTML/createPreCode.js";
export * from "./ToggleWrapper/createToggleWrapper.js";
export * from "./File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.js";
export * from "./PageBreak/getPageBreakSlashMenuItems.js";

export * from "./BlockNoteSchema.js";
Expand Down
2 changes: 1 addition & 1 deletion playground/src/examples.gen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export const examples = {
slug: "backend",
},
readme:
'This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the "Add Image" button and see there\'s now an "Upload" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)',
'This example allows users to upload files and use them in the editor. For simplicity, files are encoded as data URLs rather than uploaded to a server, but you\'d swap the `uploadFile` function for an upload to your own backend. The uploaded files can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the "Add Image" button and see there\'s now an "Upload" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)',
},
{
projectSlug: "saving-loading",
Expand Down
2 changes: 1 addition & 1 deletion tests/src/end-to-end/images/images.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("Check Image Block and Toolbar functionality", () => {
type: "image/png",
});
await userEvent.upload(uploadInput, file);
await waitForSelector(`img[src^="https://tmpfiles.org/"]`);
await waitForSelector(`img[src^="data:"]`);
await sleep(500);

await userEvent.click(await waitForSelector(`img`));
Expand Down
13 changes: 11 additions & 2 deletions tests/src/unit/core/createTestEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ import {
createCodeBlockSpec,
InlineContentSchema,
StyleSchema,
uploadToTmpFilesDotOrg_DEV_ONLY,
} from "@blocknote/core";
import { afterAll, beforeAll } from "vite-plus/test";

// "Uploads" a file by encoding it as a base64 data URL.
async function uploadFile(file: File) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}

export const createTestEditor = <
B extends BlockSchema,
I extends InlineContentSchema,
Expand Down Expand Up @@ -57,7 +66,7 @@ export const createTestEditor = <
headers: true,
},
trailingBlock: false,
uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY,
uploadFile,
}) as any;
editor.mount(div);
});
Expand Down
Loading