From 6406daf5e85afde995b5c99c6682a7ee055cdbb3 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 6 Mar 2024 20:02:53 +0100 Subject: [PATCH 1/8] wip --- .../mantine/DefaultPanels/URLPanel.tsx | 81 +++++++++ .../mantine/DefaultPanels/UploadPanel.tsx | 88 ++++++++++ .../mantine/DefaultTabs/UploadTab.tsx | 19 +++ .../ImageToolbar/mantine/ImageToolbar.tsx | 156 ++++-------------- 4 files changed, 220 insertions(+), 124 deletions(-) create mode 100644 packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx new file mode 100644 index 0000000000..14d82253e2 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx @@ -0,0 +1,81 @@ +import { Button, Tabs, TextInput } from "@mantine/core"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; +import { ImageToolbarProps } from "../../ImageToolbarProps"; + +export const URLPanel = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: ImageToolbarProps +) => { + const { block } = props; + + const editor = useBlockNoteEditor< + { image: DefaultBlockSchema["image"] }, + I, + S + >(); + + const [currentURL, setCurrentURL] = useState(""); + + const handleURLChange = useCallback( + (event: ChangeEvent) => { + setCurrentURL(event.currentTarget.value); + }, + [] + ); + + const handleURLEnter = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + editor.updateBlock(block, { + type: "image", + props: { + url: currentURL, + }, + }); + } + }, + [editor, block, currentURL] + ); + + const handleURLClick = useCallback(() => { + editor.updateBlock(block, { + type: "image", + props: { + url: currentURL, + }, + }); + }, [editor, block, currentURL]); + + return ( + +
+ + +
+
+ ); +}; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx new file mode 100644 index 0000000000..b545b3f40d --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx @@ -0,0 +1,88 @@ +import { FileInput, Tabs, Text } from "@mantine/core"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { useCallback, useEffect, useState } from "react"; +import { ImageToolbarProps } from "../../ImageToolbarProps"; + +export const UploadPanel = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: ImageToolbarProps & { + setLoading: (loading: boolean) => void; + } +) => { + const { block, setLoading } = props; + + const editor = useBlockNoteEditor< + { image: DefaultBlockSchema["image"] }, + I, + S + >(); + + const [uploadFailed, setUploadFailed] = useState(false); + + useEffect(() => { + if (uploadFailed) { + setTimeout(() => { + setUploadFailed(false); + }, 3000); + } + }, [uploadFailed]); + + const handleFileChange = useCallback( + (file: File | null) => { + if (file === null) { + return; + } + + async function upload(file: File) { + setLoading(true); + + if (editor.uploadFile !== undefined) { + try { + const uploaded = await editor.uploadFile(file); + editor.updateBlock(block, { + type: "image", + props: { + url: uploaded, + }, + }); + } catch (e) { + setUploadFailed(true); + } finally { + setLoading(false); + } + } + } + + upload(file); + }, + [block, editor, setLoading] + ); + + return editor.uploadFile !== undefined ? ( + +
+ + {uploadFailed && ( + + Error: Upload failed + + )} +
+
+ ) : null; +}; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx new file mode 100644 index 0000000000..6243196ce0 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx @@ -0,0 +1,19 @@ +import { Tabs } from "@mantine/core"; +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; + +export const UploadTab = () => { + const editor = useBlockNoteEditor< + BlockSchema, + InlineContentSchema, + StyleSchema + >(); + + return ( + editor.uploadFile !== undefined && ( + + Upload + + ) + ); +}; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx index 22a5f6c93a..78362265ef 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx @@ -5,31 +5,29 @@ import { InlineContentSchema, StyleSchema, } from "@blocknote/core"; -import { - Button, - FileInput, - LoadingOverlay, - Tabs, - Text, - TextInput, -} from "@mantine/core"; -import { - ChangeEvent, - KeyboardEvent, - useCallback, - useEffect, - useState, -} from "react"; +import { LoadingOverlay, Tabs } from "@mantine/core"; +import { FC, useState } from "react"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { ImageToolbarProps } from "../ImageToolbarProps"; +import { UploadPanel } from "./DefaultPanels/UploadPanel"; +import { URLPanel } from "./DefaultPanels/URLPanel"; export const ImageToolbar = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: ImageToolbarProps + props: ImageToolbarProps & { + tabs?: { + tab: FC>; + panel: FC< + ImageToolbarProps & { + setLoading: (loading: boolean) => void; + } + >; + }[]; + } ) => { const editor = useBlockNoteEditor< { image: DefaultBlockSchema["image"] }, @@ -40,85 +38,30 @@ export const ImageToolbar = < const [openTab, setOpenTab] = useState<"upload" | "embed">( editor.uploadFile !== undefined ? "upload" : "embed" ); - const [uploading, setUploading] = useState(false); - const [uploadFailed, setUploadFailed] = useState(false); - - useEffect(() => { - if (uploadFailed) { - setTimeout(() => { - setUploadFailed(false); - }, 3000); - } - }, [uploadFailed]); - - const handleFileChange = useCallback( - (file: File | null) => { - if (file === null) { - return; - } - - async function upload(file: File) { - setUploading(true); - - if (editor.uploadFile !== undefined) { - try { - const uploaded = await editor.uploadFile(file); - editor.updateBlock(props.block, { - type: "image", - props: { - url: uploaded, - }, - }); - } catch (e) { - setUploadFailed(true); - } finally { - setUploading(false); - } - } - } - - upload(file); - }, - [editor, props.block] - ); + const [loading, setLoading] = useState(false); - const [currentURL, setCurrentURL] = useState(""); + return ( + + + {loading && } - const handleURLChange = useCallback( - (event: ChangeEvent) => { - setCurrentURL(event.currentTarget.value); - }, - [] - ); + + {props.tabs?.map(({ tab, panel }, index) => { + const Tab = tab; + return ; + })} + - const handleURLEnter = useCallback( - (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - editor.updateBlock(props.block, { - type: "image", - props: { - url: currentURL, - }, - }); - } - }, - [editor, props.block, currentURL] + + + + ); - const handleURLClick = useCallback(() => { - editor.updateBlock(props.block, { - type: "image", - props: { - url: currentURL, - }, - }); - }, [editor, props.block, currentURL]); - return ( - {uploading && } + {loading && } {editor.uploadFile !== undefined && ( @@ -131,43 +74,8 @@ export const ImageToolbar = < - {editor.uploadFile !== undefined && ( - -
- - {uploadFailed && ( - - Error: Upload failed - - )} -
-
- )} - -
- - -
-
+ +
); From 184d36c3dbfe6cfd940569ef9d245c0072ad6409 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 6 Mar 2024 21:28:09 +0100 Subject: [PATCH 2/8] Refactored image toolbar --- .../EmbedPanel.tsx} | 41 +++++---- .../UploadPanel.tsx | 33 ++++---- .../mantine/DefaultTabs/UploadTab.tsx | 19 ----- .../ImageToolbar/mantine/ImageToolbar.tsx | 83 +++++++++++-------- .../mantine/ImageToolbarButton.tsx | 11 +++ .../mantine/ImageToolbarFileInput.tsx | 17 ++++ .../mantine/ImageToolbarPanel.tsx | 19 +++++ .../mantine/ImageToolbarTextInput.tsx | 9 ++ packages/react/src/editor/styles.css | 40 +++------ packages/react/src/index.ts | 6 ++ 10 files changed, 161 insertions(+), 117 deletions(-) rename packages/react/src/components/ImageToolbar/mantine/{DefaultPanels/URLPanel.tsx => DefaultTabPanels/EmbedPanel.tsx} (67%) rename packages/react/src/components/ImageToolbar/mantine/{DefaultPanels => DefaultTabPanels}/UploadPanel.tsx (77%) delete mode 100644 packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx create mode 100644 packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx similarity index 67% rename from packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx rename to packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx index 14d82253e2..be41ba3f07 100644 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/URLPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx @@ -1,4 +1,3 @@ -import { Button, Tabs, TextInput } from "@mantine/core"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { DefaultBlockSchema, @@ -8,9 +7,13 @@ import { StyleSchema, } from "@blocknote/core"; import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; + import { ImageToolbarProps } from "../../ImageToolbarProps"; +import { ImageToolbarPanel } from "../ImageToolbarPanel"; +import { ImageToolbarTextInput } from "../ImageToolbarTextInput"; +import { ImageToolbarButton } from "../ImageToolbarButton"; -export const URLPanel = < +export const EmbedPanel = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( @@ -58,24 +61,20 @@ export const URLPanel = < }, [editor, block, currentURL]); return ( - -
- - -
-
+ + + + Embed Image + + ); }; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx similarity index 77% rename from packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx rename to packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx index b545b3f40d..e3e57c291d 100644 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultPanels/UploadPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx @@ -1,4 +1,4 @@ -import { FileInput, Tabs, Text } from "@mantine/core"; +import { Text } from "@mantine/core"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { DefaultBlockSchema, @@ -8,7 +8,10 @@ import { StyleSchema, } from "@blocknote/core"; import { useCallback, useEffect, useState } from "react"; + import { ImageToolbarProps } from "../../ImageToolbarProps"; +import { ImageToolbarPanel } from "../ImageToolbarPanel"; +import { ImageToolbarFileInput } from "../ImageToolbarFileInput"; export const UploadPanel = < I extends InlineContentSchema = DefaultInlineContentSchema, @@ -68,21 +71,17 @@ export const UploadPanel = < ); return editor.uploadFile !== undefined ? ( - -
- - {uploadFailed && ( - - Error: Upload failed - - )} -
-
+ + + {uploadFailed && ( + + Error: Upload failed + + )} + ) : null; }; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx deleted file mode 100644 index 6243196ce0..0000000000 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Tabs } from "@mantine/core"; -import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; -import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; - -export const UploadTab = () => { - const editor = useBlockNoteEditor< - BlockSchema, - InlineContentSchema, - StyleSchema - >(); - - return ( - editor.uploadFile !== undefined && ( - - Upload - - ) - ); -}; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx index 78362265ef..8b4af41184 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx @@ -11,8 +11,8 @@ import { FC, useState } from "react"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { ImageToolbarProps } from "../ImageToolbarProps"; -import { UploadPanel } from "./DefaultPanels/UploadPanel"; -import { URLPanel } from "./DefaultPanels/URLPanel"; +import { UploadPanel } from "./DefaultTabPanels/UploadPanel"; +import { EmbedPanel } from "./DefaultTabPanels/EmbedPanel"; export const ImageToolbar = < I extends InlineContentSchema = DefaultInlineContentSchema, @@ -20,8 +20,8 @@ export const ImageToolbar = < >( props: ImageToolbarProps & { tabs?: { - tab: FC>; - panel: FC< + tabName: string; + tabPanel: FC< ImageToolbarProps & { setLoading: (loading: boolean) => void; } @@ -29,14 +29,24 @@ export const ImageToolbar = < }[]; } ) => { + if (props.tabs !== undefined && props.tabs.length === 0) { + throw Error( + "Prop `tabs` was passed to ImageToolbar component but contains no elements." + ); + } + const editor = useBlockNoteEditor< { image: DefaultBlockSchema["image"] }, I, S >(); - const [openTab, setOpenTab] = useState<"upload" | "embed">( - editor.uploadFile !== undefined ? "upload" : "embed" + const [openTab, setOpenTab] = useState( + props.tabs !== undefined + ? props.tabs[0].tabName + : editor.uploadFile !== undefined + ? "upload" + : "embed" ); const [loading, setLoading] = useState(false); @@ -46,36 +56,43 @@ export const ImageToolbar = < {loading && } - {props.tabs?.map(({ tab, panel }, index) => { - const Tab = tab; - return ; - })} - - - - - - - ); - - return ( - - - {loading && } - - - {editor.uploadFile !== undefined && ( - - Upload - + {props.tabs !== undefined ? ( + props.tabs.map(({ tabName }) => ( + {tabName} + )) + ) : ( + <> + {editor.uploadFile !== undefined && ( + + Upload + + )} + + Embed + + )} - - Embed - - - + {props.tabs !== undefined ? ( + props.tabs.map(({ tabName, tabPanel }) => { + const TabPanel = tabPanel; + return ( + + + + ); + }) + ) : ( + <> + + + + + + + + )} ); diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx new file mode 100644 index 0000000000..8475d5e1f8 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx @@ -0,0 +1,11 @@ +import { ComponentPropsWithoutRef, forwardRef } from "react"; +import { Button } from "@mantine/core"; + +export const ImageToolbarButton = forwardRef< + HTMLButtonElement, + Omit, "size"> +>((props, ref) => ( + +)); diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx new file mode 100644 index 0000000000..4453eea383 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx @@ -0,0 +1,17 @@ +import { FileInput } from "@mantine/core"; +import { ComponentPropsWithoutRef, forwardRef, ReactNode } from "react"; + +export const ImageToolbarFileInput = forwardRef< + HTMLButtonElement, + Omit< + ComponentPropsWithoutRef<"button">, + "value" | "defaultValue" | "onChange" + > & { + placeholder?: ReactNode; + value?: File | null; + defaultValue?: File | null; + onChange?: (payload: File | null) => void; + } +>((props, ref) => ( + +)); diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx new file mode 100644 index 0000000000..f5bfe6d55f --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx @@ -0,0 +1,19 @@ +import { ComponentPropsWithoutRef, forwardRef } from "react"; + +export const ImageToolbarPanel = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef<"div"> +>((props, ref) => { + const { className, children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}); diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx new file mode 100644 index 0000000000..29f4a2ea61 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx @@ -0,0 +1,9 @@ +import { TextInput } from "@mantine/core"; +import { ComponentPropsWithoutRef, forwardRef } from "react"; + +export const ImageToolbarTextInput = forwardRef< + HTMLInputElement, + Omit, "size"> +>((props, ref) => ( + +)); diff --git a/packages/react/src/editor/styles.css b/packages/react/src/editor/styles.css index 7638586d0e..1df01bc23d 100644 --- a/packages/react/src/editor/styles.css +++ b/packages/react/src/editor/styles.css @@ -364,13 +364,6 @@ background-color: var(--bn-colors-hovered-background); } -.bn-container .bn-toolbar .bn-embed-image-button { - border: solid var(--bn-colors-border) 1px; - border-radius: var(--bn-border-radius-small); - height: 32px; - width: 60%; -} - .bn-container .bn-toolbar-input-dropdown { background-color: var(--bn-colors-menu-background); border: var(--bn-border); @@ -425,35 +418,28 @@ width: 500px; } -.bn-container .bn-image-toolbar .bn-upload-image-panel > div { - align-items: stretch; +.bn-container .bn-image-toolbar .bn-image-toolbar-panel { + align-items: center; display: flex; flex-direction: column; gap: 8px; + width: 100%; } -.bn-container - .bn-image-toolbar - .bn-upload-image-panel - > div - > .mantine-Text-root { - text-align: center; +.bn-container .bn-image-toolbar .mantine-TextInput-root, +.bn-container .bn-image-toolbar .mantine-FileInput-root { + width: 100%; } -.bn-container .bn-image-toolbar .bn-embed-image-panel > div { - align-items: center; - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; +.bn-container .bn-image-toolbar .mantine-Button-root { + border: solid var(--bn-colors-border) 1px; + border-radius: var(--bn-border-radius-small); + height: 32px; + width: 60%; } -.bn-container - .bn-image-toolbar - .bn-embed-image-panel - > div - .mantine-TextInput-root { - width: 100%; +.bn-container .bn-image-toolbar .mantine-Text-root { + text-align: center; } /* Slash Menu styling*/ diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 3f05fdbf12..1688c11948 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -47,7 +47,13 @@ export * from "./components/SuggestionMenu/types"; export * from "./components/ImageToolbar/ImageToolbarController"; export * from "./components/ImageToolbar/ImageToolbarProps"; +export * from "./components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel"; +export * from "./components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel"; export * from "./components/ImageToolbar/mantine/ImageToolbar"; +export * from "./components/ImageToolbar/mantine/ImageToolbarButton"; +export * from "./components/ImageToolbar/mantine/ImageToolbarFileInput"; +export * from "./components/ImageToolbar/mantine/ImageToolbarPanel"; +export * from "./components/ImageToolbar/mantine/ImageToolbarTextInput"; export * from "./components/TableHandles/TableHandleProps"; export * from "./components/TableHandles/TableHandlesController"; From 861c2b293766093f4edf152473150f6e4957d217 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Thu, 7 Mar 2024 17:05:48 +0100 Subject: [PATCH 3/8] Refactored image toolbar, hyperlink toolbar, and table handles --- .../DefaultButtons/CreateLinkButton.tsx | 4 +- .../mantine/FormattingToolbar.tsx | 4 +- .../DefaultButtons/DeleteLinkButton.tsx | 14 +++ .../EditLinkButton.tsx} | 50 ++++---- .../mantine/DefaultButtons/OpenLinkButton.tsx | 14 +++ .../mantine/HyperlinkToolbar.tsx | 73 +++-------- .../ImageToolbar/mantine/ImageToolbar.tsx | 6 + .../DragHandleMenu/mantine/DragHandleMenu.tsx | 3 +- .../components/SideMenu/mantine/SideMenu.tsx | 5 +- .../TableHandleMenu/TableHandleMenuProps.ts | 18 ++- .../mantine/DefaultButtons/AddButton.tsx | 117 +++++++++++------- .../mantine/DefaultButtons/DeleteButton.tsx | 105 ++++++++++------ .../mantine/DefaultTableHandleMenu.tsx | 35 ------ .../mantine/TableHandleMenu.tsx | 39 +++++- .../TableHandles/mantine/TableHandle.tsx | 71 +++++++++-- .../mantine/TableHandleWrapper.tsx | 68 ---------- packages/react/src/index.ts | 12 +- 17 files changed, 338 insertions(+), 300 deletions(-) create mode 100644 packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx rename packages/react/src/components/HyperlinkToolbar/mantine/{EditHyperlinkMenu/EditHyperlinkMenu.tsx => DefaultButtons/EditLinkButton.tsx} (64%) create mode 100644 packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx delete mode 100644 packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultTableHandleMenu.tsx delete mode 100644 packages/react/src/components/TableHandles/mantine/TableHandleWrapper.tsx diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx index 9de18b94db..4e91cc2f36 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx @@ -11,9 +11,9 @@ import { import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; -import { EditHyperlinkMenu } from "../../../HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; +import { EditLinkMenu } from "../../../HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton"; // TODO: Make sure Link is in inline content schema export const CreateLinkButton = () => { @@ -64,7 +64,7 @@ export const CreateLinkButton = () => { icon={RiLink} /> } - dropdown={} + dropdown={} /> ); }; diff --git a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx index ef5b6fea49..8ab3fb8b8b 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx @@ -1,3 +1,5 @@ +import { ReactNode } from "react"; + import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { FormattingToolbarProps } from "../FormattingToolbarProps"; import { BasicTextStyleButton } from "./DefaultButtons/BasicTextStyleButton"; @@ -55,7 +57,7 @@ export const getFormattingToolbarItems = ( * `components/mantine-shared/Toolbar` directory. */ export const FormattingToolbar = ( - props: FormattingToolbarProps & { children?: React.ReactNode } + props: FormattingToolbarProps & { children?: ReactNode } ) => { return ( diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx new file mode 100644 index 0000000000..a5e74e430a --- /dev/null +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx @@ -0,0 +1,14 @@ +import { RiLinkUnlink } from "react-icons/ri"; +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; + +export const DeleteLinkButton = ( + props: Pick +) => ( + +); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx similarity index 64% rename from packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu.tsx rename to packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx index d5a71daff6..39b83d4928 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx @@ -1,31 +1,22 @@ +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; +import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; import { ChangeEvent, - forwardRef, - HTMLAttributes, KeyboardEvent, useCallback, useEffect, useState, } from "react"; +import { ToolbarInputDropdownItem } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; import { RiLink, RiText } from "react-icons/ri"; - import { ToolbarInputDropdown } from "../../../mantine-shared/Toolbar/ToolbarInputDropdown"; -import { ToolbarInputDropdownItem } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; -export type EditHyperlinkMenuProps = { - url: string; - text: string; - update: (url: string, text: string) => void; -}; +export const EditLinkMenu = ( + props: Pick +) => { + const { url, text, editHyperlink } = props; -/** - * Menu which opens when editing an existing hyperlink or creating a new one. - * Provides input fields for setting the hyperlink URL and title. - */ -export const EditHyperlinkMenu = forwardRef< - HTMLDivElement, - EditHyperlinkMenuProps & HTMLAttributes ->(({ url, text, update, ...props }, ref) => { const [currentUrl, setCurrentUrl] = useState(url); const [currentText, setCurrentText] = useState(text); @@ -38,10 +29,10 @@ export const EditHyperlinkMenu = forwardRef< (event: KeyboardEvent) => { if (event.key === "Enter") { event.preventDefault(); - update(currentUrl, currentText); + editHyperlink(currentUrl, currentText); } }, - [update, currentUrl, currentText] + [editHyperlink, currentUrl, currentText] ); const handleUrlChange = useCallback( @@ -57,12 +48,12 @@ export const EditHyperlinkMenu = forwardRef< ); const handleSubmit = useCallback( - () => update(currentUrl, currentText), - [update, currentUrl, currentText] + () => editHyperlink(currentUrl, currentText), + [editHyperlink, currentUrl, currentText] ); return ( - + ); -}); +}; + +export const EditLinkButton = ( + props: Pick +) => ( + + Edit Link + + } + dropdown={} + /> +); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx new file mode 100644 index 0000000000..196c90fb57 --- /dev/null +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx @@ -0,0 +1,14 @@ +import { RiExternalLinkFill } from "react-icons/ri"; +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; + +export const OpenLinkButton = (props: Pick) => ( + { + window.open(props.url, "_blank"); + }} + icon={RiExternalLinkFill} + /> +); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx index ba931b5d31..52e8c57c8a 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx @@ -1,83 +1,40 @@ -import { useRef, useState } from "react"; -import { RiExternalLinkFill, RiLinkUnlink } from "react-icons/ri"; +import { ReactNode } from "react"; import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; -import { EditHyperlinkMenu } from "./EditHyperlinkMenu/EditHyperlinkMenu"; import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { ToolbarButton } from "../../mantine-shared/Toolbar/ToolbarButton"; +import { EditLinkButton } from "./DefaultButtons/EditLinkButton"; +import { OpenLinkButton } from "./DefaultButtons/OpenLinkButton"; +import { DeleteLinkButton } from "./DefaultButtons/DeleteLinkButton"; /** * By default, the HyperlinkToolbar component will render with default buttons. * However, you can override the dropdowns/buttons to render by passing * children. The children you pass should be: * - * TODO: Refactor and export default buttons + * - Default buttons: Components found within the `/DefaultButtons` directory. * - Custom dropdowns: The `ToolbarDropdown` component in the * `components/mantine-shared/Toolbar` directory. * - Custom buttons: The `ToolbarButton` component in the * `components/mantine-shared/Toolbar` directory. */ export const HyperlinkToolbar = ( - props: HyperlinkToolbarProps & { children?: React.ReactNode } + props: HyperlinkToolbarProps & { children?: ReactNode } ) => { - const [isEditing, setIsEditing] = useState(false); - const editMenuRef = useRef(null); - if (props.children) { return {props.children}; } - const { - text, - url, - deleteHyperlink, - editHyperlink, - startHideTimer, - stopHideTimer, - } = props; - - if (isEditing) { - return ( - - setTimeout(() => { - if (editMenuRef.current?.contains(event.relatedTarget)) { - return; - } - setIsEditing(false); - }, 500) - } - ref={editMenuRef} - /> - ); - } - return ( - - setIsEditing(true)}> - Edit Link - - { - window.open(url, "_blank"); - }} - icon={RiExternalLinkFill} - /> - + + + ); }; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx index 8b4af41184..3e550ac87a 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx @@ -14,6 +14,12 @@ import { ImageToolbarProps } from "../ImageToolbarProps"; import { UploadPanel } from "./DefaultTabPanels/UploadPanel"; import { EmbedPanel } from "./DefaultTabPanels/EmbedPanel"; +/** + * By default, the ImageToolbar component will render with default tabs. + * However, you can override the tabs to render by passing the `tabs` prop. You + * can use the default tab panels in the `DefaultTabPanels` directory or make + * your own using the `ImageToolbarPanel` component. + */ export const ImageToolbar = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/mantine/DragHandleMenu.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/mantine/DragHandleMenu.tsx index 6d1245a446..299f9d3e99 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/mantine/DragHandleMenu.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/mantine/DragHandleMenu.tsx @@ -6,6 +6,7 @@ import { InlineContentSchema, StyleSchema, } from "@blocknote/core"; +import { ReactNode } from "react"; import { Menu } from "@mantine/core"; import { DragHandleMenuProps } from "../DragHandleMenuProps"; @@ -25,7 +26,7 @@ export const DragHandleMenu = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: DragHandleMenuProps & { children?: React.ReactNode } + props: DragHandleMenuProps & { children?: ReactNode } ) => ( {props.children || ( diff --git a/packages/react/src/components/SideMenu/mantine/SideMenu.tsx b/packages/react/src/components/SideMenu/mantine/SideMenu.tsx index 1a2c2ed49f..d0cd003b64 100644 --- a/packages/react/src/components/SideMenu/mantine/SideMenu.tsx +++ b/packages/react/src/components/SideMenu/mantine/SideMenu.tsx @@ -1,3 +1,4 @@ +import { Group } from "@mantine/core"; import { BlockSchema, DefaultBlockSchema, @@ -6,8 +7,8 @@ import { InlineContentSchema, StyleSchema, } from "@blocknote/core"; +import { ReactNode } from "react"; -import { Group } from "@mantine/core"; import { SideMenuProps } from "../SideMenuProps"; import { AddBlockButton } from "./DefaultButtons/AddBlockButton"; import { DragHandleButton } from "./DefaultButtons/DragHandleButton"; @@ -26,7 +27,7 @@ export const SideMenu = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: SideMenuProps & { children?: React.ReactNode } + props: SideMenuProps & { children?: ReactNode } ) => { const { addBlock, ...rest } = props; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts index c4c3dcf31a..3f23c87fba 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts +++ b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts @@ -1,19 +1,17 @@ import { - BlockNoteEditor, DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, SpecificBlock, + StyleSchema, } from "@blocknote/core"; export type TableHandleMenuProps< - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema > = { orientation: "row" | "column"; - editor: BlockNoteEditor; - block: SpecificBlock< - { table: DefaultBlockSchema["table"] }, - "table", - any, - any - >; + block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; index: number; -}; \ No newline at end of file +}; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/AddButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/AddButton.tsx index b6760e6f47..41f5200d2d 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/AddButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/AddButton.tsx @@ -1,68 +1,91 @@ import { DefaultBlockSchema, - PartialBlock, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, TableContent, } from "@blocknote/core"; import { TableHandleMenuProps } from "../../TableHandleMenuProps"; import { TableHandleMenuItem } from "../TableHandleMenuItem"; +import { useBlockNoteEditor } from "../../../../../hooks/useBlockNoteEditor"; export const AddRowButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps & { side: "above" | "below" } -) => ( - { - const emptyCol = props.block.content.rows[props.index].cells.map( - () => [] - ); - const rows = [...props.block.content.rows]; - rows.splice(props.index + (props.side === "below" ? 1 : 0), 0, { - cells: emptyCol, - }); + props: TableHandleMenuProps & { side: "above" | "below" } +) => { + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); - props.editor.updateBlock(props.block, { - type: "table", - content: { - type: "tableContent", - rows, - }, - } as PartialBlock); - }}> - {`Add row ${props.side}`} - -); + return ( + { + const emptyCol = props.block.content.rows[props.index].cells.map( + () => [] + ); + const rows = [...props.block.content.rows]; + rows.splice(props.index + (props.side === "below" ? 1 : 0), 0, { + cells: emptyCol, + }); + + editor.updateBlock(props.block, { + type: "table", + content: { + type: "tableContent", + rows, + }, + }); + }}> + {`Add row ${props.side}`} + + ); +}; export const AddColumnButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps & { side: "left" | "right" } -) => ( - { - const content: TableContent = { - type: "tableContent", - rows: props.block.content.rows.map((row) => { - const cells = [...row.cells]; - cells.splice(props.index + (props.side === "right" ? 1 : 0), 0, []); - return { cells }; - }), - }; + props: TableHandleMenuProps & { side: "left" | "right" } +) => { + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); - props.editor.updateBlock(props.block, { - type: "table", - content: content, - } as PartialBlock); - }}> - {`Add column ${props.side}`} - -); + return ( + { + const content: TableContent = { + type: "tableContent", + rows: props.block.content.rows.map((row) => { + const cells = [...row.cells]; + cells.splice(props.index + (props.side === "right" ? 1 : 0), 0, []); + return { cells }; + }), + }; + + editor.updateBlock(props.block, { + type: "table", + content: content, + }); + }}> + {`Add column ${props.side}`} + + ); +}; export const AddButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps & + props: TableHandleMenuProps & ( | { orientation: "row"; side: "above" | "below" } | { orientation: "column"; side: "left" | "right" } diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/DeleteButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/DeleteButton.tsx index 728f49e588..33baf221ad 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/DeleteButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultButtons/DeleteButton.tsx @@ -1,62 +1,85 @@ import { DefaultBlockSchema, - PartialBlock, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, TableContent, } from "@blocknote/core"; import { TableHandleMenuProps } from "../../TableHandleMenuProps"; import { TableHandleMenuItem } from "../TableHandleMenuItem"; +import { useBlockNoteEditor } from "../../../../../hooks/useBlockNoteEditor"; export const DeleteRowButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps -) => ( - { - const content: TableContent = { - type: "tableContent", - rows: props.block.content.rows.filter( - (_, index) => index !== props.index - ), - }; + props: TableHandleMenuProps +) => { + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); - props.editor.updateBlock(props.block, { - type: "table", - content, - } as PartialBlock); - }}> - Delete row - -); + return ( + { + const content: TableContent = { + type: "tableContent", + rows: props.block.content.rows.filter( + (_, index) => index !== props.index + ), + }; + + editor.updateBlock(props.block, { + type: "table", + content, + }); + }}> + Delete row + + ); +}; export const DeleteColumnButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps -) => ( - { - const content: TableContent = { - type: "tableContent", - rows: props.block.content.rows.map((row) => ({ - cells: row.cells.filter((_, index) => index !== props.index), - })), - }; + props: TableHandleMenuProps +) => { + const editor = useBlockNoteEditor< + { table: DefaultBlockSchema["table"] }, + I, + S + >(); - props.editor.updateBlock(props.block, { - type: "table", - content, - } as PartialBlock); - }}> - Delete column - -); + return ( + { + const content: TableContent = { + type: "tableContent", + rows: props.block.content.rows.map((row) => ({ + cells: row.cells.filter((_, index) => index !== props.index), + })), + }; + + editor.updateBlock(props.block, { + type: "table", + content, + }); + }}> + Delete column + + ); +}; export const DeleteButton = < - BSchema extends { table: DefaultBlockSchema["table"] } + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleMenuProps & { orientation: "row" | "column" } + props: TableHandleMenuProps & { orientation: "row" | "column" } ) => props.orientation === "row" ? ( diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultTableHandleMenu.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultTableHandleMenu.tsx deleted file mode 100644 index 612da16309..0000000000 --- a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/DefaultTableHandleMenu.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { DefaultBlockSchema } from "@blocknote/core"; - -import { TableHandleMenuProps } from "../TableHandleMenuProps"; -import { TableHandleMenu } from "./TableHandleMenu"; -import { AddButton } from "./DefaultButtons/AddButton"; -import { DeleteButton } from "./DefaultButtons/DeleteButton"; - -export const DefaultTableHandleMenu = < - BSchema extends { table: DefaultBlockSchema["table"] } ->( - props: TableHandleMenuProps -) => ( - - - - - -); diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/TableHandleMenu.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/TableHandleMenu.tsx index 9eca8eede9..0eb73b5e53 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/mantine/TableHandleMenu.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/mantine/TableHandleMenu.tsx @@ -1,8 +1,43 @@ import { Menu } from "@mantine/core"; +import { + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; import { ReactNode } from "react"; -export const TableHandleMenu = (props: { children: ReactNode }) => ( +import { TableHandleMenuProps } from "../TableHandleMenuProps"; +import { AddButton } from "./DefaultButtons/AddButton"; +import { DeleteButton } from "./DefaultButtons/DeleteButton"; + +export const TableHandleMenu = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: TableHandleMenuProps & { children?: ReactNode } +) => ( - {props.children} + {props.children || ( + <> + + + + + )} ); diff --git a/packages/react/src/components/TableHandles/mantine/TableHandle.tsx b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx index ed8dd67835..43b757a89b 100644 --- a/packages/react/src/components/TableHandles/mantine/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx @@ -2,20 +2,75 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, + mergeCSSClasses, StyleSchema, } from "@blocknote/core"; -import { MdDragIndicator } from "react-icons/md"; +import { Menu } from "@mantine/core"; +import { ReactNode, useState } from "react"; import { TableHandleProps } from "../TableHandleProps"; -import { TableHandleWrapper } from "./TableHandleWrapper"; +import { MdDragIndicator } from "react-icons/md"; +import { TableHandleMenu } from "../TableHandleMenu/mantine/TableHandleMenu"; +// TODO: props.tableHandleMenu should only be available if no children are +// passed +/** + * By default, the TableHandle component will render with the default icon. + * However, you can override the icon to render by passing children. + */ export const TableHandle = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: TableHandleProps -) => ( - - - -); + props: TableHandleProps & { children?: ReactNode } +) => { + const [isDragging, setIsDragging] = useState(false); + + const Component = props.tableHandleMenu || TableHandleMenu; + + return ( + { + props.freezeHandles(); + props.hideOtherSide(); + }} + onClose={() => { + props.unfreezeHandles(); + props.showOtherSide(); + }} + position={"right"}> + +
{ + setIsDragging(true); + props.dragStart(e); + }} + onDragEnd={() => { + props.dragEnd(); + setIsDragging(false); + }} + style={ + props.orientation === "column" + ? { transform: "rotate(0.25turn)" } + : undefined + }> + {props.children || ( + + )} +
+
+ +
+ ); +}; diff --git a/packages/react/src/components/TableHandles/mantine/TableHandleWrapper.tsx b/packages/react/src/components/TableHandles/mantine/TableHandleWrapper.tsx deleted file mode 100644 index af89e03aae..0000000000 --- a/packages/react/src/components/TableHandles/mantine/TableHandleWrapper.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - DefaultInlineContentSchema, - DefaultStyleSchema, - InlineContentSchema, - mergeCSSClasses, - StyleSchema, -} from "@blocknote/core"; -import { Menu } from "@mantine/core"; -import { ReactNode, useState } from "react"; - -import { TableHandleProps } from "../TableHandleProps"; -import { DefaultTableHandleMenu } from "../TableHandleMenu/mantine/DefaultTableHandleMenu"; - -export const TableHandleWrapper = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: TableHandleProps & { children: ReactNode } -) => { - const TableHandleMenu = props.tableHandleMenu || DefaultTableHandleMenu; - - const [isDragging, setIsDragging] = useState(false); - - return ( - { - props.freezeHandles(); - props.hideOtherSide(); - }} - onClose={() => { - props.unfreezeHandles(); - props.showOtherSide(); - }} - position={"right"}> - -
{ - setIsDragging(true); - props.dragStart(e); - }} - onDragEnd={() => { - props.dragEnd(); - setIsDragging(false); - }} - style={ - props.orientation === "column" - ? { transform: "rotate(0.25turn)" } - : undefined - }> - {props.children} -
-
- -
- ); -}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 1688c11948..9b0f2cce7f 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -19,7 +19,9 @@ export * from "./components/FormattingToolbar/mantine/FormattingToolbar"; export * from "./components/HyperlinkToolbar/HyperlinkToolbarController"; export * from "./components/HyperlinkToolbar/HyperlinkToolbarProps"; -export * from "./components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton"; export * from "./components/HyperlinkToolbar/mantine/HyperlinkToolbar"; export * from "./components/SideMenu/SideMenuController"; @@ -55,11 +57,17 @@ export * from "./components/ImageToolbar/mantine/ImageToolbarFileInput"; export * from "./components/ImageToolbar/mantine/ImageToolbarPanel"; export * from "./components/ImageToolbar/mantine/ImageToolbarTextInput"; -export * from "./components/TableHandles/TableHandleProps"; export * from "./components/TableHandles/TableHandlesController"; +export * from "./components/TableHandles/TableHandleProps"; export * from "./components/TableHandles/hooks/useTableHandlesPositioning"; export * from "./components/TableHandles/mantine/TableHandle"; +export * from "./components/TableHandles/TableHandleMenu/TableHandleMenuProps"; +export * from "./components/TableHandles/TableHandleMenu/mantine/DefaultButtons/AddButton"; +export * from "./components/TableHandles/TableHandleMenu/mantine/DefaultButtons/DeleteButton"; +export * from "./components/TableHandles/TableHandleMenu/mantine/TableHandleMenu"; +export * from "./components/TableHandles/TableHandleMenu/mantine/TableHandleMenuItem"; + export * from "./components/mantine-shared/Toolbar/ToolbarButton"; export * from "./components/mantine-shared/Toolbar/ToolbarDropdown"; From 68d7df5d30bfd9cee395b7efc068ce13533b5e1a Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 8 Mar 2024 17:31:12 +0100 Subject: [PATCH 4/8] Implemented PR feedback --- .../DefaultButtons/CreateLinkButton.tsx | 2 +- .../mantine/DefaultButtons/EditLinkButton.tsx | 80 +------------------ .../HyperlinkToolbar/mantine/EditLinkMenu.tsx | 80 +++++++++++++++++++ .../mantine/ImageToolbarPanel.tsx | 5 +- packages/react/src/index.ts | 1 + 5 files changed, 85 insertions(+), 83 deletions(-) create mode 100644 packages/react/src/components/HyperlinkToolbar/mantine/EditLinkMenu.tsx diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx index 4e91cc2f36..000e7a635e 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx @@ -13,7 +13,7 @@ import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorCo import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; -import { EditLinkMenu } from "../../../HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton"; +import { EditLinkMenu } from "../../../HyperlinkToolbar/mantine/EditLinkMenu"; // TODO: Make sure Link is in inline content schema export const CreateLinkButton = () => { diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx index 39b83d4928..95a00bbae8 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx @@ -1,85 +1,7 @@ import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; -import { - ChangeEvent, - KeyboardEvent, - useCallback, - useEffect, - useState, -} from "react"; -import { ToolbarInputDropdownItem } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; -import { RiLink, RiText } from "react-icons/ri"; -import { ToolbarInputDropdown } from "../../../mantine-shared/Toolbar/ToolbarInputDropdown"; - -export const EditLinkMenu = ( - props: Pick -) => { - const { url, text, editHyperlink } = props; - - const [currentUrl, setCurrentUrl] = useState(url); - const [currentText, setCurrentText] = useState(text); - - useEffect(() => { - setCurrentUrl(url); - setCurrentText(text); - }, [text, url]); - - const handleEnter = useCallback( - (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - editHyperlink(currentUrl, currentText); - } - }, - [editHyperlink, currentUrl, currentText] - ); - - const handleUrlChange = useCallback( - (event: ChangeEvent) => - setCurrentUrl(event.currentTarget.value), - [] - ); - - const handleTextChange = useCallback( - (event: ChangeEvent) => - setCurrentText(event.currentTarget.value), - [] - ); - - const handleSubmit = useCallback( - () => editHyperlink(currentUrl, currentText), - [editHyperlink, currentUrl, currentText] - ); - - return ( - - - - - ); -}; +import { EditLinkMenu } from "../EditLinkMenu"; export const EditLinkButton = ( props: Pick diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/EditLinkMenu.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/EditLinkMenu.tsx new file mode 100644 index 0000000000..f5bfc662d0 --- /dev/null +++ b/packages/react/src/components/HyperlinkToolbar/mantine/EditLinkMenu.tsx @@ -0,0 +1,80 @@ +import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; +import { + ChangeEvent, + KeyboardEvent, + useCallback, + useEffect, + useState, +} from "react"; +import { ToolbarInputDropdown } from "../../mantine-shared/Toolbar/ToolbarInputDropdown"; +import { ToolbarInputDropdownItem } from "../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; +import { RiLink, RiText } from "react-icons/ri"; + +export const EditLinkMenu = ( + props: Pick +) => { + const { url, text, editHyperlink } = props; + + const [currentUrl, setCurrentUrl] = useState(url); + const [currentText, setCurrentText] = useState(text); + + useEffect(() => { + setCurrentUrl(url); + setCurrentText(text); + }, [text, url]); + + const handleEnter = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + editHyperlink(currentUrl, currentText); + } + }, + [editHyperlink, currentUrl, currentText] + ); + + const handleUrlChange = useCallback( + (event: ChangeEvent) => + setCurrentUrl(event.currentTarget.value), + [] + ); + + const handleTextChange = useCallback( + (event: ChangeEvent) => + setCurrentText(event.currentTarget.value), + [] + ); + + const handleSubmit = useCallback( + () => editHyperlink(currentUrl, currentText), + [editHyperlink, currentUrl, currentText] + ); + + return ( + + + + + ); +}; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx index f5bfe6d55f..a0c75ce5ad 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx @@ -1,4 +1,5 @@ import { ComponentPropsWithoutRef, forwardRef } from "react"; +import { mergeCSSClasses } from "@blocknote/core"; export const ImageToolbarPanel = forwardRef< HTMLDivElement, @@ -8,9 +9,7 @@ export const ImageToolbarPanel = forwardRef< return (
{children} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 9b0f2cce7f..2113f66270 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -23,6 +23,7 @@ export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkBu export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton"; export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton"; export * from "./components/HyperlinkToolbar/mantine/HyperlinkToolbar"; +export * from "./components/HyperlinkToolbar/mantine/EditLinkMenu"; export * from "./components/SideMenu/SideMenuController"; export * from "./components/SideMenu/SideMenuProps"; From c29b63fa8329d75606fd32a09c6e83bca40aa619 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Mon, 11 Mar 2024 15:36:26 +0100 Subject: [PATCH 5/8] Implemented PR feedback --- .../DefaultButtons/ReplaceImageButton.tsx | 4 +- ...ontroller.tsx => ImagePanelController.tsx} | 10 +- ...mageToolbarProps.ts => ImagePanelProps.ts} | 4 +- .../EmbedTab.tsx} | 22 ++-- .../UploadTab.tsx} | 16 +-- .../ImageToolbar/mantine/ImagePanel.tsx | 75 +++++++++++++ ...ToolbarButton.tsx => ImagePanelButton.tsx} | 2 +- ...rFileInput.tsx => ImagePanelFileInput.tsx} | 2 +- ...mageToolbarPanel.tsx => ImagePanelTab.tsx} | 2 +- ...rTextInput.tsx => ImagePanelTextInput.tsx} | 2 +- .../ImageToolbar/mantine/ImageToolbar.tsx | 105 ------------------ .../TableHandles/mantine/TableHandle.tsx | 2 - .../react/src/editor/BlockNoteDefaultUI.tsx | 4 +- packages/react/src/index.ts | 18 +-- 14 files changed, 118 insertions(+), 150 deletions(-) rename packages/react/src/components/ImageToolbar/{ImageToolbarController.tsx => ImagePanelController.tsx} (84%) rename packages/react/src/components/ImageToolbar/{ImageToolbarProps.ts => ImagePanelProps.ts} (75%) rename packages/react/src/components/ImageToolbar/mantine/{DefaultTabPanels/EmbedPanel.tsx => DefaultTabs/EmbedTab.tsx} (78%) rename packages/react/src/components/ImageToolbar/mantine/{DefaultTabPanels/UploadPanel.tsx => DefaultTabs/UploadTab.tsx} (84%) create mode 100644 packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx rename packages/react/src/components/ImageToolbar/mantine/{ImageToolbarButton.tsx => ImagePanelButton.tsx} (86%) rename packages/react/src/components/ImageToolbar/mantine/{ImageToolbarFileInput.tsx => ImagePanelFileInput.tsx} (90%) rename packages/react/src/components/ImageToolbar/mantine/{ImageToolbarPanel.tsx => ImagePanelTab.tsx} (89%) rename packages/react/src/components/ImageToolbar/mantine/{ImageToolbarTextInput.tsx => ImagePanelTextInput.tsx} (84%) delete mode 100644 packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx index d878532d87..37de520cf6 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx @@ -10,7 +10,7 @@ import { RiImageEditFill } from "react-icons/ri"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; -import { ImageToolbar } from "../../../ImageToolbar/mantine/ImageToolbar"; +import { ImagePanel } from "../../../ImageToolbar/mantine/ImagePanel"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; export const ReplaceImageButton = () => { @@ -49,7 +49,7 @@ export const ReplaceImageButton = () => { /> - + ); diff --git a/packages/react/src/components/ImageToolbar/ImageToolbarController.tsx b/packages/react/src/components/ImageToolbar/ImagePanelController.tsx similarity index 84% rename from packages/react/src/components/ImageToolbar/ImageToolbarController.tsx rename to packages/react/src/components/ImageToolbar/ImagePanelController.tsx index 67da6a8071..b8c392bdd6 100644 --- a/packages/react/src/components/ImageToolbar/ImageToolbarController.tsx +++ b/packages/react/src/components/ImageToolbar/ImagePanelController.tsx @@ -11,14 +11,14 @@ import { FC } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning"; import { useUIPluginState } from "../../hooks/useUIPluginState"; -import { ImageToolbarProps } from "./ImageToolbarProps"; -import { ImageToolbar } from "./mantine/ImageToolbar"; +import { ImagePanelProps } from "./ImagePanelProps"; +import { ImagePanel } from "./mantine/ImagePanel"; -export const ImageToolbarController = < +export const ImagePanelController = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - imageToolbar?: FC>; + imageToolbar?: FC>; }) => { const editor = useBlockNoteEditor< { image: DefaultBlockSchema["image"] }, @@ -51,7 +51,7 @@ export const ImageToolbarController = < const { show, referencePos, ...data } = state; - const Component = props.imageToolbar || ImageToolbar; + const Component = props.imageToolbar || ImagePanel; return (
diff --git a/packages/react/src/components/ImageToolbar/ImageToolbarProps.ts b/packages/react/src/components/ImageToolbar/ImagePanelProps.ts similarity index 75% rename from packages/react/src/components/ImageToolbar/ImageToolbarProps.ts rename to packages/react/src/components/ImageToolbar/ImagePanelProps.ts index 3485227125..f771fcfb79 100644 --- a/packages/react/src/components/ImageToolbar/ImageToolbarProps.ts +++ b/packages/react/src/components/ImageToolbar/ImagePanelProps.ts @@ -7,7 +7,7 @@ import { UiElementPosition, } from "@blocknote/core"; -export type ImageToolbarProps< +export type ImagePanelProps< I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema -> = Omit, keyof UiElementPosition>; \ No newline at end of file +> = Omit, keyof UiElementPosition>; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx similarity index 78% rename from packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx rename to packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx index be41ba3f07..20439ee5cf 100644 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx @@ -8,16 +8,16 @@ import { } from "@blocknote/core"; import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; -import { ImageToolbarProps } from "../../ImageToolbarProps"; -import { ImageToolbarPanel } from "../ImageToolbarPanel"; -import { ImageToolbarTextInput } from "../ImageToolbarTextInput"; -import { ImageToolbarButton } from "../ImageToolbarButton"; +import { ImagePanelProps } from "../../ImagePanelProps"; +import { ImagePanelTab } from "../ImagePanelTab"; +import { ImagePanelTextInput } from "../ImagePanelTextInput"; +import { ImagePanelButton } from "../ImagePanelButton"; -export const EmbedPanel = < +export const EmbedTab = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: ImageToolbarProps + props: ImagePanelProps ) => { const { block } = props; @@ -61,20 +61,20 @@ export const EmbedPanel = < }, [editor, block, currentURL]); return ( - - + - Embed Image - - + + ); }; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx similarity index 84% rename from packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx rename to packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx index e3e57c291d..cbc974eda7 100644 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx @@ -9,15 +9,15 @@ import { } from "@blocknote/core"; import { useCallback, useEffect, useState } from "react"; -import { ImageToolbarProps } from "../../ImageToolbarProps"; -import { ImageToolbarPanel } from "../ImageToolbarPanel"; -import { ImageToolbarFileInput } from "../ImageToolbarFileInput"; +import { ImagePanelProps } from "../../ImagePanelProps"; +import { ImagePanelTab } from "../ImagePanelTab"; +import { ImagePanelFileInput } from "../ImagePanelFileInput"; -export const UploadPanel = < +export const UploadTab = < I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >( - props: ImageToolbarProps & { + props: ImagePanelProps & { setLoading: (loading: boolean) => void; } ) => { @@ -71,8 +71,8 @@ export const UploadPanel = < ); return editor.uploadFile !== undefined ? ( - - + )} - + ) : null; }; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx b/packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx new file mode 100644 index 0000000000..043dafaaa3 --- /dev/null +++ b/packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx @@ -0,0 +1,75 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { LoadingOverlay, Tabs } from "@mantine/core"; +import { ReactNode, useState } from "react"; + +import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; +import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; +import { ImagePanelProps } from "../ImagePanelProps"; +import { UploadTab } from "./DefaultTabs/UploadTab"; +import { EmbedTab } from "./DefaultTabs/EmbedTab"; + +/** + * By default, the ImageToolbar component will render with default tabs. + * However, you can override the tabs to render by passing the `tabs` prop. You + * can use the default tab panels in the `DefaultTabPanels` directory or make + * your own using the `ImageToolbarPanel` component. + */ +export const ImagePanel = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: ImagePanelProps & { + children?: ReactNode; + } +) => { + const editor = useBlockNoteEditor< + { image: DefaultBlockSchema["image"] }, + I, + S + >(); + + const [openTab, setOpenTab] = useState("default"); + const [loading, setLoading] = useState(false); + + return ( + + {props.children !== undefined ? ( + props.children + ) : ( + + {loading && } + + + {editor.uploadFile !== undefined && ( + + Upload + + )} + + Embed + + + + {editor.uploadFile !== undefined && ( + + + + )} + + + + + )} + + ); +}; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx b/packages/react/src/components/ImageToolbar/mantine/ImagePanelButton.tsx similarity index 86% rename from packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx rename to packages/react/src/components/ImageToolbar/mantine/ImagePanelButton.tsx index 8475d5e1f8..56ea90afe6 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarButton.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImagePanelButton.tsx @@ -1,7 +1,7 @@ import { ComponentPropsWithoutRef, forwardRef } from "react"; import { Button } from "@mantine/core"; -export const ImageToolbarButton = forwardRef< +export const ImagePanelButton = forwardRef< HTMLButtonElement, Omit, "size"> >((props, ref) => ( diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx b/packages/react/src/components/ImageToolbar/mantine/ImagePanelFileInput.tsx similarity index 90% rename from packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx rename to packages/react/src/components/ImageToolbar/mantine/ImagePanelFileInput.tsx index 4453eea383..3ec61c5e72 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarFileInput.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImagePanelFileInput.tsx @@ -1,7 +1,7 @@ import { FileInput } from "@mantine/core"; import { ComponentPropsWithoutRef, forwardRef, ReactNode } from "react"; -export const ImageToolbarFileInput = forwardRef< +export const ImagePanelFileInput = forwardRef< HTMLButtonElement, Omit< ComponentPropsWithoutRef<"button">, diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx b/packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx similarity index 89% rename from packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx rename to packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx index a0c75ce5ad..92289953ec 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarPanel.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx @@ -1,7 +1,7 @@ import { ComponentPropsWithoutRef, forwardRef } from "react"; import { mergeCSSClasses } from "@blocknote/core"; -export const ImageToolbarPanel = forwardRef< +export const ImagePanelTab = forwardRef< HTMLDivElement, ComponentPropsWithoutRef<"div"> >((props, ref) => { diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx b/packages/react/src/components/ImageToolbar/mantine/ImagePanelTextInput.tsx similarity index 84% rename from packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx rename to packages/react/src/components/ImageToolbar/mantine/ImagePanelTextInput.tsx index 29f4a2ea61..c008ef7f7f 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbarTextInput.tsx +++ b/packages/react/src/components/ImageToolbar/mantine/ImagePanelTextInput.tsx @@ -1,7 +1,7 @@ import { TextInput } from "@mantine/core"; import { ComponentPropsWithoutRef, forwardRef } from "react"; -export const ImageToolbarTextInput = forwardRef< +export const ImagePanelTextInput = forwardRef< HTMLInputElement, Omit, "size"> >((props, ref) => ( diff --git a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx b/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx deleted file mode 100644 index 3e550ac87a..0000000000 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { - DefaultBlockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema, - InlineContentSchema, - StyleSchema, -} from "@blocknote/core"; -import { LoadingOverlay, Tabs } from "@mantine/core"; -import { FC, useState } from "react"; - -import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; -import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { ImageToolbarProps } from "../ImageToolbarProps"; -import { UploadPanel } from "./DefaultTabPanels/UploadPanel"; -import { EmbedPanel } from "./DefaultTabPanels/EmbedPanel"; - -/** - * By default, the ImageToolbar component will render with default tabs. - * However, you can override the tabs to render by passing the `tabs` prop. You - * can use the default tab panels in the `DefaultTabPanels` directory or make - * your own using the `ImageToolbarPanel` component. - */ -export const ImageToolbar = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: ImageToolbarProps & { - tabs?: { - tabName: string; - tabPanel: FC< - ImageToolbarProps & { - setLoading: (loading: boolean) => void; - } - >; - }[]; - } -) => { - if (props.tabs !== undefined && props.tabs.length === 0) { - throw Error( - "Prop `tabs` was passed to ImageToolbar component but contains no elements." - ); - } - - const editor = useBlockNoteEditor< - { image: DefaultBlockSchema["image"] }, - I, - S - >(); - - const [openTab, setOpenTab] = useState( - props.tabs !== undefined - ? props.tabs[0].tabName - : editor.uploadFile !== undefined - ? "upload" - : "embed" - ); - const [loading, setLoading] = useState(false); - - return ( - - - {loading && } - - - {props.tabs !== undefined ? ( - props.tabs.map(({ tabName }) => ( - {tabName} - )) - ) : ( - <> - {editor.uploadFile !== undefined && ( - - Upload - - )} - - Embed - - - )} - - - {props.tabs !== undefined ? ( - props.tabs.map(({ tabName, tabPanel }) => { - const TabPanel = tabPanel; - return ( - - - - ); - }) - ) : ( - <> - - - - - - - - )} - - - ); -}; diff --git a/packages/react/src/components/TableHandles/mantine/TableHandle.tsx b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx index 43b757a89b..7d6945e6d5 100644 --- a/packages/react/src/components/TableHandles/mantine/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx @@ -12,8 +12,6 @@ import { TableHandleProps } from "../TableHandleProps"; import { MdDragIndicator } from "react-icons/md"; import { TableHandleMenu } from "../TableHandleMenu/mantine/TableHandleMenu"; -// TODO: props.tableHandleMenu should only be available if no children are -// passed /** * By default, the TableHandle component will render with the default icon. * However, you can override the icon to render by passing children. diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index df74cc0b45..ccc6df7e97 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,7 +1,7 @@ import { filterSuggestionItems } from "@blocknote/core"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController"; import { HyperlinkToolbarController } from "../components/HyperlinkToolbar/HyperlinkToolbarController"; -import { ImageToolbarController } from "../components/ImageToolbar/ImageToolbarController"; +import { ImagePanelController } from "../components/ImageToolbar/ImagePanelController"; import { SideMenuController } from "../components/SideMenu/SideMenuController"; import { getDefaultReactSlashMenuItems } from "../components/SuggestionMenu/getDefaultReactSlashMenuItems"; import { SuggestionMenuController } from "../components/SuggestionMenu/SuggestionMenuController"; @@ -44,7 +44,7 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { )} {props.sideMenu !== false && } {editor.imageToolbar && props.imageToolbar !== false && ( - + )} {editor.tableHandles && props.tableHandles !== false && ( diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2113f66270..3f1fa47311 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -48,15 +48,15 @@ export * from "./components/SuggestionMenu/mantine/SuggestionMenu"; export * from "./components/SuggestionMenu/mantine/SuggestionMenuItem"; export * from "./components/SuggestionMenu/types"; -export * from "./components/ImageToolbar/ImageToolbarController"; -export * from "./components/ImageToolbar/ImageToolbarProps"; -export * from "./components/ImageToolbar/mantine/DefaultTabPanels/EmbedPanel"; -export * from "./components/ImageToolbar/mantine/DefaultTabPanels/UploadPanel"; -export * from "./components/ImageToolbar/mantine/ImageToolbar"; -export * from "./components/ImageToolbar/mantine/ImageToolbarButton"; -export * from "./components/ImageToolbar/mantine/ImageToolbarFileInput"; -export * from "./components/ImageToolbar/mantine/ImageToolbarPanel"; -export * from "./components/ImageToolbar/mantine/ImageToolbarTextInput"; +export * from "./components/ImageToolbar/ImagePanelController"; +export * from "./components/ImageToolbar/ImagePanelProps"; +export * from "./components/ImageToolbar/mantine/DefaultTabs/EmbedTab"; +export * from "./components/ImageToolbar/mantine/DefaultTabs/UploadTab"; +export * from "./components/ImageToolbar/mantine/ImagePanel"; +export * from "./components/ImageToolbar/mantine/ImagePanelButton"; +export * from "./components/ImageToolbar/mantine/ImagePanelFileInput"; +export * from "./components/ImageToolbar/mantine/ImagePanelTab"; +export * from "./components/ImageToolbar/mantine/ImagePanelTextInput"; export * from "./components/TableHandles/TableHandlesController"; export * from "./components/TableHandles/TableHandleProps"; From 40d5c44fee9072720df3ca4d69cb189670d522f3 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Thu, 14 Mar 2024 13:59:01 +0100 Subject: [PATCH 6/8] Implemented PR feedback --- .../ImageBlockContent/ImageBlockContent.ts | 2 +- packages/core/src/editor/BlockNoteEditor.ts | 16 ++--- .../ImageToolbarPlugin.ts | 28 ++++---- .../getDefaultSlashMenuItems.ts | 2 +- packages/core/src/index.ts | 2 +- ...nkButton.tsx => CreateHyperlinkButton.tsx} | 8 ++- .../DefaultButtons/ReplaceImageButton.tsx | 2 +- .../mantine/FormattingToolbar.tsx | 4 +- ...nkButton.tsx => DeleteHyperlinkButton.tsx} | 2 +- ...LinkButton.tsx => EditHyperlinkButton.tsx} | 6 +- ...LinkButton.tsx => OpenHyperlinkButton.tsx} | 4 +- ...EditLinkMenu.tsx => EditHyperlinkMenu.tsx} | 2 +- .../mantine/HyperlinkToolbar.tsx | 12 ++-- .../ImagePanelController.tsx | 4 +- .../ImagePanelProps.ts | 4 +- .../mantine/DefaultTabs/EmbedTab.tsx | 2 +- .../mantine/DefaultTabs/UploadTab.tsx | 0 .../mantine/ImagePanel.tsx | 7 +- .../mantine/ImagePanelButton.tsx | 0 .../mantine/ImagePanelFileInput.tsx | 0 .../mantine/ImagePanelTab.tsx | 2 +- .../mantine/ImagePanelTextInput.tsx | 0 .../react/src/editor/BlockNoteDefaultUI.tsx | 4 +- packages/react/src/editor/styles.css | 68 +++++++++++-------- packages/react/src/index.ts | 28 ++++---- 25 files changed, 111 insertions(+), 98 deletions(-) rename packages/core/src/extensions/{ImageToolbar => ImagePanel}/ImageToolbarPlugin.ts (87%) rename packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/{CreateLinkButton.tsx => CreateHyperlinkButton.tsx} (88%) rename packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/{DeleteLinkButton.tsx => DeleteHyperlinkButton.tsx} (91%) rename packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/{EditLinkButton.tsx => EditHyperlinkButton.tsx} (77%) rename packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/{OpenLinkButton.tsx => OpenHyperlinkButton.tsx} (81%) rename packages/react/src/components/HyperlinkToolbar/mantine/{EditLinkMenu.tsx => EditHyperlinkMenu.tsx} (98%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/ImagePanelController.tsx (94%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/ImagePanelProps.ts (77%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/DefaultTabs/EmbedTab.tsx (97%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/DefaultTabs/UploadTab.tsx (100%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/ImagePanel.tsx (92%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/ImagePanelButton.tsx (100%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/ImagePanelFileInput.tsx (100%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/ImagePanelTab.tsx (82%) rename packages/react/src/components/{ImageToolbar => ImagePanel}/mantine/ImagePanelTextInput.tsx (100%) diff --git a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts index 8bfc3d4678..21f8eeeb00 100644 --- a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts +++ b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts @@ -234,7 +234,7 @@ export const renderImage = ( // Opens the image toolbar. const addImageButtonClickHandler = () => { editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setMeta(editor.imageToolbar!.plugin, { + editor._tiptapEditor.state.tr.setMeta(editor.imagePanel!.plugin, { block: block, }) ); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 65596426c7..8950d706ef 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -28,9 +28,9 @@ import { } from "../blocks/defaultBlocks"; 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 { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin"; +import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin"; import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin"; import { UniqueID } from "../extensions/UniqueID/UniqueID"; import { @@ -54,14 +54,15 @@ import { TextCursorPosition } from "./cursorPositionTypes"; import { Selection } from "./selectionTypes"; import { transformPasted } from "./transformPasted"; -// CSS import { checkDefaultBlockTypeInSchema } from "../blocks/defaultBlockTypeGuards"; -import "./Block.css"; import { BlockNoteSchema } from "./BlockNoteSchema"; import { BlockNoteTipTapEditor, BlockNoteTipTapEditorOptions, } from "./BlockNoteTipTapEditor"; + +// CSS +import "./Block.css"; import "./editor.css"; export type BlockNoteEditorOptions< @@ -171,10 +172,7 @@ export class BlockNoteEditor< ISchema, SSchema >; - public readonly imageToolbar?: ImageToolbarProsemirrorPlugin< - ISchema, - SSchema - >; + public readonly imagePanel?: ImagePanelProsemirrorPlugin; public readonly tableHandles?: TableHandlesProsemirrorPlugin< ISchema, SSchema @@ -237,7 +235,7 @@ export class BlockNoteEditor< this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this); if (checkDefaultBlockTypeInSchema("image", this)) { // Type guards only work on `const`s? Not working for `this` - this.imageToolbar = new ImageToolbarProsemirrorPlugin(this as any); + this.imagePanel = new ImagePanelProsemirrorPlugin(this as any); } if (checkDefaultBlockTypeInSchema("table", this)) { this.tableHandles = new TableHandlesProsemirrorPlugin(this as any); @@ -263,7 +261,7 @@ export class BlockNoteEditor< this.hyperlinkToolbar.plugin, this.sideMenu.plugin, this.suggestionMenus.plugin, - ...(this.imageToolbar ? [this.imageToolbar.plugin] : []), + ...(this.imagePanel ? [this.imagePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), ]; }, diff --git a/packages/core/src/extensions/ImageToolbar/ImageToolbarPlugin.ts b/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts similarity index 87% rename from packages/core/src/extensions/ImageToolbar/ImageToolbarPlugin.ts rename to packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts index 26a28c13c2..516f7f2852 100644 --- a/packages/core/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +++ b/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts @@ -11,7 +11,7 @@ import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; import { EventEmitter } from "../../util/EventEmitter"; import { DefaultBlockSchema } from "../../blocks/defaultBlocks"; -export type ImageToolbarState< +export type ImagePanelState< I extends InlineContentSchema, S extends StyleSchema > = UiElementPosition & { @@ -19,11 +19,11 @@ export type ImageToolbarState< block: BlockFromConfig; }; -export class ImageToolbarView< +export class ImagePanelView< I extends InlineContentSchema, S extends StyleSchema > { - public state?: ImageToolbarState; + public state?: ImagePanelState; public emitUpdate: () => void; public prevWasEditable: boolean | null = null; @@ -31,11 +31,11 @@ export class ImageToolbarView< constructor( private readonly pluginKey: PluginKey, private readonly pmView: EditorView, - emitUpdate: (state: ImageToolbarState) => void + emitUpdate: (state: ImagePanelState) => void ) { this.emitUpdate = () => { if (!this.state) { - throw new Error("Attempting to update uninitialized image toolbar"); + throw new Error("Attempting to update uninitialized image panel"); } emitUpdate(this.state); @@ -69,7 +69,7 @@ export class ImageToolbarView< const editorWrapper = this.pmView.dom.parentElement!; // Checks if the focus is moving to an element outside the editor. If it is, - // the toolbar is hidden. + // the panel is hidden. if ( // An element is clicked. event && @@ -142,13 +142,13 @@ export class ImageToolbarView< } } -const imageToolbarPluginKey = new PluginKey("ImageToolbarPlugin"); +const imagePanelPluginKey = new PluginKey("ImagePanelPlugin"); -export class ImageToolbarProsemirrorPlugin< +export class ImagePanelProsemirrorPlugin< I extends InlineContentSchema, S extends StyleSchema > extends EventEmitter { - private view: ImageToolbarView | undefined; + private view: ImagePanelView | undefined; public readonly plugin: Plugin; constructor( @@ -158,11 +158,11 @@ export class ImageToolbarProsemirrorPlugin< this.plugin = new Plugin<{ block: BlockFromConfig | undefined; }>({ - key: imageToolbarPluginKey, + key: imagePanelPluginKey, view: (editorView) => { - this.view = new ImageToolbarView( + this.view = new ImagePanelView( // editor, - imageToolbarPluginKey, + imagePanelPluginKey, editorView, (state) => { this.emit("update", state); @@ -179,7 +179,7 @@ export class ImageToolbarProsemirrorPlugin< apply: (transaction) => { const block: | BlockFromConfig - | undefined = transaction.getMeta(imageToolbarPluginKey)?.block; + | undefined = transaction.getMeta(imagePanelPluginKey)?.block; return { block, @@ -189,7 +189,7 @@ export class ImageToolbarProsemirrorPlugin< }); } - public onUpdate(callback: (state: ImageToolbarState) => void) { + public onUpdate(callback: (state: ImagePanelState) => void) { return this.on("update", callback); } } diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index d110436173..a56363634e 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -205,7 +205,7 @@ export function getDefaultSlashMenuItems< // Immediately open the image toolbar editor.prosemirrorView.dispatch( - editor._tiptapEditor.state.tr.setMeta(editor.imageToolbar!.plugin, { + editor._tiptapEditor.state.tr.setMeta(editor.imagePanel!.plugin, { block: insertedBlock, }) ); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0a809fae0d..cc959ce3ac 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -12,7 +12,7 @@ export * from "./editor/selectionTypes"; export * from "./extensions-shared/UiElementPosition"; export * from "./extensions/FormattingToolbar/FormattingToolbarPlugin"; export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; -export * from "./extensions/ImageToolbar/ImageToolbarPlugin"; +export * from "./extensions/ImagePanel/ImageToolbarPlugin"; export * from "./extensions/SideMenu/SideMenuPlugin"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem"; export * from "./extensions/SuggestionMenu/SuggestionPlugin"; diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx similarity index 88% rename from packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx rename to packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx index 000e7a635e..4026db749d 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx @@ -13,10 +13,10 @@ import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorCo import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; -import { EditLinkMenu } from "../../../HyperlinkToolbar/mantine/EditLinkMenu"; +import { EditHyperlinkMenu } from "../../../HyperlinkToolbar/mantine/EditHyperlinkMenu"; // TODO: Make sure Link is in inline content schema -export const CreateLinkButton = () => { +export const CreateHyperlinkButton = () => { const editor = useBlockNoteEditor< BlockSchema, InlineContentSchema, @@ -64,7 +64,9 @@ export const CreateLinkButton = () => { icon={RiLink} /> } - dropdown={} + dropdown={ + + } /> ); }; diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx index 37de520cf6..b5dba08d3c 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx @@ -10,7 +10,7 @@ import { RiImageEditFill } from "react-icons/ri"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; -import { ImagePanel } from "../../../ImageToolbar/mantine/ImagePanel"; +import { ImagePanel } from "../../../ImagePanel/mantine/ImagePanel"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; export const ReplaceImageButton = () => { diff --git a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx index 8ab3fb8b8b..a7d2bbac7a 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx @@ -4,7 +4,7 @@ import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { FormattingToolbarProps } from "../FormattingToolbarProps"; import { BasicTextStyleButton } from "./DefaultButtons/BasicTextStyleButton"; import { ColorStyleButton } from "./DefaultButtons/ColorStyleButton"; -import { CreateLinkButton } from "./DefaultButtons/CreateLinkButton"; +import { CreateHyperlinkButton } from "./DefaultButtons/CreateHyperlinkButton"; import { ImageCaptionButton } from "./DefaultButtons/ImageCaptionButton"; import { NestBlockButton, @@ -39,7 +39,7 @@ export const getFormattingToolbarItems = ( , , , - , + , ]; // TODO: props.blockTypeDropdownItems should only be available if no children diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx similarity index 91% rename from packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx rename to packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx index a5e74e430a..34aee50f1f 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx @@ -2,7 +2,7 @@ import { RiLinkUnlink } from "react-icons/ri"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; -export const DeleteLinkButton = ( +export const DeleteHyperlinkButton = ( props: Pick ) => ( ) => ( } - dropdown={} + dropdown={} /> ); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx similarity index 81% rename from packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx rename to packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx index 196c90fb57..788dc8724f 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx @@ -2,7 +2,9 @@ import { RiExternalLinkFill } from "react-icons/ri"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; -export const OpenLinkButton = (props: Pick) => ( +export const OpenHyperlinkButton = ( + props: Pick +) => ( ) => { const { url, text, editHyperlink } = props; diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx index 52e8c57c8a..92faabde28 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx @@ -2,9 +2,9 @@ import { ReactNode } from "react"; import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { EditLinkButton } from "./DefaultButtons/EditLinkButton"; -import { OpenLinkButton } from "./DefaultButtons/OpenLinkButton"; -import { DeleteLinkButton } from "./DefaultButtons/DeleteLinkButton"; +import { EditHyperlinkButton } from "./DefaultButtons/EditHyperlinkButton"; +import { OpenHyperlinkButton } from "./DefaultButtons/OpenHyperlinkButton"; +import { DeleteHyperlinkButton } from "./DefaultButtons/DeleteHyperlinkButton"; /** * By default, the HyperlinkToolbar component will render with default buttons. @@ -28,13 +28,13 @@ export const HyperlinkToolbar = ( - - - + + ); }; diff --git a/packages/react/src/components/ImageToolbar/ImagePanelController.tsx b/packages/react/src/components/ImagePanel/ImagePanelController.tsx similarity index 94% rename from packages/react/src/components/ImageToolbar/ImagePanelController.tsx rename to packages/react/src/components/ImagePanel/ImagePanelController.tsx index b8c392bdd6..adadd8a364 100644 --- a/packages/react/src/components/ImageToolbar/ImagePanelController.tsx +++ b/packages/react/src/components/ImagePanel/ImagePanelController.tsx @@ -26,14 +26,14 @@ export const ImagePanelController = < S >(); - if (!editor.imageToolbar) { + if (!editor.imagePanel) { throw new Error( "ImageToolbarController can only be used when BlockNote editor schema contains image block" ); } const state = useUIPluginState( - editor.imageToolbar.onUpdate.bind(editor.imageToolbar) + editor.imagePanel.onUpdate.bind(editor.imagePanel) ); const { isMounted, ref, style } = useUIElementPositioning( state?.show || false, diff --git a/packages/react/src/components/ImageToolbar/ImagePanelProps.ts b/packages/react/src/components/ImagePanel/ImagePanelProps.ts similarity index 77% rename from packages/react/src/components/ImageToolbar/ImagePanelProps.ts rename to packages/react/src/components/ImagePanel/ImagePanelProps.ts index f771fcfb79..3ee608a3fa 100644 --- a/packages/react/src/components/ImageToolbar/ImagePanelProps.ts +++ b/packages/react/src/components/ImagePanel/ImagePanelProps.ts @@ -1,7 +1,7 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, - ImageToolbarState, + ImagePanelState, InlineContentSchema, StyleSchema, UiElementPosition, @@ -10,4 +10,4 @@ import { export type ImagePanelProps< I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema -> = Omit, keyof UiElementPosition>; +> = Omit, keyof UiElementPosition>; diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx similarity index 97% rename from packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx rename to packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx index 20439ee5cf..da4db82ac9 100644 --- a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/EmbedTab.tsx +++ b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx @@ -70,7 +70,7 @@ export const EmbedTab = < data-test={"embed-input"} /> Embed Image diff --git a/packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx similarity index 100% rename from packages/react/src/components/ImageToolbar/mantine/DefaultTabs/UploadTab.tsx rename to packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx similarity index 92% rename from packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx rename to packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx index 043dafaaa3..d5dd599bf8 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImagePanel.tsx +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx @@ -5,11 +5,10 @@ import { InlineContentSchema, StyleSchema, } from "@blocknote/core"; -import { LoadingOverlay, Tabs } from "@mantine/core"; +import { Group, LoadingOverlay, Tabs } from "@mantine/core"; import { ReactNode, useState } from "react"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; -import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { ImagePanelProps } from "../ImagePanelProps"; import { UploadTab } from "./DefaultTabs/UploadTab"; import { EmbedTab } from "./DefaultTabs/EmbedTab"; @@ -38,7 +37,7 @@ export const ImagePanel = < const [loading, setLoading] = useState(false); return ( - + {props.children !== undefined ? ( props.children ) : ( @@ -70,6 +69,6 @@ export const ImagePanel = < )} - + ); }; diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanelButton.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx similarity index 100% rename from packages/react/src/components/ImageToolbar/mantine/ImagePanelButton.tsx rename to packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanelFileInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx similarity index 100% rename from packages/react/src/components/ImageToolbar/mantine/ImagePanelFileInput.tsx rename to packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx similarity index 82% rename from packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx rename to packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx index 92289953ec..29e4689c39 100644 --- a/packages/react/src/components/ImageToolbar/mantine/ImagePanelTab.tsx +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx @@ -9,7 +9,7 @@ export const ImagePanelTab = forwardRef< return (
{children} diff --git a/packages/react/src/components/ImageToolbar/mantine/ImagePanelTextInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx similarity index 100% rename from packages/react/src/components/ImageToolbar/mantine/ImagePanelTextInput.tsx rename to packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index ccc6df7e97..bf165aa616 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,7 +1,7 @@ import { filterSuggestionItems } from "@blocknote/core"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController"; import { HyperlinkToolbarController } from "../components/HyperlinkToolbar/HyperlinkToolbarController"; -import { ImagePanelController } from "../components/ImageToolbar/ImagePanelController"; +import { ImagePanelController } from "../components/ImagePanel/ImagePanelController"; import { SideMenuController } from "../components/SideMenu/SideMenuController"; import { getDefaultReactSlashMenuItems } from "../components/SuggestionMenu/getDefaultReactSlashMenuItems"; import { SuggestionMenuController } from "../components/SuggestionMenu/SuggestionMenuController"; @@ -43,7 +43,7 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { /> )} {props.sideMenu !== false && } - {editor.imageToolbar && props.imageToolbar !== false && ( + {editor.imagePanel && props.imageToolbar !== false && ( )} {editor.tableHandles && props.tableHandles !== false && ( diff --git a/packages/react/src/editor/styles.css b/packages/react/src/editor/styles.css index 1df01bc23d..3bb2f0e679 100644 --- a/packages/react/src/editor/styles.css +++ b/packages/react/src/editor/styles.css @@ -414,34 +414,6 @@ color: var(--bn-colors-menu-text); } -.bn-container .bn-image-toolbar { - width: 500px; -} - -.bn-container .bn-image-toolbar .bn-image-toolbar-panel { - align-items: center; - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; -} - -.bn-container .bn-image-toolbar .mantine-TextInput-root, -.bn-container .bn-image-toolbar .mantine-FileInput-root { - width: 100%; -} - -.bn-container .bn-image-toolbar .mantine-Button-root { - border: solid var(--bn-colors-border) 1px; - border-radius: var(--bn-border-radius-small); - height: 32px; - width: 60%; -} - -.bn-container .bn-image-toolbar .mantine-Text-root { - text-align: center; -} - /* Slash Menu styling*/ .bn-container .bn-slash-menu { max-height: 100%; @@ -498,6 +470,46 @@ width: 24px; } +/* Image Panel styling*/ +.bn-container .bn-image-panel { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + padding: 2px; + width: 500px; +} + +.bn-container .bn-image-panel .bn-image-panel-tab { + align-items: center; + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; +} + +.bn-container .bn-image-panel .mantine-TextInput-root, +.bn-container .bn-image-panel .mantine-FileInput-root { + width: 100%; +} + +.bn-container .bn-image-panel .mantine-Button-root { + background-color: var(--bn-colors-menu-background); + border: solid var(--bn-colors-border) 1px; + border-radius: var(--bn-border-radius-small); + color: var(--bn-colors-menu-text); + height: 32px; + width: 60%; +} + +.bn-container .bn-image-panel .mantine-Button-root:hover { + background-color: var(--bn-colors-hovered-background); +} + +.bn-container .bn-image-panel .mantine-Text-root { + text-align: center; +} + /* Table Handle styling */ .bn-container .bn-table-handle { align-items: center; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 3f1fa47311..786ba9d178 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -9,7 +9,7 @@ export * from "./components/FormattingToolbar/FormattingToolbarController"; export * from "./components/FormattingToolbar/FormattingToolbarProps"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/BasicTextStyleButton"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/ColorStyleButton"; -export * from "./components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton"; +export * from "./components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/ImageCaptionButton"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/NestBlockButtons"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton"; @@ -19,11 +19,11 @@ export * from "./components/FormattingToolbar/mantine/FormattingToolbar"; export * from "./components/HyperlinkToolbar/HyperlinkToolbarController"; export * from "./components/HyperlinkToolbar/HyperlinkToolbarProps"; -export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/DeleteLinkButton"; -export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/EditLinkButton"; -export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/OpenLinkButton"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton"; +export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton"; export * from "./components/HyperlinkToolbar/mantine/HyperlinkToolbar"; -export * from "./components/HyperlinkToolbar/mantine/EditLinkMenu"; +export * from "./components/HyperlinkToolbar/mantine/EditHyperlinkMenu"; export * from "./components/SideMenu/SideMenuController"; export * from "./components/SideMenu/SideMenuProps"; @@ -48,15 +48,15 @@ export * from "./components/SuggestionMenu/mantine/SuggestionMenu"; export * from "./components/SuggestionMenu/mantine/SuggestionMenuItem"; export * from "./components/SuggestionMenu/types"; -export * from "./components/ImageToolbar/ImagePanelController"; -export * from "./components/ImageToolbar/ImagePanelProps"; -export * from "./components/ImageToolbar/mantine/DefaultTabs/EmbedTab"; -export * from "./components/ImageToolbar/mantine/DefaultTabs/UploadTab"; -export * from "./components/ImageToolbar/mantine/ImagePanel"; -export * from "./components/ImageToolbar/mantine/ImagePanelButton"; -export * from "./components/ImageToolbar/mantine/ImagePanelFileInput"; -export * from "./components/ImageToolbar/mantine/ImagePanelTab"; -export * from "./components/ImageToolbar/mantine/ImagePanelTextInput"; +export * from "./components/ImagePanel/ImagePanelController"; +export * from "./components/ImagePanel/ImagePanelProps"; +export * from "./components/ImagePanel/mantine/DefaultTabs/EmbedTab"; +export * from "./components/ImagePanel/mantine/DefaultTabs/UploadTab"; +export * from "./components/ImagePanel/mantine/ImagePanel"; +export * from "./components/ImagePanel/mantine/ImagePanelButton"; +export * from "./components/ImagePanel/mantine/ImagePanelFileInput"; +export * from "./components/ImagePanel/mantine/ImagePanelTab"; +export * from "./components/ImagePanel/mantine/ImagePanelTextInput"; export * from "./components/TableHandles/TableHandlesController"; export * from "./components/TableHandles/TableHandleProps"; From 3346ee0ac46a6153311711bc2d69b323795437c3 Mon Sep 17 00:00:00 2001 From: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com> Date: Thu, 14 Mar 2024 18:27:35 +0100 Subject: [PATCH 7/8] refactor: Clean up component code (#637) * Cleaned up component code * Renamed "Link" in components to "Hyperlink" * Implemented PR feedback * alternative solution for toolbar fadeout (#645) * chore: Playwright update (#640) * Updated playwright * Small fix * dont upgrade tiptap etc * fix pw install? --------- Co-authored-by: yousefed * fix `setEditable` called when not necessary (#635) * Added temp fix for shortcuts and input rules in tables (#561) --------- Co-authored-by: Yousef --- .github/workflows/build.yml | 2 +- .../docs/ui-components/formatting-toolbar.mdx | 10 +- .../docs/ui-components/hyperlink-toolbar.mdx | 4 +- .../02-formatting-toolbar-buttons/App.tsx | 8 +- .../App.tsx | 16 +- .../README.md | 8 +- .../index.html | 2 +- .../05-custom-schema/03-font-style/App.tsx | 8 +- package-lock.json | 1375 ++++++++++++++++- package.json | 2 +- .../src/api/getCurrentBlockContentType.ts | 14 + .../HeadingBlockContent.ts | 71 +- .../BulletListItemBlockContent.ts | 23 +- .../NumberedListItemBlockContent.ts | 23 +- .../ParagraphBlockContent.ts | 19 +- packages/core/src/editor/BlockNoteEditor.ts | 4 +- .../FormattingToolbarController.tsx | 21 +- .../FormattingToolbarProps.ts | 4 +- .../DefaultButtons/CreateHyperlinkButton.tsx | 12 +- .../DefaultButtons/ImageCaptionButton.tsx | 36 +- .../BlockTypeSelect.tsx} | 55 +- .../mantine/FormattingToolbar.tsx | 24 +- .../DefaultButtons/EditHyperlinkButton.tsx | 10 +- .../mantine/EditHyperlinkMenu.tsx | 80 - .../mantine/EditHyperlinkMenuItems.tsx | 75 + .../mantine/HyperlinkToolbar.tsx | 4 +- .../hooks/useTableHandlesPositioning.ts | 2 +- .../Toolbar/ToolbarDropdown.tsx | 47 - .../Toolbar/ToolbarDropdownTarget.tsx | 39 - .../Toolbar/ToolbarInputDropdown.tsx | 25 - .../Toolbar/ToolbarInputDropdownButton.tsx | 42 - .../Toolbar/ToolbarInputsMenu.tsx | 22 + ...downItem.tsx => ToolbarInputsMenuItem.tsx} | 14 +- .../mantine-shared/Toolbar/ToolbarSelect.tsx | 57 + ...DropdownItem.tsx => ToolbarSelectItem.tsx} | 7 +- .../src/hooks/useUIElementPositioning.ts | 2 +- packages/react/src/index.ts | 6 +- playground/src/examples.gen.tsx | 2 +- tests/package.json | 6 +- .../slash-menu-end-product-webkit-linux.png | Bin 42542 -> 42543 bytes .../dark-drag-handle-menu-firefox-linux.png | Bin 24311 -> 28123 bytes .../dark-image-toolbar-firefox-linux.png | Bin 29329 -> 30568 bytes .../dark-side-menu-firefox-linux.png | Bin 19824 -> 23843 bytes 43 files changed, 1686 insertions(+), 495 deletions(-) create mode 100644 packages/core/src/api/getCurrentBlockContentType.ts rename packages/react/src/components/FormattingToolbar/mantine/{DefaultDropdowns/BlockTypeDropdown.tsx => DefaultSelects/BlockTypeSelect.tsx} (66%) create mode 100644 packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenuItems.tsx delete mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdown.tsx delete mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownTarget.tsx delete mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdown.tsx delete mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownButton.tsx create mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenu.tsx rename packages/react/src/components/mantine-shared/Toolbar/{ToolbarInputDropdownItem.tsx => ToolbarInputsMenuItem.tsx} (63%) create mode 100644 packages/react/src/components/mantine-shared/Toolbar/ToolbarSelect.tsx rename packages/react/src/components/mantine-shared/Toolbar/{ToolbarDropdownItem.tsx => ToolbarSelectItem.tsx} (79%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 047c51cfc8..16cdab8558 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: CI: true - name: Install Playwright - run: npx playwright install --with-deps + run: npm run install-playwright - name: Run Playwright tests working-directory: ./tests diff --git a/docs/pages/docs/ui-components/formatting-toolbar.mdx b/docs/pages/docs/ui-components/formatting-toolbar.mdx index e9e2559ee4..2f2ee74ad3 100644 --- a/docs/pages/docs/ui-components/formatting-toolbar.mdx +++ b/docs/pages/docs/ui-components/formatting-toolbar.mdx @@ -32,16 +32,16 @@ Setting `formattingToolbar={false}` on `BlockNoteView` tells BlockNote not to sh
Tip: The children you pass to the `FormattingToolbar` component - should be default dropdowns/buttons (e.g. `BlockTypeDropdown` & `BasicTextStyleButton`) or custom dropdowns/buttons - (`ToolbarDropdown` & `ToolbarButton`). To see all the components you can use, head to the + should be default selects/buttons (e.g. `BlockTypeSelect` & `BasicTextStyleButton`) or custom selects/buttons + (`ToolbarSelect` & `ToolbarButton`). To see all the components you can use, head to the [Formatting Toolbar's source code](https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx).
-## Changing Block Type Dropdown Items +## Changing Block Type Select (Dropdown) Items -The first element in the default Formatting Toolbar is the Block Type Dropdown, and you can change the items in it. The demo makes the Block Type Dropdown work for image blocks by adding an item to it. +The first element in the default Formatting Toolbar is the Block Type Select, and you can change the items in it. The demo makes the Block Type Select work for image blocks by adding an item to it. -Here, we use the `FormattingToolbar` component but keep the default buttons (we don't pass any children). Instead, we pass our customized Block Type Dropdown items using the `blockTypeDropdownItems` prop. +Here, we use the `FormattingToolbar` component but keep the default buttons (we don't pass any children). Instead, we pass our customized Block Type Select items using the `blockTypeSelectItems` prop. diff --git a/docs/pages/docs/ui-components/hyperlink-toolbar.mdx b/docs/pages/docs/ui-components/hyperlink-toolbar.mdx index e7f5418139..0b9572bcbf 100644 --- a/docs/pages/docs/ui-components/hyperlink-toolbar.mdx +++ b/docs/pages/docs/ui-components/hyperlink-toolbar.mdx @@ -30,8 +30,8 @@ Setting `hyperlinkToolbar={false}` on `BlockNoteView` tells BlockNote not to sho
Tip: The children you pass to the `HyperlinkToolbar` - component should be default buttons (e.g. TODO) or custom dropdowns/buttons - (`ToolbarDropdown` & `ToolbarButton`). To see all the components you can + component should be default buttons (e.g. TODO) or custom selects/buttons + (`ToolbarSelect` & `ToolbarButton`). To see all the components you can use, head to the [Hyperlink Toolbar's source code](link).
diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx index e34a0d9cdf..e52277e6f1 100644 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx @@ -2,9 +2,9 @@ import "@blocknote/core/fonts/inter.css"; import { BasicTextStyleButton, BlockNoteView, - BlockTypeDropdown, + BlockTypeSelect, ColorStyleButton, - CreateLinkButton, + CreateHyperlinkButton, FormattingToolbar, FormattingToolbarController, ImageCaptionButton, @@ -72,7 +72,7 @@ export default function App() { ( - + {/* Extra button to toggle blue text & background */} @@ -120,7 +120,7 @@ export default function App() { - + )} /> diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx b/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx index 315e4afc5b..0ede58d56e 100644 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx +++ b/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx @@ -2,11 +2,11 @@ import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView, - BlockTypeDropdownItem, FormattingToolbar, FormattingToolbarController, - blockTypeDropdownItems, useCreateBlockNote, + blockTypeSelectItems, + BlockTypeSelectItem, } from "@blocknote/react"; import "@blocknote/react/style.css"; import { RiAlertFill } from "react-icons/ri"; @@ -36,12 +36,12 @@ export default function App() { { type: "paragraph", content: - "Try selecting some text - you'll see the new 'Alert' item in the Block Type Dropdown", + "Try selecting some text - you'll see the new 'Alert' item in the Block Type Select", }, { type: "alert", content: - "Or select text in this alert - the Block Type Dropdown also appears", + "Or select text in this alert - the Block Type Select also appears", }, { type: "paragraph", @@ -49,20 +49,20 @@ export default function App() { ], }); - // Renders the editor instance with the updated Block Type Dropdown. + // Renders the editor instance with the updated Block Type Select. return ( ( block.type === "alert", - } satisfies BlockTypeDropdownItem, + } satisfies BlockTypeSelectItem, ]} /> )} diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md b/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md index 28a7a27833..005e3bc916 100644 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md +++ b/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md @@ -1,11 +1,11 @@ -# Adding Block Type Dropdown Items +# Adding Block Type Select Items -In this example, we add an item to the Block Type Dropdown, so that it works for a custom alert block we create. +In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create. -**Try it out:** Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Dropdown to change the selected block! +**Try it out:** Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Select to change the selected block! **Relevant Docs:** -- [Changing Block Type Dropdown Items](/docs/ui-components/formatting-toolbar#changing-block-type-dropdown-items) +- [Changing Block Type Select Items](/docs/ui-components/formatting-toolbar#changing-block-type-select-items) - [Custom Block Types](/docs/custom-schemas/custom-blocks) - [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html b/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html index 258dfd61f4..1e84936eca 100644 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html +++ b/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html @@ -5,7 +5,7 @@ - Adding Block Type Dropdown Items + Adding Block Type Select Items
diff --git a/examples/05-custom-schema/03-font-style/App.tsx b/examples/05-custom-schema/03-font-style/App.tsx index 1b8c79a96f..1c596a1c51 100644 --- a/examples/05-custom-schema/03-font-style/App.tsx +++ b/examples/05-custom-schema/03-font-style/App.tsx @@ -3,9 +3,9 @@ import "@blocknote/core/fonts/inter.css"; import { BasicTextStyleButton, BlockNoteView, - BlockTypeDropdown, + BlockTypeSelect, ColorStyleButton, - CreateLinkButton, + CreateHyperlinkButton, FormattingToolbar, FormattingToolbarController, ImageCaptionButton, @@ -99,7 +99,7 @@ export default function App() { ( - + @@ -141,7 +141,7 @@ export default function App() { - + )} /> diff --git a/package-lock.json b/package-lock.json index 7368362e4c..768274d61f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2406,6 +2406,70 @@ "license": "MIT", "optional": true }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "cpu": [ @@ -2421,6 +2485,294 @@ "node": ">=12" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -4507,58 +4859,13 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@playwright/experimental-ct-core": { - "version": "1.40.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.40.1", - "playwright-core": "1.40.1", - "vite": "^4.4.10" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@playwright/experimental-ct-react": { - "version": "1.40.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@playwright/experimental-ct-core": "1.40.1", - "@vitejs/plugin-react": "^4.0.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@playwright/test": { - "version": "1.40.1", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.40.1" - }, - "bin": { - "playwright": "cli.js" - }, + "license": "MIT", + "optional": true, "engines": { - "node": ">=16" + "node": ">=14" } }, "node_modules/@polka/url": { @@ -8587,6 +8894,342 @@ "@esbuild/win32-x64": "0.18.20" } }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -15602,49 +16245,379 @@ "protocols": "^2.0.0" } }, - "node_modules/parse-url": { - "version": "8.1.0", - "license": "MIT", - "dependencies": { - "parse-path": "^7.0.0" + "node_modules/parse-url": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "parse-path": "^7.0.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/partykit": { + "version": "0.0.84", + "license": "MIT", + "dependencies": { + "@cloudflare/workers-types": "4.20240129.0", + "clipboardy": "4.0.0", + "esbuild": "0.20.0", + "miniflare": "3.20240129.0", + "yoga-wasm-web": "0.3.3" + }, + "bin": { + "partykit": "dist/bin.mjs" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/partykit/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", + "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-arm": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", + "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", + "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", + "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.0", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/darwin-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", + "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", + "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", + "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-arm": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", + "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", + "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-ia32": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", + "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-loong64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", + "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", + "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", + "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", + "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-s390x": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", + "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", + "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", + "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", + "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/sunos-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", + "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" } }, - "node_modules/parse5": { - "version": "7.1.2", - "license": "MIT", - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "node_modules/partykit/node_modules/@esbuild/win32-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", + "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/partykit": { - "version": "0.0.84", - "license": "MIT", - "dependencies": { - "@cloudflare/workers-types": "4.20240129.0", - "clipboardy": "4.0.0", - "esbuild": "0.20.0", - "miniflare": "3.20240129.0", - "yoga-wasm-web": "0.3.3" - }, - "bin": { - "partykit": "dist/bin.mjs" - }, - "optionalDependencies": { - "fsevents": "2.3.3" + "node_modules/partykit/node_modules/@esbuild/win32-ia32": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", + "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/partykit/node_modules/@esbuild/darwin-arm64": { + "node_modules/partykit/node_modules/@esbuild/win32-x64": { "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", + "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", "cpu": [ - "arm64" + "x64" ], - "license": "MIT", "optional": true, "os": [ - "darwin" + "win32" ], "engines": { "node": ">=12" @@ -16184,6 +17157,8 @@ "version": "1.40.1", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "playwright-core": "1.40.1" }, @@ -16201,6 +17176,8 @@ "version": "1.40.1", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -18154,7 +19131,6 @@ "version": "4.9.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.5" }, @@ -21458,13 +22434,187 @@ "devDependencies": { "@blocknote/core": "^0.12.0", "@blocknote/react": "^0.12.0", - "@playwright/experimental-ct-react": "^1.38.1", - "@playwright/test": "^1.38.1", + "@playwright/experimental-ct-react": "^1.42.1", + "@playwright/test": "^1.42.1", "eslint": "^8.10.0", "react-icons": "^4.3.1", "rimraf": "^5.0.5" } }, + "tests/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "tests/node_modules/@playwright/experimental-ct-core": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.42.1.tgz", + "integrity": "sha512-wBS6pzgJwRuK0MwgiooamLW0prQLgT0RAhxooeMyqU97VbDI5aYuuynlxobJDDymU4HcOKARg2rG8h6jX7ShrA==", + "dev": true, + "dependencies": { + "playwright": "1.42.1", + "playwright-core": "1.42.1", + "vite": "^5.0.12" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "tests/node_modules/@playwright/experimental-ct-react": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.42.1.tgz", + "integrity": "sha512-/dxu7nkGOlZIx9MUGQPM5kiIGksYIBaaJKQuNmaJNV29V3VGI1p9yQqrvl1jFK/Hu7SC1LmFB8RlkrOgMiZKYg==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.42.1", + "@vitejs/plugin-react": "^4.2.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "tests/node_modules/@playwright/test": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "dev": true, + "dependencies": { + "playwright": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "tests/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "tests/node_modules/playwright": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "dev": true, + "dependencies": { + "playwright-core": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "tests/node_modules/playwright-core": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "tests/node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "tests/node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "tests/node_modules/rimraf": { "version": "5.0.5", "dev": true, @@ -21481,6 +22631,61 @@ "funding": { "url": "https://github.com/sponsors/isaacs" } + }, + "tests/node_modules/vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index e17267e7e8..d7f5d213f2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "bootstrap": "lerna bootstrap --ci -- --force && patch-package", "install-new-packages": "lerna bootstrap -- --force && patch-package", "test": "lerna run --stream test", - "install-playwright": "npx playwright install --with-deps", + "install-playwright": "cd tests && npx playwright install --with-deps", "deploy": "lerna publish -- --access public", "prepublishOnly": "npm run build && cp README.md packages/core/README.md && cp README.md packages/react/README.md", "postpublish": "rm -rf packages/core/README.md && rm -rf packages/react/README.md", diff --git a/packages/core/src/api/getCurrentBlockContentType.ts b/packages/core/src/api/getCurrentBlockContentType.ts new file mode 100644 index 0000000000..caccc1aee4 --- /dev/null +++ b/packages/core/src/api/getCurrentBlockContentType.ts @@ -0,0 +1,14 @@ +import { Editor } from "@tiptap/core"; +import { getBlockInfoFromPos } from "./getBlockInfoFromPos"; + +// Used to get the content type of the block that the text cursor is in. This is +// a band-aid fix to prevent input rules and keyboard shortcuts from triggering +// in tables, but really those should be extended to work with block selections. +export const getCurrentBlockContentType = (editor: Editor) => { + const { contentType } = getBlockInfoFromPos( + editor.state.doc, + editor.state.selection.from + ); + + return contentType.spec.content; +}; diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 9b38c720f7..b70c9a8566 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -6,6 +6,7 @@ import { } from "../../schema"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers"; import { defaultProps } from "../defaultProps"; +import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType"; export const headingPropSchema = { ...defaultProps, @@ -45,6 +46,10 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ return new InputRule({ find: new RegExp(`^(#{${level}})\\s$`), handler: ({ state, chain, range }) => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return; + } + chain() .BNUpdateBlock(state.selection.from, { type: "heading", @@ -62,27 +67,51 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - "Mod-Alt-1": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "heading", - props: { - level: 1 as any, - }, - }), - "Mod-Alt-2": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "heading", - props: { - level: 2 as any, - }, - }), - "Mod-Alt-3": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "heading", - props: { - level: 3 as any, - }, - }), + "Mod-Alt-1": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "heading", + props: { + level: 1 as any, + }, + } + ); + }, + "Mod-Alt-2": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "heading", + props: { + level: 2 as any, + }, + } + ); + }, + "Mod-Alt-3": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "heading", + props: { + level: 3 as any, + }, + } + ); + }, }; }, parseHTML() { diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 3a862d49ec..bdcbbb5eab 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -7,6 +7,7 @@ import { import { createDefaultBlockDOMOutputSpec } from "../../defaultBlockHelpers"; import { defaultProps } from "../../defaultProps"; import { handleEnter } from "../ListItemKeyboardShortcuts"; +import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType"; export const bulletListItemPropSchema = { ...defaultProps, @@ -22,6 +23,10 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^[-+*]\\s$`), handler: ({ state, chain, range }) => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return; + } + chain() .BNUpdateBlock(state.selection.from, { type: "bulletListItem", @@ -37,11 +42,19 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { Enter: () => handleEnter(this.editor), - "Mod-Shift-8": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "bulletListItem", - props: {}, - }), + "Mod-Shift-8": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "bulletListItem", + props: {}, + } + ); + }, }; }, diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index 09464c067e..a2fd3b4142 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -8,6 +8,7 @@ import { createDefaultBlockDOMOutputSpec } from "../../defaultBlockHelpers"; import { defaultProps } from "../../defaultProps"; import { handleEnter } from "../ListItemKeyboardShortcuts"; import { NumberedListIndexingPlugin } from "./NumberedListIndexingPlugin"; +import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType"; export const numberedListItemPropSchema = { ...defaultProps, @@ -37,6 +38,10 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^1\\.\\s$`), handler: ({ state, chain, range }) => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return; + } + chain() .BNUpdateBlock(state.selection.from, { type: "numberedListItem", @@ -52,11 +57,19 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { Enter: () => handleEnter(this.editor), - "Mod-Shift-7": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "numberedListItem", - props: {}, - }), + "Mod-Shift-7": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "numberedListItem", + props: {}, + } + ); + }, }; }, diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index 07425c77de..d9186f8e1c 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -5,6 +5,7 @@ import { import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers"; import { defaultProps } from "../defaultProps"; import { handleEnter } from "../ListItemBlockContent/ListItemKeyboardShortcuts"; +import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType"; export const paragraphPropSchema = { ...defaultProps, @@ -18,11 +19,19 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { Enter: () => handleEnter(this.editor), - "Mod-Alt-0": () => - this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, { - type: "paragraph", - props: {}, - }), + "Mod-Alt-0": () => { + if (getCurrentBlockContentType(this.editor) !== "inline*") { + return true; + } + + return this.editor.commands.BNUpdateBlock( + this.editor.state.selection.anchor, + { + type: "paragraph", + props: {}, + } + ); + }, }; }, diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 8950d706ef..ae23e5fada 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -652,7 +652,9 @@ export class BlockNoteEditor< * @param editable True to make the editor editable, or false to lock it. */ public set isEditable(editable: boolean) { - this._tiptapEditor.setEditable(editable); + if (this._tiptapEditor.options.editable !== editable) { + this._tiptapEditor.setEditable(editable); + } } /** diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx index 839903c172..ae9852d206 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx @@ -5,12 +5,13 @@ import { StyleSchema, } from "@blocknote/core"; import { flip, offset } from "@floating-ui/react"; -import { FC, useState } from "react"; +import { FC, useMemo, useRef, useState } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor"; import { useEditorContentOrSelectionChange } from "../../hooks/useEditorContentOrSelectionChange"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning"; import { useUIPluginState } from "../../hooks/useUIPluginState"; +import { mergeRefs } from "../../util/mergeRefs"; import { FormattingToolbarProps } from "./FormattingToolbarProps"; import { FormattingToolbar } from "./mantine/FormattingToolbar"; @@ -32,6 +33,8 @@ const textAlignmentToPlacement = ( export const FormattingToolbarController = (props: { formattingToolbar?: FC; }) => { + const divRef = useRef(null); + const editor = useBlockNoteEditor< BlockSchema, InlineContentSchema, @@ -79,14 +82,28 @@ export const FormattingToolbarController = (props: { } ); + const combinedRef = useMemo(() => mergeRefs([divRef, ref]), [divRef, ref]); + if (!isMounted || !state) { return null; } + if (!state.show && divRef.current) { + // The component is fading out. Use the previous state to render the toolbar with innerHTML, + // because otherwise the toolbar will quickly flickr (i.e.: show a different state) while fading out, + // which looks weird + return ( +
+ ); + } + const Component = props.formattingToolbar || FormattingToolbar; return ( -
+
); diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbarProps.ts b/packages/react/src/components/FormattingToolbar/FormattingToolbarProps.ts index 158ac4ec32..afa71ed7c7 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbarProps.ts +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbarProps.ts @@ -1,5 +1,5 @@ -import { BlockTypeDropdownItem } from "./mantine/DefaultDropdowns/BlockTypeDropdown"; +import { BlockTypeSelectItem } from "./mantine/DefaultSelects/BlockTypeSelect"; export type FormattingToolbarProps = { - blockTypeDropdownItems?: BlockTypeDropdownItem[]; + blockTypeSelectItems?: BlockTypeSelectItem[]; }; diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx index 4026db749d..92b97a90e5 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx @@ -12,8 +12,8 @@ import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; -import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; -import { EditHyperlinkMenu } from "../../../HyperlinkToolbar/mantine/EditHyperlinkMenu"; +import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; +import { EditHyperlinkMenuItems } from "../../../HyperlinkToolbar/mantine/EditHyperlinkMenuItems"; // TODO: Make sure Link is in inline content schema export const CreateHyperlinkButton = () => { @@ -56,16 +56,16 @@ export const CreateHyperlinkButton = () => { } return ( - } - dropdown={ - + dropdownItems={ + } /> ); diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ImageCaptionButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ImageCaptionButton.tsx index 91811faff1..9fb6c7896b 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ImageCaptionButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ImageCaptionButton.tsx @@ -17,9 +17,8 @@ import { RiText } from "react-icons/ri"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; -import { ToolbarInputDropdown } from "../../../mantine-shared/Toolbar/ToolbarInputDropdown"; -import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; -import { ToolbarInputDropdownItem } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; +import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; +import { ToolbarInputsMenuItem } from "../../../mantine-shared/Toolbar/ToolbarInputsMenuItem"; export const ImageCaptionButton = () => { const editor = useBlockNoteEditor< @@ -77,30 +76,25 @@ export const ImageCaptionButton = () => { } return ( - } - dropdown={ - - - + dropdownItems={ + } /> ); diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultDropdowns/BlockTypeDropdown.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultSelects/BlockTypeSelect.tsx similarity index 66% rename from packages/react/src/components/FormattingToolbar/mantine/DefaultDropdowns/BlockTypeDropdown.tsx rename to packages/react/src/components/FormattingToolbar/mantine/DefaultSelects/BlockTypeSelect.tsx index 1218753798..566469ab3c 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultDropdowns/BlockTypeDropdown.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultSelects/BlockTypeSelect.tsx @@ -1,6 +1,8 @@ import { Block, BlockSchema, + checkDefaultBlockTypeInSchema, + DefaultBlockSchema, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -18,10 +20,10 @@ import { import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; -import { ToolbarDropdown } from "../../../mantine-shared/Toolbar/ToolbarDropdown"; -import { ToolbarDropdownItemProps } from "../../../mantine-shared/Toolbar/ToolbarDropdownItem"; +import { ToolbarSelect } from "../../../mantine-shared/Toolbar/ToolbarSelect"; +import { ToolbarSelectItemProps } from "../../../mantine-shared/Toolbar/ToolbarSelectItem"; -export type BlockTypeDropdownItem = { +export type BlockTypeSelectItem = { name: string; type: string; props?: Record; @@ -31,8 +33,7 @@ export type BlockTypeDropdownItem = { ) => boolean; }; -// TODO: Filtering from schema should be done here, not in component? -export const blockTypeDropdownItems: BlockTypeDropdownItem[] = [ +export const blockTypeSelectItems: BlockTypeSelectItem[] = [ { name: "Paragraph", type: "paragraph", @@ -83,9 +84,7 @@ export const blockTypeDropdownItems: BlockTypeDropdownItem[] = [ }, ]; -export const BlockTypeDropdown = (props: { - items?: BlockTypeDropdownItem[]; -}) => { +export const BlockTypeSelect = (props: { items?: BlockTypeSelectItem[] }) => { const editor = useBlockNoteEditor< BlockSchema, InlineContentSchema, @@ -96,33 +95,13 @@ export const BlockTypeDropdown = (props: { const [block, setBlock] = useState(editor.getTextCursorPosition().block); - const filteredItems: BlockTypeDropdownItem[] = useMemo(() => { - return (props.items || blockTypeDropdownItems).filter((item) => { - // Checks if block type exists in the schema - if (!(item.type in editor.schema.blockSchema)) { - return false; - } - - // Checks if props for the block type are valid - for (const [prop, value] of Object.entries(item.props || {})) { - const propSchema = editor.schema.blockSchema[item.type].propSchema; - - // Checks if the prop exists for the block type - if (!(prop in propSchema)) { - return false; - } - - // Checks if the prop's value is valid - if ( - propSchema[prop].values !== undefined && - !propSchema[prop].values!.includes(value) - ) { - return false; - } - } - - return true; - }); + const filteredItems: BlockTypeSelectItem[] = useMemo(() => { + return (props.items || blockTypeSelectItems).filter((item) => + checkDefaultBlockTypeInSchema( + item.type as keyof DefaultBlockSchema, + editor + ) + ); }, [editor, props.items]); const shouldShow: boolean = useMemo( @@ -130,8 +109,8 @@ export const BlockTypeDropdown = (props: { [block.type, filteredItems] ); - const fullItems: ToolbarDropdownItemProps[] = useMemo(() => { - const onClick = (item: BlockTypeDropdownItem) => { + const fullItems: ToolbarSelectItemProps[] = useMemo(() => { + const onClick = (item: BlockTypeSelectItem) => { editor.focus(); for (const block of selectedBlocks) { @@ -158,5 +137,5 @@ export const BlockTypeDropdown = (props: { return null; } - return ; + return ; }; diff --git a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx index a7d2bbac7a..e7b15e2330 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx @@ -13,17 +13,14 @@ import { import { ReplaceImageButton } from "./DefaultButtons/ReplaceImageButton"; import { TextAlignButton } from "./DefaultButtons/TextAlignButton"; import { - BlockTypeDropdown, - BlockTypeDropdownItem, -} from "./DefaultDropdowns/BlockTypeDropdown"; + BlockTypeSelect, + BlockTypeSelectItem, +} from "./DefaultSelects/BlockTypeSelect"; export const getFormattingToolbarItems = ( - blockTypeDropdownItems?: BlockTypeDropdownItem[] + blockTypeSelectItems?: BlockTypeSelectItem[] ): JSX.Element[] => [ - , + , , , , @@ -42,16 +39,16 @@ export const getFormattingToolbarItems = ( , ]; -// TODO: props.blockTypeDropdownItems should only be available if no children +// TODO: props.blockTypeSelectItems should only be available if no children // are passed /** * By default, the FormattingToolbar component will render with default - * dropdowns/buttons. However, you can override the dropdowns/buttons to render + * selects/buttons. However, you can override the selects/buttons to render * by passing children. The children you pass should be: * - * - Default dropdowns: Components found within the `/DefaultDropdowns` directory. + * - Default selects: Components found within the `/DefaultSelects` directory. * - Default buttons: Components found within the `/DefaultButtons` directory. - * - Custom dropdowns: The `ToolbarDropdown` component in the + * - Custom selects: The `ToolbarSelect` component in the * `components/mantine-shared/Toolbar` directory. * - Custom buttons: The `ToolbarButton` component in the * `components/mantine-shared/Toolbar` directory. @@ -61,8 +58,7 @@ export const FormattingToolbar = ( ) => { return ( - {props.children || - getFormattingToolbarItems(props.blockTypeDropdownItems)} + {props.children || getFormattingToolbarItems(props.blockTypeSelectItems)} ); }; diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx index 6cd5602435..a6968a06c5 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx @@ -1,17 +1,17 @@ import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; -import { ToolbarInputDropdownButton } from "../../../mantine-shared/Toolbar/ToolbarInputDropdownButton"; +import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; -import { EditHyperlinkMenu } from "../EditHyperlinkMenu"; +import { EditHyperlinkMenuItems } from "../EditHyperlinkMenuItems"; export const EditHyperlinkButton = ( props: Pick ) => ( - Edit Link } - dropdown={} + dropdownItems={} /> ); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx index 112fda99e2..e69de29bb2 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx @@ -1,80 +0,0 @@ -import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; -import { - ChangeEvent, - KeyboardEvent, - useCallback, - useEffect, - useState, -} from "react"; -import { ToolbarInputDropdown } from "../../mantine-shared/Toolbar/ToolbarInputDropdown"; -import { ToolbarInputDropdownItem } from "../../mantine-shared/Toolbar/ToolbarInputDropdownItem"; -import { RiLink, RiText } from "react-icons/ri"; - -export const EditHyperlinkMenu = ( - props: Pick -) => { - const { url, text, editHyperlink } = props; - - const [currentUrl, setCurrentUrl] = useState(url); - const [currentText, setCurrentText] = useState(text); - - useEffect(() => { - setCurrentUrl(url); - setCurrentText(text); - }, [text, url]); - - const handleEnter = useCallback( - (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - editHyperlink(currentUrl, currentText); - } - }, - [editHyperlink, currentUrl, currentText] - ); - - const handleUrlChange = useCallback( - (event: ChangeEvent) => - setCurrentUrl(event.currentTarget.value), - [] - ); - - const handleTextChange = useCallback( - (event: ChangeEvent) => - setCurrentText(event.currentTarget.value), - [] - ); - - const handleSubmit = useCallback( - () => editHyperlink(currentUrl, currentText), - [editHyperlink, currentUrl, currentText] - ); - - return ( - - - - - ); -}; diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenuItems.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenuItems.tsx new file mode 100644 index 0000000000..0da2941b08 --- /dev/null +++ b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenuItems.tsx @@ -0,0 +1,75 @@ +import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; +import { + ChangeEvent, + KeyboardEvent, + useCallback, + useEffect, + useState, +} from "react"; +import { ToolbarInputsMenuItem } from "../../mantine-shared/Toolbar/ToolbarInputsMenuItem"; +import { RiLink, RiText } from "react-icons/ri"; + +export const EditHyperlinkMenuItems = ( + props: Pick +) => { + const { url, text, editHyperlink } = props; + + const [currentUrl, setCurrentUrl] = useState(url); + const [currentText, setCurrentText] = useState(text); + + useEffect(() => { + setCurrentUrl(url); + setCurrentText(text); + }, [text, url]); + + const handleEnter = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + editHyperlink(currentUrl, currentText); + } + }, + [editHyperlink, currentUrl, currentText] + ); + + const handleUrlChange = useCallback( + (event: ChangeEvent) => + setCurrentUrl(event.currentTarget.value), + [] + ); + + const handleTextChange = useCallback( + (event: ChangeEvent) => + setCurrentText(event.currentTarget.value), + [] + ); + + const handleSubmit = useCallback( + () => editHyperlink(currentUrl, currentText), + [editHyperlink, currentUrl, currentText] + ); + + return ( + <> + + + + ); +}; diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx index 92faabde28..eee9a7d365 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx +++ b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx @@ -8,11 +8,11 @@ import { DeleteHyperlinkButton } from "./DefaultButtons/DeleteHyperlinkButton"; /** * By default, the HyperlinkToolbar component will render with default buttons. - * However, you can override the dropdowns/buttons to render by passing + * However, you can override the selects/buttons to render by passing * children. The children you pass should be: * * - Default buttons: Components found within the `/DefaultButtons` directory. - * - Custom dropdowns: The `ToolbarDropdown` component in the + * - Custom selects: The `ToolbarSelect` component in the * `components/mantine-shared/Toolbar` directory. * - Custom buttons: The `ToolbarButton` component in the * `components/mantine-shared/Toolbar` directory. diff --git a/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts b/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts index 1c46a8f1a2..e45704c35a 100644 --- a/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts +++ b/packages/react/src/components/TableHandles/hooks/useTableHandlesPositioning.ts @@ -76,7 +76,7 @@ function useTableHandlePosition( }, [referencePosCell, referencePosTable, update]); useEffect(() => { - // TODO: Maybe throw error instead if null + // Will be null on initial render when used in UI component controllers. if (referencePosCell === null || referencePosTable === null) { return; } diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdown.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdown.tsx deleted file mode 100644 index d0f76e9649..0000000000 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdown.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Menu } from "@mantine/core"; -import { - ToolbarDropdownItem, - ToolbarDropdownItemProps, -} from "./ToolbarDropdownItem"; -import { ToolbarDropdownTarget } from "./ToolbarDropdownTarget"; -import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow"; - -export type ToolbarDropdownProps = { - items: ToolbarDropdownItemProps[]; - isDisabled?: boolean; -}; - -export function ToolbarDropdown(props: ToolbarDropdownProps) { - const selectedItem = props.items.filter((p) => p.isSelected)[0]; - - const { ref, updateMaxHeight } = usePreventMenuOverflow(); - - if (!selectedItem) { - return null; - } - - return ( - - - - -
- - {props.items.map((item) => ( - - ))} - -
-
- ); -} diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownTarget.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownTarget.tsx deleted file mode 100644 index cd8a25b9b8..0000000000 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownTarget.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { isSafari } from "@blocknote/core"; -import { Button } from "@mantine/core"; -import { MouseEventHandler, forwardRef } from "react"; -import type { IconType } from "react-icons"; -import { HiChevronDown } from "react-icons/hi"; - -export type ToolbarDropdownTargetProps = { - text: string; - icon?: IconType; - isDisabled?: boolean; - onClick?: MouseEventHandler; -}; - -export const ToolbarDropdownTarget = forwardRef< - HTMLButtonElement, - ToolbarDropdownTargetProps ->((props: ToolbarDropdownTargetProps, ref) => { - const TargetIcon = props.icon; - - return ( - - ); -}); diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdown.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdown.tsx deleted file mode 100644 index d3061bb27d..0000000000 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdown.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { forwardRef, HTMLAttributes, ReactElement } from "react"; -import { Stack } from "@mantine/core"; - -import { InputProps } from "./ToolbarInputDropdownItem"; -import { mergeCSSClasses } from "@blocknote/core"; - -export type ToolbarInputDropdownProps = { - children: - | ReactElement - | Array>; -}; - -export const ToolbarInputDropdown = forwardRef< - HTMLDivElement, - ToolbarInputDropdownProps & HTMLAttributes ->(({ className, ...props }, ref) => { - return ( - - {props.children} - - ); -}); diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownButton.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownButton.tsx deleted file mode 100644 index 6c30e34b63..0000000000 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownButton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { ToolbarButton } from "./ToolbarButton"; -import { ReactElement, useCallback, useState } from "react"; -import { ToolbarInputDropdown } from "./ToolbarInputDropdown"; -import { Popover } from "@mantine/core"; - -export type ToolbarInputDropdownButtonProps = { - target: ReactElement; - dropdown: ReactElement; -}; - -export const ToolbarInputDropdownButton = ( - props: ToolbarInputDropdownButtonProps -) => { - const [renderDropdown, setRenderDropdown] = useState(false); - - // TODO: review code; does this pattern still make sense? - // This is to make autofocus work on the input fields in the dropdown. - const destroyDropdown = useCallback(() => { - setRenderDropdown(false); - }, []); - const createDropdown = useCallback(() => { - setRenderDropdown(true); - }, []); - - return ( - { - createDropdown(); - }} - onClose={() => { - destroyDropdown(); - }} - zIndex={10000} - {...props}> - {props.target} - - {renderDropdown ? props.dropdown : null} - - - ); -}; diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenu.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenu.tsx new file mode 100644 index 0000000000..c1cc44c26e --- /dev/null +++ b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenu.tsx @@ -0,0 +1,22 @@ +import { ToolbarButton } from "./ToolbarButton"; +import { ReactElement } from "react"; +import { Popover, Stack } from "@mantine/core"; +import { InputProps } from "./ToolbarInputsMenuItem"; + +export type ToolbarInputsMenuButtonProps = { + button: ReactElement; + dropdownItems: + | ReactElement + | Array>; +}; + +export const ToolbarInputsMenu = (props: ToolbarInputsMenuButtonProps) => ( + + {props.button} + + + {props.dropdownItems} + + + +); diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownItem.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenuItem.tsx similarity index 63% rename from packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownItem.tsx rename to packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenuItem.tsx index 1383a0e2ca..7075c1dff3 100644 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputDropdownItem.tsx +++ b/packages/react/src/components/mantine-shared/Toolbar/ToolbarInputsMenuItem.tsx @@ -19,21 +19,21 @@ export const inputComponents: Record = { file: FileInput, }; -export type ToolbarInputDropdownItemProps = { +export type ToolbarInputsMenuItemProps = { type: Type; - inputProps: Omit; icon: IconType; -}; +} & Omit; -export const ToolbarInputDropdownItem = ( - props: ToolbarInputDropdownItemProps +export const ToolbarInputsMenuItem = ( + props: ToolbarInputsMenuItemProps ) => { - const Icon = props.icon; + const { type, icon, ...rest } = props; const Input = inputComponents[props.type]; + const Icon = props.icon; return ( - } {...props.inputProps} /> + } {...rest} /> ); }; diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarSelect.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarSelect.tsx new file mode 100644 index 0000000000..0a38acbada --- /dev/null +++ b/packages/react/src/components/mantine-shared/Toolbar/ToolbarSelect.tsx @@ -0,0 +1,57 @@ +import { Button, Menu } from "@mantine/core"; +import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow"; +import { ToolbarSelectItem, ToolbarSelectItemProps } from "./ToolbarSelectItem"; +import { isSafari } from "@blocknote/core"; +import { HiChevronDown } from "react-icons/hi"; + +export type ToolbarSelectProps = { + items: ToolbarSelectItemProps[]; + isDisabled?: boolean; +}; + +export function ToolbarSelect(props: ToolbarSelectProps) { + const selectedItem = props.items.filter((p) => p.isSelected)[0]; + + const { ref, updateMaxHeight } = usePreventMenuOverflow(); + + if (!selectedItem) { + return null; + } + + const Icon = selectedItem.icon; + + return ( + + + + +
+ + {props.items.map((item) => ( + + ))} + +
+
+ ); +} diff --git a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownItem.tsx b/packages/react/src/components/mantine-shared/Toolbar/ToolbarSelectItem.tsx similarity index 79% rename from packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownItem.tsx rename to packages/react/src/components/mantine-shared/Toolbar/ToolbarSelectItem.tsx index 4b67cb5fd7..15cd7e911e 100644 --- a/packages/react/src/components/mantine-shared/Toolbar/ToolbarDropdownItem.tsx +++ b/packages/react/src/components/mantine-shared/Toolbar/ToolbarSelectItem.tsx @@ -1,17 +1,16 @@ import { Menu } from "@mantine/core"; -import { MouseEvent } from "react"; import type { IconType } from "react-icons"; import { TiTick } from "react-icons/ti"; -export type ToolbarDropdownItemProps = { +export type ToolbarSelectItemProps = { text: string; icon?: IconType; - onClick?: (e: MouseEvent) => void; + onClick?: () => void; isSelected?: boolean; isDisabled?: boolean; }; -export function ToolbarDropdownItem(props: ToolbarDropdownItemProps) { +export function ToolbarSelectItem(props: ToolbarSelectItemProps) { const ItemIcon = props.icon; return ( diff --git a/packages/react/src/hooks/useUIElementPositioning.ts b/packages/react/src/hooks/useUIElementPositioning.ts index 51e0fffab1..01e3e6a3e3 100644 --- a/packages/react/src/hooks/useUIElementPositioning.ts +++ b/packages/react/src/hooks/useUIElementPositioning.ts @@ -25,7 +25,7 @@ export function useUIElementPositioning( }, [referencePos, update]); useEffect(() => { - // TODO: Maybe throw error instead if null + // Will be null on initial render when used in UI component controllers. if (referencePos === null) { return; } diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 786ba9d178..0522c2d916 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -14,7 +14,7 @@ export * from "./components/FormattingToolbar/mantine/DefaultButtons/ImageCaptio export * from "./components/FormattingToolbar/mantine/DefaultButtons/NestBlockButtons"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton"; export * from "./components/FormattingToolbar/mantine/DefaultButtons/TextAlignButton"; -export * from "./components/FormattingToolbar/mantine/DefaultDropdowns/BlockTypeDropdown"; +export * from "./components/FormattingToolbar/mantine/DefaultSelects/BlockTypeSelect"; export * from "./components/FormattingToolbar/mantine/FormattingToolbar"; export * from "./components/HyperlinkToolbar/HyperlinkToolbarController"; @@ -23,7 +23,7 @@ export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperl export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton"; export * from "./components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton"; export * from "./components/HyperlinkToolbar/mantine/HyperlinkToolbar"; -export * from "./components/HyperlinkToolbar/mantine/EditHyperlinkMenu"; +export * from "./components/HyperlinkToolbar/mantine/EditHyperlinkMenuItems"; export * from "./components/SideMenu/SideMenuController"; export * from "./components/SideMenu/SideMenuProps"; @@ -70,7 +70,7 @@ export * from "./components/TableHandles/TableHandleMenu/mantine/TableHandleMenu export * from "./components/TableHandles/TableHandleMenu/mantine/TableHandleMenuItem"; export * from "./components/mantine-shared/Toolbar/ToolbarButton"; -export * from "./components/mantine-shared/Toolbar/ToolbarDropdown"; +export * from "./components/mantine-shared/Toolbar/ToolbarSelect"; export * from "./hooks/useActiveStyles"; export * from "./hooks/useBlockNoteEditor"; diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 78c9af386b..fcb84300df 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -199,7 +199,7 @@ "react-icons": "^4.3.1" } }, - "title": "Adding Block Type Dropdown Items", + "title": "Adding Block Type Select Items", "group": { "pathFromRoot": "examples/02-ui-components", "slug": "ui-components" diff --git a/tests/package.json b/tests/package.json index 4e8a0e3f54..e055afa9bd 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,7 +6,7 @@ "build": "tsc", "lint": "eslint src --max-warnings 0", "playwright": "npx playwright test", - "test:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.38.1-focal npx playwright test -u", + "test:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.42.1-focal npx playwright test -u", "test-ct": "playwright test -c playwright-ct.config.ts --headed", "test-ct:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.35.1-focal npm install && playwright test -c playwright-ct.config.ts -u", "clean": "rimraf dist" @@ -18,8 +18,8 @@ "devDependencies": { "@blocknote/core": "^0.12.0", "@blocknote/react": "^0.12.0", - "@playwright/experimental-ct-react": "^1.38.1", - "@playwright/test": "^1.38.1", + "@playwright/experimental-ct-react": "^1.42.1", + "@playwright/test": "^1.42.1", "eslint": "^8.10.0", "react-icons": "^4.3.1", "rimraf": "^5.0.5" diff --git a/tests/src/end-to-end/slashmenu/slashmenu.test.ts-snapshots/slash-menu-end-product-webkit-linux.png b/tests/src/end-to-end/slashmenu/slashmenu.test.ts-snapshots/slash-menu-end-product-webkit-linux.png index 9d6ed417890d9d6630900b9ffabc5ef77a423796..08a328f1be1c7d1883e64ab874cbcc9bff9b73aa 100644 GIT binary patch delta 18980 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 delta 22478 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 ccp2;3(Hlmd^ypu&l1VnSg&k(xbI2( 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 OhYq-WQ@Rgz{{H}%O2>Nu literal 24311 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 Hz9;_+=q1GT diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-image-toolbar-firefox-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-image-toolbar-firefox-linux.png index 1475151c45f61eed76823f82f671f9a40e77a556..064314d00b336b74a7a7144f990c6f2c60fc38a9 100644 GIT binary patch literal 30568 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 literal 29329 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 F{V!X5=hgrK diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-side-menu-firefox-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-side-menu-firefox-linux.png index fc4b89527309ecf931e963641ad0f5bb3a28fa44..099f6e806628a6a88c151da76b9aae6d47c01208 100644 GIT binary patch literal 23843 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 literal 19824 zcmeHPdsGuw8lRbt7*Lu3dN`{B0X?FX5-sf-DI_C4z=8>@ww#_K@wh#wo~>)JRCz@5 zup(_~iCZ7AOG=LIc3lpw&?-d*G6O88mKAhGX)BOf0_CB=1e7EMlD#~V!M1;O|J%L) zB=_Xbow@h>e&6qYkDGZfSrYH-v&;uU5Z@gMTX!MI0?@>6alF7+Yi{Q&2*O8pY~8#k zD{ZKgEH~xV87)#jpS0Rd&Hvh9IH+}^qT)j_CR*2Jot@&=`y-Z);^0krGHuT}e(D*0-9m zlCG&Mht1b5MdV~}$5Ez%U8!U<9WS1^ZE=`fPFp=!neL-C|Axa8zJa2Qj!XA?>pr3c zDtoi}i|VfE&*DcT*r6>u>50b=j3Y{GO@<;@kff#tI2`qbj6`W1ow~p|UQGsx9pASW z3dqUPR4dcXj+AVpzcdEFD6-M-m~VvZW6s8puE06E(?AarChZ(m=3P7f!<)wC^4i0H z(Dr7tI@6U?%>wdR?dZ4b`mVhb7*5dLi#X~)*U3^f5l7qm^kR{{=XBK2TfN_&HJ4Wy zuDQzU4eW59u&_L(B6!5$i1Yyh)a?K=3ed||6SNAyxVPqLhXbcC9qV);e^SUc%DZBk zi&f6yI@Nmn1@Y9tl* z2-6V&&aXb2j6^Zlk~LmbQCiM`Z8(#32v9~mbv47^Gir?wN8MEZ-FsA%KMxVyt2>y; zUzfaC?es|>AKJgc4Hr#CC?8S!Mu=ZhQ{+-$5F0qWV$PDM=$`t;p&m0!Zvju;6hbuz z2vL9oys+PovjoSfhNTbLYu*RftWCQc$#;*& zyD@CvByn$LItm6aN?*0k{|X0dLi%^B1r0A63q2a(f@O!S>T%$lm;?ejcIwnf0G~<5 zxB*DW^ZnO9Ty9nM{CKm3J8?UI!a!n^$w|t0-F(B3F zf%P}p`{Ti@_=qUs8>=@j>l9cKKhy-^cOFbCfB)zf&opqu*PyfN#v}$b77s}39G}=1 zHlqlp=kiI9H+& z>;2Yv5qd0U(-$oG`li`v46TjP1n_u>lzul@Lu{shsCv`bS(Dj%`F$xWnnC#-!3sCe zkWTfX&glB^u>_oRtOTgCx@@S5qpPGYwTcvgtNN0pS-^-Kq{=Zq6m`?m%&rhv@8I2X zW6g*`@r>5I36MqjG+EGf5t`irSwmIrg&VuF%x%HVW1iLWgejULnbMxsdp4!z$U=cc zO*I7qm^yu9QOyZT=9*|XPeZ`cTO?aO{w40ARZT%8^WE>YL8^&6`obo%I`{Uo52b4A zdm#2pcKaVO+*w4)m18AluFCdQfE(ptxEE#>wGH1*kp(3^cO22g?**7>Goht=Dw!@2 z7=#yPBwjeBBzGrT7@2YSOAc<2u0J+AMgSw2xA5==z~XU$674^&JXEw zy-n9-j_jJZsnI4*$D-VsO(18m%$ygPCM?bB_LcyV7Xz!_EotXN|0mw);`L1MtabI%_*vMfbYm zzX8~sxwC$r)ZJarqG;t2?tNDO9%;b^0ii$*0c=9ZO&c4uA;@yG1PVz3lEU0J2}uEx z0we|KoIc@=LsEdG07(Io0we`CIWa2>m{Wi`h1tv#%qhT}!W=t5Qh=lYNrBDU|Jv}O z`^Y)meHH*#B4B9+ChlkJYQNT=pbbGAf;I$g2v)@BpALY5FCc!Fa&3}Nf?4dQh*^i z48foHd)xmvDSRlESAzEigiOB|VD2C$^ctYo0KEq2HO%gmkQCU{*)YEV^9wM)0P_o; p)bn%$k^&?JNDBYcq`=68c~#p!X#Fz<-lT@?*e2O}F>c@S{{X`Q<_7=( From 885b43657b678c3db836bbc676470095a2c62aa2 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Thu, 14 Mar 2024 19:21:47 +0100 Subject: [PATCH 8/8] Renamed hyperlink toolbar -> link toolbar and small changes --- docs/pages/docs/advanced/vanilla-js.mdx | 2 +- docs/pages/docs/editor-basics/setup.mdx | 4 +- .../docs/styling-theming/overriding-css.mdx | 2 +- docs/pages/docs/ui-components.mdx | 2 +- docs/pages/docs/ui-components/_meta.json | 4 +- .../docs/ui-components/hyperlink-toolbar.mdx | 30 +-- .../01-ui-elements-remove/App.tsx | 2 +- .../02-formatting-toolbar-buttons/App.tsx | 4 +- .../hyperlink-toolbar-buttons/README.md | 10 - .../.bnexample.json | 0 .../AlertButton.tsx | 6 +- .../App.tsx | 16 +- .../link-toolbar-buttons/README.md | 10 + .../index.html | 2 +- .../main.tsx | 0 .../package.json | 2 +- .../tsconfig.json | 0 .../vite.config.ts | 0 .../05-custom-schema/03-font-style/App.tsx | 4 +- packages/core/src/editor/BlockNoteEditor.ts | 8 +- .../LinkToolbarPlugin.ts} | 178 +++++++++--------- packages/core/src/index.ts | 2 +- ...perlinkButton.tsx => CreateLinkButton.tsx} | 35 +++- .../mantine/FormattingToolbar.tsx | 4 +- .../HyperlinkToolbar/HyperlinkToolbarProps.ts | 21 --- .../mantine/EditHyperlinkMenu.tsx | 0 .../LinkToolbarController.tsx} | 20 +- .../LinkToolbar/LinkToolbarProps.ts | 18 ++ .../DefaultButtons/DeleteLinkButton.tsx} | 8 +- .../DefaultButtons/EditLinkButton.tsx} | 10 +- .../DefaultButtons/OpenLinkButton.tsx} | 6 +- .../mantine/EditLinkMenuItems.tsx} | 18 +- .../mantine/LinkToolbar.tsx} | 22 +-- .../react/src/editor/BlockNoteDefaultUI.tsx | 6 +- packages/react/src/editor/BlockNoteView.tsx | 6 +- packages/react/src/index.ts | 16 +- playground/src/examples.gen.tsx | 8 +- tests/src/end-to-end/theming/theming.test.ts | 6 +- .../dark-hyperlink-toolbar-chromium-linux.png | Bin 11022 -> 0 bytes .../dark-hyperlink-toolbar-webkit-linux.png | Bin 24340 -> 0 bytes .../dark-link-toolbar-chromium-linux.png | Bin 0 -> 11014 bytes ...ng => dark-link-toolbar-firefox-linux.png} | Bin 25988 -> 26019 bytes .../dark-link-toolbar-webkit-linux.png | Bin 0 -> 24340 bytes 43 files changed, 253 insertions(+), 239 deletions(-) delete mode 100644 examples/02-ui-components/hyperlink-toolbar-buttons/README.md rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/.bnexample.json (100%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/AlertButton.tsx (50%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/App.tsx (74%) create mode 100644 examples/02-ui-components/link-toolbar-buttons/README.md rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/index.html (86%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/main.tsx (100%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/package.json (92%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/tsconfig.json (100%) rename examples/02-ui-components/{hyperlink-toolbar-buttons => link-toolbar-buttons}/vite.config.ts (100%) rename packages/core/src/extensions/{HyperlinkToolbar/HyperlinkToolbarPlugin.ts => LinkToolbar/LinkToolbarPlugin.ts} (55%) rename packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/{CreateHyperlinkButton.tsx => CreateLinkButton.tsx} (70%) delete mode 100644 packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarProps.ts delete mode 100644 packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx rename packages/react/src/components/{HyperlinkToolbar/HyperlinkToolbarController.tsx => LinkToolbar/LinkToolbarController.tsx} (66%) create mode 100644 packages/react/src/components/LinkToolbar/LinkToolbarProps.ts rename packages/react/src/components/{HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx => LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx} (54%) rename packages/react/src/components/{HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx => LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx} (51%) rename packages/react/src/components/{HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx => LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx} (66%) rename packages/react/src/components/{HyperlinkToolbar/mantine/EditHyperlinkMenuItems.tsx => LinkToolbar/mantine/EditLinkMenuItems.tsx} (78%) rename packages/react/src/components/{HyperlinkToolbar/mantine/HyperlinkToolbar.tsx => LinkToolbar/mantine/LinkToolbar.tsx} (54%) delete mode 100644 tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-chromium-linux.png delete mode 100644 tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-webkit-linux.png create mode 100644 tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png rename tests/src/end-to-end/theming/theming.test.ts-snapshots/{dark-hyperlink-toolbar-firefox-linux.png => dark-link-toolbar-firefox-linux.png} (51%) create mode 100644 tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-webkit-linux.png diff --git a/docs/pages/docs/advanced/vanilla-js.mdx b/docs/pages/docs/advanced/vanilla-js.mdx index 0921d07b11..6c0981696b 100644 --- a/docs/pages/docs/advanced/vanilla-js.mdx +++ b/docs/pages/docs/advanced/vanilla-js.mdx @@ -49,7 +49,7 @@ While it's up to you to decide how you want the elements to be rendered, BlockNo ```typescript type UIElement = | "formattingToolbar" - | "hyperlinkToolbar" + | "linkToolbar" | "imageToolbar" | "sideMenu" | "suggestionMenu" diff --git a/docs/pages/docs/editor-basics/setup.mdx b/docs/pages/docs/editor-basics/setup.mdx index 220a1ab91a..eddf89a58b 100644 --- a/docs/pages/docs/editor-basics/setup.mdx +++ b/docs/pages/docs/editor-basics/setup.mdx @@ -87,7 +87,7 @@ export type BlockNoteViewProps = { dark: Theme; }; formattingToolbar?: boolean; - hyperlinkToolbar?: boolean; + linkToolbar?: boolean; sideMenu?: boolean; slashMenu?: boolean; imageToolbar?: boolean; @@ -108,7 +108,7 @@ export type BlockNoteViewProps = { `formattingToolbar`: Whether the [Formatting Toolbar](/docs/ui-components/formatting-toolbar) should be enabled. -`hyperlinkToolbar`: Whether the Hyperlink Toolbar should be enabled. +`linkToolbar`: Whether the Link Toolbar should be enabled. `sideMenu`: Whether the [Block Side Menu](/docs/ui-components/side-menu) should be enabled. diff --git a/docs/pages/docs/styling-theming/overriding-css.mdx b/docs/pages/docs/styling-theming/overriding-css.mdx index 2f926f1ca2..dffa489a17 100644 --- a/docs/pages/docs/styling-theming/overriding-css.mdx +++ b/docs/pages/docs/styling-theming/overriding-css.mdx @@ -31,7 +31,7 @@ Within the editor's DOM structure, you'll find many elements have classes with t `.bn-inline-content`: Element for only the block's editable, rich text content. -`.bn-toolbar`: Element for the formatting & hyperlink toolbars. +`.bn-toolbar`: Element for the formatting & link toolbars. `.bn-side-menu`: Element for the side menu. diff --git a/docs/pages/docs/ui-components.mdx b/docs/pages/docs/ui-components.mdx index ece58c1632..b6073d6b38 100644 --- a/docs/pages/docs/ui-components.mdx +++ b/docs/pages/docs/ui-components.mdx @@ -12,5 +12,5 @@ BlockNote includes a number of UI Components (like menus and toolbars) that can - [Block Side Menu](/docs/ui-components/side-menu) - [Formatting Toolbar](/docs/ui-components/formatting-toolbar) - [Suggestion Menus](/docs/ui-components/suggestion-menus) - {/* - Hyperlink Toolbar */} + {/* - Link Toolbar */} {/* - [Image Toolbar](/docs/ui-components/image-toolbar) */} diff --git a/docs/pages/docs/ui-components/_meta.json b/docs/pages/docs/ui-components/_meta.json index 850fab1a9d..5784d1ed55 100644 --- a/docs/pages/docs/ui-components/_meta.json +++ b/docs/pages/docs/ui-components/_meta.json @@ -1,8 +1,8 @@ { "side-menu": "Block Side Menu", "formatting-toolbar": "Formatting Toolbar", - "hyperlink-toolbar": { - "title": "Hyperlink Toolbar", + "link-toolbar": { + "title": "Link Toolbar", "display": "hidden" }, "image-toolbar": { diff --git a/docs/pages/docs/ui-components/hyperlink-toolbar.mdx b/docs/pages/docs/ui-components/hyperlink-toolbar.mdx index 0b9572bcbf..2f1ea13f49 100644 --- a/docs/pages/docs/ui-components/hyperlink-toolbar.mdx +++ b/docs/pages/docs/ui-components/hyperlink-toolbar.mdx @@ -1,37 +1,37 @@ --- -title: Hyperlink Toolbar -description: The Hyperlink Toolbar appears whenever you hover a link in the editor. -imageTitle: Hyperlink Toolbar -path: /docs/hyperlink-toolbar +title: Link Toolbar +description: The Link Toolbar appears whenever you hover a link in the editor. +imageTitle: Link Toolbar +path: /docs/link-toolbar --- import { Example } from "@/components/example"; -# Hyperlink Toolbar +# Link Toolbar -The Hyperlink Toolbar appears whenever you hover a link in the editor. +The Link Toolbar appears whenever you hover a link in the editor. TODO Image -[//]: # 'image' +[//]: # 'image' -## Changing the Hyperlink Toolbar +## Changing the Link Toolbar -You can change or replace the Hyperlink Toolbar with your own React component. In the demo below, a button is added to the default Hyperlink Toolbar, which opens a browser alert. +You can change or replace the Link Toolbar with your own React component. In the demo below, a button is added to the default Link Toolbar, which opens a browser alert. -[//]: # () +[//]: # () -We use the `HyperlinkToolbar` component to create a custom Hyperlink Toolbar. By specifying its children, we can replace the default buttons in the toolbar with our own. +We use the `LinkToolbar` component to create a custom Link Toolbar. By specifying its children, we can replace the default buttons in the toolbar with our own. -This custom Hyperlink Toolbar is passed to a `HyperlinkToolbarController`, which controls its position and visibility (above or below the hovered link). +This custom Link Toolbar is passed to a `LinkToolbarController`, which controls its position and visibility (above or below the hovered link). -Setting `hyperlinkToolbar={false}` on `BlockNoteView` tells BlockNote not to show the default Hyperlink Toolbar. +Setting `linkToolbar={false}` on `BlockNoteView` tells BlockNote not to show the default Link Toolbar.
- Tip: The children you pass to the `HyperlinkToolbar` + Tip: The children you pass to the `LinkToolbar` component should be default buttons (e.g. TODO) or custom selects/buttons (`ToolbarSelect` & `ToolbarButton`). To see all the components you can - use, head to the [Hyperlink Toolbar's source code](link). + use, head to the [Link Toolbar's source code](link).
diff --git a/examples/02-ui-components/01-ui-elements-remove/App.tsx b/examples/02-ui-components/01-ui-elements-remove/App.tsx index 1c5dbfd0f8..0f67fdc449 100644 --- a/examples/02-ui-components/01-ui-elements-remove/App.tsx +++ b/examples/02-ui-components/01-ui-elements-remove/App.tsx @@ -32,7 +32,7 @@ export default function App() { editor={editor} // Removes all menus and toolbars. formattingToolbar={false} - hyperlinkToolbar={false} + linkToolbar={false} imageToolbar={false} sideMenu={false} slashMenu={false} diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx index e52277e6f1..8f44929e33 100644 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx @@ -4,7 +4,7 @@ import { BlockNoteView, BlockTypeSelect, ColorStyleButton, - CreateHyperlinkButton, + CreateLinkButton, FormattingToolbar, FormattingToolbarController, ImageCaptionButton, @@ -120,7 +120,7 @@ export default function App() { - + )} /> diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/README.md b/examples/02-ui-components/hyperlink-toolbar-buttons/README.md deleted file mode 100644 index af0cbc607a..0000000000 --- a/examples/02-ui-components/hyperlink-toolbar-buttons/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Adding Hyperlink Toolbar Buttons - -In this example, we add a button to the Hyperlink Toolbar which opens a browser alert. - -**Try it out:** Hover the link open the Hyperlink Toolbar, and click the new "Open Alert" button! - -**Relevant Docs:** - -- [Changing the Hyperlink Toolbar](/docs/ui-components/hyperlink-toolbar#changing-the-hyperlink-toolbar) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/.bnexample.json b/examples/02-ui-components/link-toolbar-buttons/.bnexample.json similarity index 100% rename from examples/02-ui-components/hyperlink-toolbar-buttons/.bnexample.json rename to examples/02-ui-components/link-toolbar-buttons/.bnexample.json diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/AlertButton.tsx b/examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx similarity index 50% rename from examples/02-ui-components/hyperlink-toolbar-buttons/AlertButton.tsx rename to examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx index bdf9640b4b..123d856b0e 100644 --- a/examples/02-ui-components/hyperlink-toolbar-buttons/AlertButton.tsx +++ b/examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx @@ -1,7 +1,7 @@ -import { HyperlinkToolbarProps, ToolbarButton } from "@blocknote/react"; +import { LinkToolbarProps, ToolbarButton } from "@blocknote/react"; -// Custom Hyperlink Toolbar button to open a browser alert. -export function AlertButton(props: HyperlinkToolbarProps) { +// Custom Link Toolbar button to open a browser alert. +export function AlertButton(props: LinkToolbarProps) { return ( - ( - + + ( + - + )} /> diff --git a/examples/02-ui-components/link-toolbar-buttons/README.md b/examples/02-ui-components/link-toolbar-buttons/README.md new file mode 100644 index 0000000000..2257f47059 --- /dev/null +++ b/examples/02-ui-components/link-toolbar-buttons/README.md @@ -0,0 +1,10 @@ +# Adding Link Toolbar Buttons + +In this example, we add a button to the Link Toolbar which opens a browser alert. + +**Try it out:** Hover the link open the Link Toolbar, and click the new "Open Alert" button! + +**Relevant Docs:** + +- [Changing the Link Toolbar](/docs/ui-components/link-toolbar#changing-the-link-toolbar) +- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/index.html b/examples/02-ui-components/link-toolbar-buttons/index.html similarity index 86% rename from examples/02-ui-components/hyperlink-toolbar-buttons/index.html rename to examples/02-ui-components/link-toolbar-buttons/index.html index b30eb96292..c7356c7253 100644 --- a/examples/02-ui-components/hyperlink-toolbar-buttons/index.html +++ b/examples/02-ui-components/link-toolbar-buttons/index.html @@ -5,7 +5,7 @@ - Adding Hyperlink Toolbar Buttons + Adding Link Toolbar Buttons
diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/main.tsx b/examples/02-ui-components/link-toolbar-buttons/main.tsx similarity index 100% rename from examples/02-ui-components/hyperlink-toolbar-buttons/main.tsx rename to examples/02-ui-components/link-toolbar-buttons/main.tsx diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/package.json b/examples/02-ui-components/link-toolbar-buttons/package.json similarity index 92% rename from examples/02-ui-components/hyperlink-toolbar-buttons/package.json rename to examples/02-ui-components/link-toolbar-buttons/package.json index 840d70465b..470ec0c68f 100644 --- a/examples/02-ui-components/hyperlink-toolbar-buttons/package.json +++ b/examples/02-ui-components/link-toolbar-buttons/package.json @@ -1,5 +1,5 @@ { - "name": "@blocknote/example-hyperlink-toolbar-buttons", + "name": "@blocknote/example-link-toolbar-buttons", "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", "private": true, "version": "0.12.0", diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/tsconfig.json b/examples/02-ui-components/link-toolbar-buttons/tsconfig.json similarity index 100% rename from examples/02-ui-components/hyperlink-toolbar-buttons/tsconfig.json rename to examples/02-ui-components/link-toolbar-buttons/tsconfig.json diff --git a/examples/02-ui-components/hyperlink-toolbar-buttons/vite.config.ts b/examples/02-ui-components/link-toolbar-buttons/vite.config.ts similarity index 100% rename from examples/02-ui-components/hyperlink-toolbar-buttons/vite.config.ts rename to examples/02-ui-components/link-toolbar-buttons/vite.config.ts diff --git a/examples/05-custom-schema/03-font-style/App.tsx b/examples/05-custom-schema/03-font-style/App.tsx index 1c596a1c51..edb7f96322 100644 --- a/examples/05-custom-schema/03-font-style/App.tsx +++ b/examples/05-custom-schema/03-font-style/App.tsx @@ -5,7 +5,7 @@ import { BlockNoteView, BlockTypeSelect, ColorStyleButton, - CreateHyperlinkButton, + CreateLinkButton, FormattingToolbar, FormattingToolbarController, ImageCaptionButton, @@ -141,7 +141,7 @@ export default function App() { - + )} /> diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index ae23e5fada..001782123b 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -27,7 +27,7 @@ import { PartialBlock, } from "../blocks/defaultBlocks"; import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin"; -import { HyperlinkToolbarProsemirrorPlugin } from "../extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; +import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin"; import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin"; import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin"; import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin"; @@ -157,7 +157,7 @@ export class BlockNoteEditor< public readonly styleImplementations: StyleSpecs; public readonly formattingToolbar: FormattingToolbarProsemirrorPlugin; - public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin< + public readonly linkToolbar: LinkToolbarProsemirrorPlugin< BSchema, ISchema, SSchema @@ -230,7 +230,7 @@ export class BlockNoteEditor< this.styleImplementations = newOptions.schema.styleSpecs; this.formattingToolbar = new FormattingToolbarProsemirrorPlugin(this); - this.hyperlinkToolbar = new HyperlinkToolbarProsemirrorPlugin(this); + this.linkToolbar = new LinkToolbarProsemirrorPlugin(this); this.sideMenu = new SideMenuProsemirrorPlugin(this); this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this); if (checkDefaultBlockTypeInSchema("image", this)) { @@ -258,7 +258,7 @@ export class BlockNoteEditor< addProseMirrorPlugins: () => { return [ this.formattingToolbar.plugin, - this.hyperlinkToolbar.plugin, + this.linkToolbar.plugin, this.sideMenu.plugin, this.suggestionMenus.plugin, ...(this.imagePanel ? [this.imagePanel.plugin] : []), diff --git a/packages/core/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts similarity index 55% rename from packages/core/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts rename to packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts index c0d524c0b4..7fd9cde3a4 100644 --- a/packages/core/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +++ b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts @@ -8,38 +8,38 @@ import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; import { EventEmitter } from "../../util/EventEmitter"; -export type HyperlinkToolbarState = UiElementPosition & { - // The hovered hyperlink's URL, and the text it's displayed with in the +export type LinkToolbarState = UiElementPosition & { + // The hovered link's URL, and the text it's displayed with in the // editor. url: string; text: string; }; -class HyperlinkToolbarView { - public state?: HyperlinkToolbarState; +class LinkToolbarView { + public state?: LinkToolbarState; public emitUpdate: () => void; menuUpdateTimer: ReturnType | undefined; startMenuUpdateTimer: () => void; stopMenuUpdateTimer: () => void; - mouseHoveredHyperlinkMark: Mark | undefined; - mouseHoveredHyperlinkMarkRange: Range | undefined; + mouseHoveredLinkMark: Mark | undefined; + mouseHoveredLinkMarkRange: Range | undefined; - keyboardHoveredHyperlinkMark: Mark | undefined; - keyboardHoveredHyperlinkMarkRange: Range | undefined; + keyboardHoveredLinkMark: Mark | undefined; + keyboardHoveredLinkMarkRange: Range | undefined; - hyperlinkMark: Mark | undefined; - hyperlinkMarkRange: Range | undefined; + linkMark: Mark | undefined; + linkMarkRange: Range | undefined; constructor( private readonly editor: BlockNoteEditor, private readonly pmView: EditorView, - emitUpdate: (state: HyperlinkToolbarState) => void + emitUpdate: (state: LinkToolbarState) => void ) { this.emitUpdate = () => { if (!this.state) { - throw new Error("Attempting to update uninitialized hyperlink toolbar"); + throw new Error("Attempting to update uninitialized link toolbar"); } emitUpdate(this.state); @@ -66,9 +66,9 @@ class HyperlinkToolbarView { } mouseOverHandler = (event: MouseEvent) => { - // Resets the hyperlink mark currently hovered by the mouse cursor. - this.mouseHoveredHyperlinkMark = undefined; - this.mouseHoveredHyperlinkMarkRange = undefined; + // Resets the link mark currently hovered by the mouse cursor. + this.mouseHoveredLinkMark = undefined; + this.mouseHoveredLinkMarkRange = undefined; this.stopMenuUpdateTimer(); @@ -76,27 +76,23 @@ class HyperlinkToolbarView { event.target instanceof HTMLAnchorElement && event.target.nodeName === "A" ) { - // Finds link mark at the hovered element's position to update mouseHoveredHyperlinkMark and - // mouseHoveredHyperlinkMarkRange. - const hoveredHyperlinkElement = event.target; - const posInHoveredHyperlinkMark = - this.pmView.posAtDOM(hoveredHyperlinkElement, 0) + 1; - const resolvedPosInHoveredHyperlinkMark = this.pmView.state.doc.resolve( - posInHoveredHyperlinkMark - ); - const marksAtPos = resolvedPosInHoveredHyperlinkMark.marks(); + // Finds link mark at the hovered element's position to update mouseHoveredLinkMark and + // mouseHoveredLinkMarkRange. + const hoveredLinkElement = event.target; + const posInHoveredLinkMark = + this.pmView.posAtDOM(hoveredLinkElement, 0) + 1; + const resolvedPosInHoveredLinkMark = + this.pmView.state.doc.resolve(posInHoveredLinkMark); + const marksAtPos = resolvedPosInHoveredLinkMark.marks(); for (const mark of marksAtPos) { if ( mark.type.name === this.pmView.state.schema.mark("link").type.name ) { - this.mouseHoveredHyperlinkMark = mark; - this.mouseHoveredHyperlinkMarkRange = - getMarkRange( - resolvedPosInHoveredHyperlinkMark, - mark.type, - mark.attrs - ) || undefined; + this.mouseHoveredLinkMark = mark; + this.mouseHoveredLinkMarkRange = + getMarkRange(resolvedPosInHoveredLinkMark, mark.type, mark.attrs) || + undefined; break; } @@ -113,7 +109,7 @@ class HyperlinkToolbarView { if ( // Toolbar is open. - this.hyperlinkMark && + this.linkMark && // An element is clicked. event && event.target && @@ -131,27 +127,27 @@ class HyperlinkToolbarView { }; scrollHandler = () => { - if (this.hyperlinkMark !== undefined) { + if (this.linkMark !== undefined) { if (this.state?.show) { this.state.referencePos = posToDOMRect( this.pmView, - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.to + this.linkMarkRange!.from, + this.linkMarkRange!.to ); this.emitUpdate(); } } }; - editHyperlink(url: string, text: string) { + editLink(url: string, text: string) { const tr = this.pmView.state.tr.insertText( text, - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.to + this.linkMarkRange!.from, + this.linkMarkRange!.to ); tr.addMark( - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.from + text.length, + this.linkMarkRange!.from, + this.linkMarkRange!.from + text.length, this.pmView.state.schema.mark("link", { href: url }) ); this.pmView.dispatch(tr); @@ -163,13 +159,13 @@ class HyperlinkToolbarView { } } - deleteHyperlink() { + deleteLink() { this.pmView.dispatch( this.pmView.state.tr .removeMark( - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.to, - this.hyperlinkMark!.type + this.linkMarkRange!.from, + this.linkMarkRange!.to, + this.linkMark!.type ) .setMeta("preventAutolink", true) ); @@ -186,19 +182,19 @@ class HyperlinkToolbarView { return; } - // Saves the currently hovered hyperlink mark before it's updated. - const prevHyperlinkMark = this.hyperlinkMark; + // Saves the currently hovered link mark before it's updated. + const prevLinkMark = this.linkMark; - // Resets the currently hovered hyperlink mark. - this.hyperlinkMark = undefined; - this.hyperlinkMarkRange = undefined; + // Resets the currently hovered link mark. + this.linkMark = undefined; + this.linkMarkRange = undefined; - // Resets the hyperlink mark currently hovered by the keyboard cursor. - this.keyboardHoveredHyperlinkMark = undefined; - this.keyboardHoveredHyperlinkMarkRange = undefined; + // Resets the link mark currently hovered by the keyboard cursor. + this.keyboardHoveredLinkMark = undefined; + this.keyboardHoveredLinkMarkRange = undefined; - // Finds link mark at the editor selection's position to update keyboardHoveredHyperlinkMark and - // keyboardHoveredHyperlinkMarkRange. + // Finds link mark at the editor selection's position to update keyboardHoveredLinkMark and + // keyboardHoveredLinkMarkRange. if (this.pmView.state.selection.empty) { const marksAtPos = this.pmView.state.selection.$from.marks(); @@ -206,8 +202,8 @@ class HyperlinkToolbarView { if ( mark.type.name === this.pmView.state.schema.mark("link").type.name ) { - this.keyboardHoveredHyperlinkMark = mark; - this.keyboardHoveredHyperlinkMarkRange = + this.keyboardHoveredLinkMark = mark; + this.keyboardHoveredLinkMarkRange = getMarkRange( this.pmView.state.selection.$from, mark.type, @@ -219,29 +215,29 @@ class HyperlinkToolbarView { } } - if (this.mouseHoveredHyperlinkMark) { - this.hyperlinkMark = this.mouseHoveredHyperlinkMark; - this.hyperlinkMarkRange = this.mouseHoveredHyperlinkMarkRange; + if (this.mouseHoveredLinkMark) { + this.linkMark = this.mouseHoveredLinkMark; + this.linkMarkRange = this.mouseHoveredLinkMarkRange; } - // Keyboard cursor position takes precedence over mouse hovered hyperlink. - if (this.keyboardHoveredHyperlinkMark) { - this.hyperlinkMark = this.keyboardHoveredHyperlinkMark; - this.hyperlinkMarkRange = this.keyboardHoveredHyperlinkMarkRange; + // Keyboard cursor position takes precedence over mouse hovered link. + if (this.keyboardHoveredLinkMark) { + this.linkMark = this.keyboardHoveredLinkMark; + this.linkMarkRange = this.keyboardHoveredLinkMarkRange; } - if (this.hyperlinkMark && this.editor.isEditable) { + if (this.linkMark && this.editor.isEditable) { this.state = { show: true, referencePos: posToDOMRect( this.pmView, - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.to + this.linkMarkRange!.from, + this.linkMarkRange!.to ), - url: this.hyperlinkMark!.attrs.href, + url: this.linkMark!.attrs.href, text: this.pmView.state.doc.textBetween( - this.hyperlinkMarkRange!.from, - this.hyperlinkMarkRange!.to + this.linkMarkRange!.from, + this.linkMarkRange!.to ), }; this.emitUpdate(); @@ -252,8 +248,8 @@ class HyperlinkToolbarView { // Hides menu. if ( this.state?.show && - prevHyperlinkMark && - (!this.hyperlinkMark || !this.editor.isEditable) + prevLinkMark && + (!this.linkMark || !this.editor.isEditable) ) { this.state.show = false; this.emitUpdate(); @@ -269,24 +265,22 @@ class HyperlinkToolbarView { } } -export const hyperlinkToolbarPluginKey = new PluginKey( - "HyperlinkToolbarPlugin" -); +export const linkToolbarPluginKey = new PluginKey("LinkToolbarPlugin"); -export class HyperlinkToolbarProsemirrorPlugin< +export class LinkToolbarProsemirrorPlugin< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema > extends EventEmitter { - private view: HyperlinkToolbarView | undefined; + private view: LinkToolbarView | undefined; public readonly plugin: Plugin; constructor(editor: BlockNoteEditor) { super(); this.plugin = new Plugin({ - key: hyperlinkToolbarPluginKey, + key: linkToolbarPluginKey, view: (editorView) => { - this.view = new HyperlinkToolbarView(editor, editorView, (state) => { + this.view = new LinkToolbarView(editor, editorView, (state) => { this.emit("update", state); }); return this.view; @@ -294,39 +288,41 @@ export class HyperlinkToolbarProsemirrorPlugin< }); } - public onUpdate(callback: (state: HyperlinkToolbarState) => void) { + public onUpdate(callback: (state: LinkToolbarState) => void) { return this.on("update", callback); } /** - * Edit the currently hovered hyperlink. + * Edit the currently hovered link. */ - public editHyperlink = (url: string, text: string) => { - this.view!.editHyperlink(url, text); + public editLink = (url: string, text: string) => { + this.view!.editLink(url, text); }; /** - * Delete the currently hovered hyperlink. + * Delete the currently hovered link. */ - public deleteHyperlink = () => { - this.view!.deleteHyperlink(); + public deleteLink = () => { + this.view!.deleteLink(); }; /** - * When hovering on/off hyperlinks using the mouse cursor, the hyperlink - * toolbar will open & close with a delay. + * When hovering on/off links using the mouse cursor, the link toolbar will + * open & close with a delay. * - * This function starts the delay timer, and should be used for when the mouse cursor enters the hyperlink toolbar. + * This function starts the delay timer, and should be used for when the mouse + * cursor enters the link toolbar. */ public startHideTimer = () => { this.view!.startMenuUpdateTimer(); }; /** - * When hovering on/off hyperlinks using the mouse cursor, the hyperlink - * toolbar will open & close with a delay. + * When hovering on/off links using the mouse cursor, the link toolbar will + * open & close with a delay. * - * This function stops the delay timer, and should be used for when the mouse cursor exits the hyperlink toolbar. + * This function stops the delay timer, and should be used for when the mouse + * cursor exits the link toolbar. */ public stopHideTimer = () => { this.view!.stopMenuUpdateTimer(); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cc959ce3ac..5a202f72bd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,8 +11,8 @@ export * from "./editor/BlockNoteSchema"; export * from "./editor/selectionTypes"; export * from "./extensions-shared/UiElementPosition"; export * from "./extensions/FormattingToolbar/FormattingToolbarPlugin"; -export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; export * from "./extensions/ImagePanel/ImageToolbarPlugin"; +export * from "./extensions/LinkToolbar/LinkToolbarPlugin"; export * from "./extensions/SideMenu/SideMenuPlugin"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem"; export * from "./extensions/SuggestionMenu/SuggestionPlugin"; diff --git a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx similarity index 70% rename from packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx rename to packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx index 92b97a90e5..d80f8b4e9d 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateHyperlinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx @@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"; import { RiLink } from "react-icons/ri"; import { + BlockNoteEditor, BlockSchema, formatKeyboardShortcut, InlineContentSchema, @@ -13,16 +14,36 @@ import { useEditorContentOrSelectionChange } from "../../../../hooks/useEditorCo import { useSelectedBlocks } from "../../../../hooks/useSelectedBlocks"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; -import { EditHyperlinkMenuItems } from "../../../HyperlinkToolbar/mantine/EditHyperlinkMenuItems"; +import { EditLinkMenuItems } from "../../../LinkToolbar/mantine/EditLinkMenuItems"; -// TODO: Make sure Link is in inline content schema -export const CreateHyperlinkButton = () => { +function checkLinkInSchema( + editor: BlockNoteEditor +): editor is BlockNoteEditor< + BlockSchema, + { + link: { + type: "link"; + propSchema: any; + content: "styled"; + }; + }, + StyleSchema +> { + return ( + "link" in editor.schema.inlineContentSchema && + editor.schema.inlineContentSchema["link"] === "link" + ); +} + +export const CreateLinkButton = () => { const editor = useBlockNoteEditor< BlockSchema, InlineContentSchema, StyleSchema >(); + const linkInSchema = checkLinkInSchema(editor); + const selectedBlocks = useSelectedBlocks(editor); const [url, setUrl] = useState(editor.getSelectedLinkUrl() || ""); @@ -42,6 +63,10 @@ export const CreateHyperlinkButton = () => { ); const show = useMemo(() => { + if (!linkInSchema) { + return false; + } + for (const block of selectedBlocks) { if (block.content === undefined) { return false; @@ -49,7 +74,7 @@ export const CreateHyperlinkButton = () => { } return true; - }, [selectedBlocks]); + }, [linkInSchema, selectedBlocks]); if (!show) { return null; @@ -65,7 +90,7 @@ export const CreateHyperlinkButton = () => { /> } dropdownItems={ - + } /> ); diff --git a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx index e7b15e2330..04b83212d2 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx +++ b/packages/react/src/components/FormattingToolbar/mantine/FormattingToolbar.tsx @@ -4,7 +4,7 @@ import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; import { FormattingToolbarProps } from "../FormattingToolbarProps"; import { BasicTextStyleButton } from "./DefaultButtons/BasicTextStyleButton"; import { ColorStyleButton } from "./DefaultButtons/ColorStyleButton"; -import { CreateHyperlinkButton } from "./DefaultButtons/CreateHyperlinkButton"; +import { CreateLinkButton } from "./DefaultButtons/CreateLinkButton"; import { ImageCaptionButton } from "./DefaultButtons/ImageCaptionButton"; import { NestBlockButton, @@ -36,7 +36,7 @@ export const getFormattingToolbarItems = ( , , , - , + , ]; // TODO: props.blockTypeSelectItems should only be available if no children diff --git a/packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarProps.ts b/packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarProps.ts deleted file mode 100644 index 864b5dbd48..0000000000 --- a/packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarProps.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - BlockNoteEditor, - BlockSchema, - HyperlinkToolbarState, - InlineContentSchema, - StyleSchema, - UiElementPosition, -} from "@blocknote/core"; - -export type HyperlinkToolbarProps = Omit< - HyperlinkToolbarState, - keyof UiElementPosition -> & - Pick< - BlockNoteEditor< - BlockSchema, - InlineContentSchema, - StyleSchema - >["hyperlinkToolbar"], - "deleteHyperlink" | "editHyperlink" | "startHideTimer" | "stopHideTimer" - >; \ No newline at end of file diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarController.tsx b/packages/react/src/components/LinkToolbar/LinkToolbarController.tsx similarity index 66% rename from packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarController.tsx rename to packages/react/src/components/LinkToolbar/LinkToolbarController.tsx index 3433267745..0ec27ab170 100644 --- a/packages/react/src/components/HyperlinkToolbar/HyperlinkToolbarController.tsx +++ b/packages/react/src/components/LinkToolbar/LinkToolbarController.tsx @@ -12,27 +12,27 @@ import { FC } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor"; import { useUIElementPositioning } from "../../hooks/useUIElementPositioning"; import { useUIPluginState } from "../../hooks/useUIPluginState"; -import { HyperlinkToolbarProps } from "./HyperlinkToolbarProps"; -import { HyperlinkToolbar } from "./mantine/HyperlinkToolbar"; +import { LinkToolbarProps } from "./LinkToolbarProps"; +import { LinkToolbar } from "./mantine/LinkToolbar"; -export const HyperlinkToolbarController = < +export const LinkToolbarController = < BSchema extends BlockSchema = DefaultBlockSchema, I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema >(props: { - hyperlinkToolbar?: FC; + linkToolbar?: FC; }) => { const editor = useBlockNoteEditor(); const callbacks = { - deleteHyperlink: editor.hyperlinkToolbar.deleteHyperlink, - editHyperlink: editor.hyperlinkToolbar.editHyperlink, - startHideTimer: editor.hyperlinkToolbar.startHideTimer, - stopHideTimer: editor.hyperlinkToolbar.stopHideTimer, + deleteLink: editor.linkToolbar.deleteLink, + editLink: editor.linkToolbar.editLink, + startHideTimer: editor.linkToolbar.startHideTimer, + stopHideTimer: editor.linkToolbar.stopHideTimer, }; const state = useUIPluginState( - editor.hyperlinkToolbar.onUpdate.bind(editor.hyperlinkToolbar) + editor.linkToolbar.onUpdate.bind(editor.linkToolbar) ); const { isMounted, ref, style } = useUIElementPositioning( state?.show || false, @@ -50,7 +50,7 @@ export const HyperlinkToolbarController = < const { show, referencePos, ...data } = state; - const Component = props.hyperlinkToolbar || HyperlinkToolbar; + const Component = props.linkToolbar || LinkToolbar; return (
diff --git a/packages/react/src/components/LinkToolbar/LinkToolbarProps.ts b/packages/react/src/components/LinkToolbar/LinkToolbarProps.ts new file mode 100644 index 0000000000..d56fb3d327 --- /dev/null +++ b/packages/react/src/components/LinkToolbar/LinkToolbarProps.ts @@ -0,0 +1,18 @@ +import { + BlockNoteEditor, + BlockSchema, + LinkToolbarState, + InlineContentSchema, + StyleSchema, + UiElementPosition, +} from "@blocknote/core"; + +export type LinkToolbarProps = Omit & + Pick< + BlockNoteEditor< + BlockSchema, + InlineContentSchema, + StyleSchema + >["linkToolbar"], + "deleteLink" | "editLink" | "startHideTimer" | "stopHideTimer" + >; diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx similarity index 54% rename from packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx rename to packages/react/src/components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx index 34aee50f1f..f1ccf37fbb 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/DeleteHyperlinkButton.tsx +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx @@ -1,14 +1,14 @@ import { RiLinkUnlink } from "react-icons/ri"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; -import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; -export const DeleteHyperlinkButton = ( - props: Pick +export const DeleteLinkButton = ( + props: Pick ) => ( ); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx similarity index 51% rename from packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx rename to packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx index a6968a06c5..ae1c879ce5 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/EditHyperlinkButton.tsx +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx @@ -1,10 +1,10 @@ import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; -import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; -import { EditHyperlinkMenuItems } from "../EditHyperlinkMenuItems"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; +import { EditLinkMenuItems } from "../EditLinkMenuItems"; -export const EditHyperlinkButton = ( - props: Pick +export const EditLinkButton = ( + props: Pick ) => ( } - dropdownItems={} + dropdownItems={} /> ); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx similarity index 66% rename from packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx rename to packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx index 788dc8724f..abe52b41de 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/DefaultButtons/OpenHyperlinkButton.tsx +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx @@ -1,10 +1,8 @@ import { RiExternalLinkFill } from "react-icons/ri"; import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; -import { HyperlinkToolbarProps } from "../../HyperlinkToolbarProps"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; -export const OpenHyperlinkButton = ( - props: Pick -) => ( +export const OpenLinkButton = (props: Pick) => ( +export const EditLinkMenuItems = ( + props: Pick ) => { - const { url, text, editHyperlink } = props; + const { url, text, editLink } = props; const [currentUrl, setCurrentUrl] = useState(url); const [currentText, setCurrentText] = useState(text); @@ -26,10 +26,10 @@ export const EditHyperlinkMenuItems = ( (event: KeyboardEvent) => { if (event.key === "Enter") { event.preventDefault(); - editHyperlink(currentUrl, currentText); + editLink(currentUrl, currentText); } }, - [editHyperlink, currentUrl, currentText] + [editLink, currentUrl, currentText] ); const handleUrlChange = useCallback( @@ -45,8 +45,8 @@ export const EditHyperlinkMenuItems = ( ); const handleSubmit = useCallback( - () => editHyperlink(currentUrl, currentText), - [editHyperlink, currentUrl, currentText] + () => editLink(currentUrl, currentText), + [editLink, currentUrl, currentText] ); return ( diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx b/packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx similarity index 54% rename from packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx rename to packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx index eee9a7d365..4cac275c65 100644 --- a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx +++ b/packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx @@ -1,13 +1,13 @@ import { ReactNode } from "react"; -import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; +import { LinkToolbarProps } from "../LinkToolbarProps"; import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { EditHyperlinkButton } from "./DefaultButtons/EditHyperlinkButton"; -import { OpenHyperlinkButton } from "./DefaultButtons/OpenHyperlinkButton"; -import { DeleteHyperlinkButton } from "./DefaultButtons/DeleteHyperlinkButton"; +import { EditLinkButton } from "./DefaultButtons/EditLinkButton"; +import { OpenLinkButton } from "./DefaultButtons/OpenLinkButton"; +import { DeleteLinkButton } from "./DefaultButtons/DeleteLinkButton"; /** - * By default, the HyperlinkToolbar component will render with default buttons. + * By default, the LinkToolbar component will render with default buttons. * However, you can override the selects/buttons to render by passing * children. The children you pass should be: * @@ -17,8 +17,8 @@ import { DeleteHyperlinkButton } from "./DefaultButtons/DeleteHyperlinkButton"; * - Custom buttons: The `ToolbarButton` component in the * `components/mantine-shared/Toolbar` directory. */ -export const HyperlinkToolbar = ( - props: HyperlinkToolbarProps & { children?: ReactNode } +export const LinkToolbar = ( + props: LinkToolbarProps & { children?: ReactNode } ) => { if (props.children) { return {props.children}; @@ -28,13 +28,13 @@ export const HyperlinkToolbar = ( - - - + + ); }; diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index bf165aa616..3fd44a55dd 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -1,6 +1,6 @@ import { filterSuggestionItems } from "@blocknote/core"; import { FormattingToolbarController } from "../components/FormattingToolbar/FormattingToolbarController"; -import { HyperlinkToolbarController } from "../components/HyperlinkToolbar/HyperlinkToolbarController"; +import { LinkToolbarController } from "../components/LinkToolbar/LinkToolbarController"; import { ImagePanelController } from "../components/ImagePanel/ImagePanelController"; import { SideMenuController } from "../components/SideMenu/SideMenuController"; import { getDefaultReactSlashMenuItems } from "../components/SuggestionMenu/getDefaultReactSlashMenuItems"; @@ -10,7 +10,7 @@ import { useBlockNoteEditor } from "../hooks/useBlockNoteEditor"; export type BlockNoteDefaultUIProps = { formattingToolbar?: boolean; - hyperlinkToolbar?: boolean; + linkToolbar?: boolean; slashMenu?: boolean; sideMenu?: boolean; imageToolbar?: boolean; @@ -29,7 +29,7 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { return ( <> {props.formattingToolbar !== false && } - {props.hyperlinkToolbar !== false && } + {props.linkToolbar !== false && } {props.slashMenu !== false && ( diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index 764159418a..bf95fc3ffa 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -91,7 +91,7 @@ function BlockNoteViewComponent< onSelectionChange, onChange, formattingToolbar, - hyperlinkToolbar, + linkToolbar, slashMenu, sideMenu, imageToolbar, @@ -163,7 +163,7 @@ function BlockNoteViewComponent< {children} { "dark-formatting-toolbar.png" ); }); - test("Should show dark hyperlink toolbar", async ({ page }) => { + test("Should show dark link toolbar", async ({ page }) => { await focusOnEditor(page); await page.keyboard.type("Paragraph"); await page.keyboard.press("Shift+Home"); @@ -47,9 +47,7 @@ test.describe("Check Dark Theme is Automatically Applied", () => { await page.keyboard.press("Enter"); await page.waitForTimeout(500); - expect(await page.screenshot()).toMatchSnapshot( - "dark-hyperlink-toolbar.png" - ); + expect(await page.screenshot()).toMatchSnapshot("dark-link-toolbar.png"); }); test("Should show dark slash menu", async ({ page }) => { await focusOnEditor(page); diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-chromium-linux.png deleted file mode 100644 index 3f49e9fab136833e2f8b98840cc59d0a04338eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11022 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 diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a93bb3dba940f88c28e812136b8755f8b9add218 GIT binary patch literal 11014 zcmeHNSyWS5m%a=tDk2e-d5}02DoCNslZv9EprC@F1W6$Z$RI<|KoYf(Fqh?zAft+a zh=>s)$V>^OWD0^1#srdL9%2lE5J=MJ`ggB>>!*I`UW>fk+?#vO*=O%>fBV~KCHB07 z-Ih(Nn;;0$_^JIRjoZ%y0{ zj~wt;u`S#d)?A#G{^#&Wbliw{=

KJ3mE@#GUUI#-0*}zC{0XwF)aH%+GaCJ;~e^ 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 e4*qW){1|OXU#chXYaQ>@AJHS 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 F{s&SqimU(t diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-webkit-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..16e64013a93d27fcaabb4791278763ad0b221e8f GIT binary patch literal 24340 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