diff --git a/docs/app/demo/_components/utils.ts b/docs/app/demo/_components/utils.ts index c69c63e3e8..1053616a09 100644 --- a/docs/app/demo/_components/utils.ts +++ b/docs/app/demo/_components/utils.ts @@ -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((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/", - ); } diff --git a/docs/content/docs/react/components/image-toolbar.mdx b/docs/content/docs/react/components/image-toolbar.mdx index e76c47a1a7..5daebf5f1d 100644 --- a/docs/content/docs/react/components/image-toolbar.mdx +++ b/docs/content/docs/react/components/image-toolbar.mdx @@ -29,7 +29,7 @@ type uploadFile = (file: File) => Promise; `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: diff --git a/examples/01-basic/testing/src/App.tsx b/examples/01-basic/testing/src/App.tsx index 353e445e91..7868ed3384 100644 --- a/examples/01-basic/testing/src/App.tsx +++ b/examples/01-basic/testing/src/App.tsx @@ -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((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. diff --git a/examples/02-backend/01-file-uploading/README.md b/examples/02-backend/01-file-uploading/README.md index 7c471cf83a..3c3c0ede8b 100644 --- a/examples/02-backend/01-file-uploading/README.md +++ b/examples/02-backend/01-file-uploading/README.md @@ -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! diff --git a/examples/02-backend/01-file-uploading/src/App.tsx b/examples/02-backend/01-file-uploading/src/App.tsx index 982d0f37a6..c11a95e1b4 100644 --- a/examples/02-backend/01-file-uploading/src/App.tsx +++ b/examples/02-backend/01-file-uploading/src/App.tsx @@ -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((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() { diff --git a/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx b/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx index 6d8ad0a067..7b1ba3e100 100644 --- a/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx +++ b/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx @@ -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 @@ -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> = ( - file, - response, + // Listen for completed Dashboard uploads, then update the Block with the + // uploaded file's URL. + const handler: UploadCompleteCallback> = 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]); @@ -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((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); - } } diff --git a/packages/core/src/blocks/File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.ts b/packages/core/src/blocks/File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.ts deleted file mode 100644 index ab0a686e82..0000000000 --- a/packages/core/src/blocks/File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Uploads a file to tmpfiles.org and returns the URL to the uploaded file. - * - * @warning This function should only be used for development purposes, replace with your own backend! - */ -export const uploadToTmpFilesDotOrg_DEV_ONLY = async ( - file: File, -): Promise => { - const body = new FormData(); - body.append("file", file); - - const ret = await fetch("https://tmpfiles.org/api/v1/upload", { - method: "POST", - body: body, - }); - return (await ret.json()).data.url.replace( - "tmpfiles.org/", - "tmpfiles.org/dl/", - ); -}; diff --git a/packages/core/src/blocks/index.ts b/packages/core/src/blocks/index.ts index d40bba055c..76fc76d8a7 100644 --- a/packages/core/src/blocks/index.ts +++ b/packages/core/src/blocks/index.ts @@ -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"; diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 155a460786..fc95039f22 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -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", diff --git a/tests/src/end-to-end/images/images.test.tsx b/tests/src/end-to-end/images/images.test.tsx index 67ded55285..52bc073ac2 100644 --- a/tests/src/end-to-end/images/images.test.tsx +++ b/tests/src/end-to-end/images/images.test.tsx @@ -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`)); diff --git a/tests/src/unit/core/createTestEditor.ts b/tests/src/unit/core/createTestEditor.ts index 26c9324f91..71b7866059 100644 --- a/tests/src/unit/core/createTestEditor.ts +++ b/tests/src/unit/core/createTestEditor.ts @@ -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((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, @@ -57,7 +66,7 @@ export const createTestEditor = < headers: true, }, trailingBlock: false, - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, + uploadFile, }) as any; editor.mount(div); });