From b8a474b38050a8ac217b52a6c1dfe06060a2c77a Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 22 Sep 2023 17:31:15 +0200 Subject: [PATCH 1/3] small fixes --- packages/core/src/BlockNoteEditor.ts | 47 +++++++++---------- .../nodeConversions/nodeConversions.test.ts | 8 ++-- .../api/nodeConversions/nodeConversions.ts | 2 +- .../DefaultButtons/ReplaceImageButton.tsx | 19 ++++---- .../DefaultButtons/TextAlignButton.tsx | 10 +--- .../DefaultButtons/ToggledStyleButton.tsx | 12 ++--- .../components/DefaultImageToolbar.tsx | 23 +++++---- 7 files changed, 51 insertions(+), 70 deletions(-) diff --git a/packages/core/src/BlockNoteEditor.ts b/packages/core/src/BlockNoteEditor.ts index f7d468347e..a48039d0f5 100644 --- a/packages/core/src/BlockNoteEditor.ts +++ b/packages/core/src/BlockNoteEditor.ts @@ -11,9 +11,9 @@ import { updateBlock, } from "./api/blockManipulation/blockManipulation"; import { + HTMLToBlocks, blocksToHTML, blocksToMarkdown, - HTMLToBlocks, markdownToBlocks, } from "./api/formatConversions/formatConversions"; import { @@ -44,15 +44,13 @@ import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFro import { FormattingToolbarProsemirrorPlugin } from "./extensions/FormattingToolbar/FormattingToolbarPlugin"; import { HyperlinkToolbarProsemirrorPlugin } from "./extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; +import { ImageToolbarProsemirrorPlugin } from "./extensions/ImageToolbar/ImageToolbarPlugin"; import { SideMenuProsemirrorPlugin } from "./extensions/SideMenu/SideMenuPlugin"; import { BaseSlashMenuItem } from "./extensions/SlashMenu/BaseSlashMenuItem"; import { SlashMenuProsemirrorPlugin } from "./extensions/SlashMenu/SlashMenuPlugin"; import { getDefaultSlashMenuItems } from "./extensions/SlashMenu/defaultSlashMenuItems"; import { UniqueID } from "./extensions/UniqueID/UniqueID"; import { mergeCSSClasses } from "./shared/utils"; -import { ImageToolbarProsemirrorPlugin } from "./extensions/ImageToolbar/ImageToolbarPlugin"; - -const IMAGE_UPLOAD_API_KEY = ""; export type BlockNoteEditorOptions = { // TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them. @@ -114,7 +112,7 @@ export type BlockNoteEditorOptions = { * @param file The image file that should be uploaded. * @returns The URL of the uploaded image. */ - uploadImage: (file: File) => Promise; + uploadFile: (file: File) => Promise; /** * When enabled, allows for collaboration between multiple users. @@ -163,7 +161,7 @@ export class BlockNoteEditor { public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin; public readonly imageToolbar: ImageToolbarProsemirrorPlugin; - public readonly imageUpload: (file: File) => Promise; + public readonly uploadFile: (file: File) => Promise; constructor( private readonly options: Partial> = {} @@ -217,25 +215,24 @@ export class BlockNoteEditor { this.schema = newOptions.blockSchema; - this.imageUpload = async (file) => { - if (newOptions.uploadImage) { - return newOptions.uploadImage(file); - } - - // TODO: Proper backend - right now using imgbb.com since you can get an - // API key for free by just making an account. - // https://imgbb.com/ - const body = new FormData(); - body.append("key", IMAGE_UPLOAD_API_KEY); - body.append("image", file); - - return fetch("https://api.imgbb.com/1/upload?expiration=600", { - method: "POST", - body: body, - }) - .then((response) => response.json()) - .then((data) => data.data.url); - }; + this.uploadFile = + newOptions.uploadFile || + (async (file) => { + // TODO: Proper backend - right now using imgbb.com since you can get an + // API key for free by just making an account. + // https://imgbb.com/ + 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/" + ); + }); const initialContent = newOptions.initialContent || diff --git a/packages/core/src/api/nodeConversions/nodeConversions.test.ts b/packages/core/src/api/nodeConversions/nodeConversions.test.ts index f5cf430b8a..37bdadfdb9 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.test.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.test.ts @@ -49,8 +49,8 @@ describe("Simple ProseMirror Node Conversions", () => { expect(firstBlockConversion).toMatchSnapshot(); - const firstNodeConversion = blockToNode( - firstBlockConversion as any, + const firstNodeConversion = blockToNode( + firstBlockConversion, tt.schema ); @@ -150,8 +150,8 @@ describe("Complex ProseMirror Node Conversions", () => { expect(firstBlockConversion).toMatchSnapshot(); - const firstNodeConversion = blockToNode( - firstBlockConversion as any, + const firstNodeConversion = blockToNode( + firstBlockConversion, tt.schema ); diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts index 6be5ec9cf3..9241ac5aec 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.ts @@ -15,9 +15,9 @@ import { Styles, ToggledStyle, } from "../../extensions/Blocks/api/inlineContentTypes"; +import { getBlockInfo } from "../../extensions/Blocks/helpers/getBlockInfoFromPos"; import UniqueID from "../../extensions/UniqueID/UniqueID"; import { UnreachableCaseError } from "../../shared/utils"; -import { getBlockInfo } from "../../extensions/Blocks/helpers/getBlockInfoFromPos"; const toggleStyles = new Set([ "bold", diff --git a/packages/react/src/FormattingToolbar/components/DefaultButtons/ReplaceImageButton.tsx b/packages/react/src/FormattingToolbar/components/DefaultButtons/ReplaceImageButton.tsx index 7813e750a9..2169e3ff7e 100644 --- a/packages/react/src/FormattingToolbar/components/DefaultButtons/ReplaceImageButton.tsx +++ b/packages/react/src/FormattingToolbar/components/DefaultButtons/ReplaceImageButton.tsx @@ -1,11 +1,11 @@ import { BlockNoteEditor, BlockSchema } from "@blocknote/core"; -import { useEffect, useMemo, useState } from "react"; -import { RiImageEditFill } from "react-icons/ri"; import Tippy from "@tippyjs/react"; +import { useEffect, useState } from "react"; +import { RiImageEditFill } from "react-icons/ri"; +import { DefaultImageToolbar } from "../../../ImageToolbar/components/DefaultImageToolbar"; import { ToolbarButton } from "../../../SharedComponents/Toolbar/components/ToolbarButton"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; -import { DefaultImageToolbar } from "../../../ImageToolbar/components/DefaultImageToolbar"; export const ReplaceImageButton = (props: { editor: BlockNoteEditor; @@ -18,14 +18,11 @@ export const ReplaceImageButton = (props: { setIsOpen(false); }, [selectedBlocks]); - const show = useMemo( - () => - // Checks if only one block is selected. - selectedBlocks.length === 1 && - // Checks if the selected block is an image. - selectedBlocks[0].type === "image", - [selectedBlocks] - ); + const show = + // Checks if only one block is selected. + selectedBlocks.length === 1 && + // Checks if the selected block is an image. + selectedBlocks[0].type === "image"; if (!show) { return null; diff --git a/packages/react/src/FormattingToolbar/components/DefaultButtons/TextAlignButton.tsx b/packages/react/src/FormattingToolbar/components/DefaultButtons/TextAlignButton.tsx index 6e9cb6a98e..cf89ecef97 100644 --- a/packages/react/src/FormattingToolbar/components/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/FormattingToolbar/components/DefaultButtons/TextAlignButton.tsx @@ -1,10 +1,10 @@ -import { useCallback, useMemo } from "react"; import { BlockNoteEditor, BlockSchema, DefaultProps, PartialBlock, } from "@blocknote/core"; +import { useCallback, useMemo } from "react"; import { IconType } from "react-icons"; import { RiAlignCenter, @@ -55,13 +55,7 @@ export const TextAlignButton = (props: { ); const show = useMemo(() => { - for (const block of selectedBlocks) { - if ("textAlignment" in block.props) { - return true; - } - } - - return false; + return !!selectedBlocks.find((block) => "textAlignment" in block.props); }, [selectedBlocks]); if (!show) { diff --git a/packages/react/src/FormattingToolbar/components/DefaultButtons/ToggledStyleButton.tsx b/packages/react/src/FormattingToolbar/components/DefaultButtons/ToggledStyleButton.tsx index e8a9647436..c71824e873 100644 --- a/packages/react/src/FormattingToolbar/components/DefaultButtons/ToggledStyleButton.tsx +++ b/packages/react/src/FormattingToolbar/components/DefaultButtons/ToggledStyleButton.tsx @@ -1,5 +1,5 @@ -import { useMemo, useState } from "react"; import { BlockNoteEditor, BlockSchema, ToggledStyle } from "@blocknote/core"; +import { useMemo, useState } from "react"; import { IconType } from "react-icons"; import { RiBold, @@ -10,8 +10,8 @@ import { } from "react-icons/ri"; import { ToolbarButton } from "../../../SharedComponents/Toolbar/components/ToolbarButton"; -import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; import { useEditorChange } from "../../../hooks/useEditorChange"; +import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; import { formatKeyboardShortcut } from "../../../utils"; const shortcuts: Record = { @@ -50,13 +50,7 @@ export const ToggledStyleButton = (props: { }; const show = useMemo(() => { - for (const block of selectedBlocks) { - if (block.content !== undefined) { - return true; - } - } - - return false; + return !!selectedBlocks.find((block) => block.content !== undefined); }, [selectedBlocks]); if (!show) { diff --git a/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx b/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx index 4db888f013..3b81307a79 100644 --- a/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx +++ b/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx @@ -1,9 +1,9 @@ import { BlockSchema, PartialBlock } from "@blocknote/core"; -import { ImageToolbarProps } from "./ImageToolbarPositioner"; -import { Toolbar } from "../../SharedComponents/Toolbar/components/Toolbar"; -import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; import { Button, FileInput, Tabs, TextInput } from "@mantine/core"; +import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; +import { Toolbar } from "../../SharedComponents/Toolbar/components/Toolbar"; +import { ImageToolbarProps } from "./ImageToolbarPositioner"; export const DefaultImageToolbar = ( props: ImageToolbarProps @@ -11,15 +11,14 @@ export const DefaultImageToolbar = ( const [openTab, setOpenTab] = useState<"upload" | "embed">("upload"); const handleFileChange = useCallback( - (file: File) => { - props.editor.imageUpload(file).then((src) => - props.editor.updateBlock(props.block, { - type: "image", - props: { - src: src, - }, - } as PartialBlock) - ); + async (file: File) => { + const uploaded = await props.editor.uploadFile(file); + props.editor.updateBlock(props.block, { + type: "image", + props: { + src: uploaded, + }, + } as PartialBlock); }, [props.block, props.editor] ); From d96f816e68b3d1dc22c24b471e6508193d288ae7 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 22 Sep 2023 18:56:31 +0200 Subject: [PATCH 2/3] Updated `uploadFile` JSDoc --- packages/core/src/BlockNoteEditor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/BlockNoteEditor.ts b/packages/core/src/BlockNoteEditor.ts index a48039d0f5..95b5cac70b 100644 --- a/packages/core/src/BlockNoteEditor.ts +++ b/packages/core/src/BlockNoteEditor.ts @@ -108,9 +108,9 @@ export type BlockNoteEditorOptions = { blockSchema: BSchema; /** - * A custom function to handle image uploads for the default image block. - * @param file The image file that should be uploaded. - * @returns The URL of the uploaded image. + * A custom function to handle file uploads. + * @param file The file that should be uploaded. + * @returns The URL of the uploaded file. */ uploadFile: (file: File) => Promise; From bcc831f5ff6c1cf2b354733192d8029ef294848c Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 27 Sep 2023 01:16:52 +0200 Subject: [PATCH 3/3] Extracted example file upload function to new file removed it as a default --- packages/core/src/BlockNoteEditor.ts | 21 +--------- .../uploadToTmpFilesOrg_DEV_ONLY.ts | 13 ++++++ packages/core/src/index.ts | 1 + .../components/DefaultImageToolbar.tsx | 42 +++++++++++-------- 4 files changed, 41 insertions(+), 36 deletions(-) create mode 100644 packages/core/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.ts diff --git a/packages/core/src/BlockNoteEditor.ts b/packages/core/src/BlockNoteEditor.ts index 95b5cac70b..a1c858c358 100644 --- a/packages/core/src/BlockNoteEditor.ts +++ b/packages/core/src/BlockNoteEditor.ts @@ -161,7 +161,7 @@ export class BlockNoteEditor { public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin; public readonly imageToolbar: ImageToolbarProsemirrorPlugin; - public readonly uploadFile: (file: File) => Promise; + public readonly uploadFile: ((file: File) => Promise) | undefined; constructor( private readonly options: Partial> = {} @@ -215,24 +215,7 @@ export class BlockNoteEditor { this.schema = newOptions.blockSchema; - this.uploadFile = - newOptions.uploadFile || - (async (file) => { - // TODO: Proper backend - right now using imgbb.com since you can get an - // API key for free by just making an account. - // https://imgbb.com/ - 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/" - ); - }); + this.uploadFile = newOptions.uploadFile; const initialContent = newOptions.initialContent || diff --git a/packages/core/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.ts b/packages/core/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.ts new file mode 100644 index 0000000000..5944735c98 --- /dev/null +++ b/packages/core/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.ts @@ -0,0 +1,13 @@ +export const uploadToTmpFilesOrg = async (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 (await ret.json()).data.url.replace( + "tmpfiles.org/", + "tmpfiles.org/dl/" + ); +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 26bd8eff8e..68078fecfa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,6 +8,7 @@ export * from "./extensions/Blocks/api/inlineContentTypes"; export * from "./extensions/Blocks/api/selectionTypes"; export * from "./extensions/Blocks/api/serialization"; export * as blockStyles from "./extensions/Blocks/nodes/Block.module.css"; +export * from "./extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY"; export * from "./extensions/FormattingToolbar/FormattingToolbarPlugin"; export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; export * from "./extensions/ImageToolbar/ImageToolbarPlugin"; diff --git a/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx b/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx index 3b81307a79..351e2905eb 100644 --- a/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx +++ b/packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx @@ -8,17 +8,21 @@ import { ImageToolbarProps } from "./ImageToolbarPositioner"; export const DefaultImageToolbar = ( props: ImageToolbarProps ) => { - const [openTab, setOpenTab] = useState<"upload" | "embed">("upload"); + const [openTab, setOpenTab] = useState<"upload" | "embed">( + props.editor.uploadFile !== undefined ? "upload" : "embed" + ); const handleFileChange = useCallback( async (file: File) => { - const uploaded = await props.editor.uploadFile(file); - props.editor.updateBlock(props.block, { - type: "image", - props: { - src: uploaded, - }, - } as PartialBlock); + if (props.editor.uploadFile !== undefined) { + const uploaded = await props.editor.uploadFile(file); + props.editor.updateBlock(props.block, { + type: "image", + props: { + src: uploaded, + }, + } as PartialBlock); + } }, [props.block, props.editor] ); @@ -63,18 +67,22 @@ export const DefaultImageToolbar = ( }}> - Upload + {props.editor.uploadFile !== undefined && ( + Upload + )} Embed - - - + {props.editor.uploadFile !== undefined && ( + + + + )}