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
36 changes: 8 additions & 28 deletions packages/core/src/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
updateBlock,
} from "./api/blockManipulation/blockManipulation";
import {
HTMLToBlocks,
blocksToHTML,
blocksToMarkdown,
HTMLToBlocks,
markdownToBlocks,
} from "./api/formatConversions/formatConversions";
import {
Expand Down Expand Up @@ -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<BSchema extends BlockSchema> = {
// TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
Expand Down Expand Up @@ -110,11 +108,11 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
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.
*/
uploadImage: (file: File) => Promise<string>;
uploadFile: (file: File) => Promise<string>;

/**
* When enabled, allows for collaboration between multiple users.
Expand Down Expand Up @@ -163,7 +161,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin<BSchema>;
public readonly imageToolbar: ImageToolbarProsemirrorPlugin<BSchema>;

public readonly imageUpload: (file: File) => Promise<string>;
public readonly uploadFile: ((file: File) => Promise<string>) | undefined;

constructor(
private readonly options: Partial<BlockNoteEditorOptions<BSchema>> = {}
Expand Down Expand Up @@ -217,25 +215,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {

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;

const initialContent =
newOptions.initialContent ||
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/api/nodeConversions/nodeConversions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe("Simple ProseMirror Node Conversions", () => {

expect(firstBlockConversion).toMatchSnapshot();

const firstNodeConversion = blockToNode(
firstBlockConversion as any,
const firstNodeConversion = blockToNode<DefaultBlockSchema>(
firstBlockConversion,
tt.schema
);

Expand Down Expand Up @@ -150,8 +150,8 @@ describe("Complex ProseMirror Node Conversions", () => {

expect(firstBlockConversion).toMatchSnapshot();

const firstNodeConversion = blockToNode(
firstBlockConversion as any,
const firstNodeConversion = blockToNode<DefaultBlockSchema>(
firstBlockConversion,
tt.schema
);

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/api/nodeConversions/nodeConversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ToggledStyle>([
"bold",
Expand Down
Original file line number Diff line number Diff line change
@@ -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/"
);
};
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = <BSchema extends BlockSchema>(props: {
editor: BlockNoteEditor<BSchema>;
Expand All @@ -18,14 +18,11 @@ export const ReplaceImageButton = <BSchema extends BlockSchema>(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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -55,13 +55,7 @@ export const TextAlignButton = <BSchema extends BlockSchema>(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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<ToggledStyle, string> = {
Expand Down Expand Up @@ -50,13 +50,7 @@ export const ToggledStyleButton = <BSchema extends BlockSchema>(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) {
Expand Down
34 changes: 20 additions & 14 deletions packages/react/src/ImageToolbar/components/DefaultImageToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ import {
export const DefaultImageToolbar = <BSchema extends BlockSchema>(
props: ImageToolbarProps<BSchema>
) => {
const [openTab, setOpenTab] = useState<"upload" | "embed">("upload");
const [openTab, setOpenTab] = useState<"upload" | "embed">(props.editor.uploadFile !== undefined ? "upload" : "embed");
const [uploading, setUploading] = useState<boolean>(false);


const handleFileChange = useCallback(
(file: File) => {
async (file: File) => {
setUploading(true);
props.editor.imageUpload(file).then((url) => {
if (props.editor.uploadFile !== undefined) {
const uploaded = await props.editor.uploadFile(file);
props.editor.updateBlock(props.block, {
type: "image",
props: {
url: url,
url: uploaded,
},
} as PartialBlock<BSchema>);
setUploading(false);
});
}
},
[props.block, props.editor]
);
Expand Down Expand Up @@ -75,18 +77,22 @@ export const DefaultImageToolbar = <BSchema extends BlockSchema>(
{uploading && <LoadingOverlay visible={uploading} />}

<Tabs.List>
<Tabs.Tab value="upload">Upload</Tabs.Tab>
{props.editor.uploadFile !== undefined && (
<Tabs.Tab value="upload">Upload</Tabs.Tab>
)}
<Tabs.Tab value="embed">Embed</Tabs.Tab>
</Tabs.List>

<Tabs.Panel value="upload">
<FileInput
placeholder={"Upload Image"}
size={"xs"}
value={null}
onChange={handleFileChange}
/>
</Tabs.Panel>
{props.editor.uploadFile !== undefined && (
<Tabs.Panel value="upload">
<FileInput
placeholder={"Upload Image"}
size={"xs"}
value={null}
onChange={handleFileChange}
/>
</Tabs.Panel>
)}
<Tabs.Panel value="embed">
<div
style={{
Expand Down