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/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..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` - component should be default buttons (e.g. TODO) or custom dropdowns/buttons - (`ToolbarDropdown` & `ToolbarButton`). To see all the components you can - use, head to the [Hyperlink Toolbar's source code](link). + 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 [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 e34a0d9cdf..8f44929e33 100644 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx @@ -2,7 +2,7 @@ import "@blocknote/core/fonts/inter.css"; import { BasicTextStyleButton, BlockNoteView, - BlockTypeDropdown, + BlockTypeSelect, ColorStyleButton, CreateLinkButton, FormattingToolbar, @@ -72,7 +72,7 @@ export default function App() { ( - + {/* Extra button to toggle blue text & background */} 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/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 1b8c79a96f..edb7f96322 100644 --- a/examples/05-custom-schema/03-font-style/App.tsx +++ b/examples/05-custom-schema/03-font-style/App.tsx @@ -3,7 +3,7 @@ import "@blocknote/core/fonts/inter.css"; import { BasicTextStyleButton, BlockNoteView, - BlockTypeDropdown, + BlockTypeSelect, ColorStyleButton, CreateLinkButton, FormattingToolbar, @@ -99,7 +99,7 @@ export default function App() { ( - + 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 659e9ff823..001782123b 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -27,10 +27,10 @@ import { PartialBlock, } from "../blocks/defaultBlocks"; import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin"; -import { HyperlinkToolbarProsemirrorPlugin } from "../extensions/HyperlinkToolbar/HyperlinkToolbarPlugin"; -import { ImageToolbarProsemirrorPlugin } from "../extensions/ImageToolbar/ImageToolbarPlugin"; +import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin"; 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< @@ -156,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 @@ -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 @@ -232,12 +230,12 @@ 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)) { // 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); @@ -260,10 +258,10 @@ export class BlockNoteEditor< addProseMirrorPlugins: () => { return [ this.formattingToolbar.plugin, - this.hyperlinkToolbar.plugin, + this.linkToolbar.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/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/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..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/ImageToolbar/ImageToolbarPlugin"; +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/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/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.tsx index 9de18b94db..d80f8b4e9d 100644 --- a/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/CreateLinkButton.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, @@ -11,11 +12,29 @@ 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 { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; +import { EditLinkMenuItems } from "../../../LinkToolbar/mantine/EditLinkMenuItems"; + +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" + ); +} -// TODO: Make sure Link is in inline content schema export const CreateLinkButton = () => { const editor = useBlockNoteEditor< BlockSchema, @@ -23,6 +42,8 @@ export const CreateLinkButton = () => { StyleSchema >(); + const linkInSchema = checkLinkInSchema(editor); + const selectedBlocks = useSelectedBlocks(editor); const [url, setUrl] = useState(editor.getSelectedLinkUrl() || ""); @@ -42,6 +63,10 @@ export const CreateLinkButton = () => { ); const show = useMemo(() => { + if (!linkInSchema) { + return false; + } + for (const block of selectedBlocks) { if (block.content === undefined) { return false; @@ -49,22 +74,24 @@ export const CreateLinkButton = () => { } return true; - }, [selectedBlocks]); + }, [linkInSchema, selectedBlocks]); if (!show) { return null; } 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/DefaultButtons/ReplaceImageButton.tsx b/packages/react/src/components/FormattingToolbar/mantine/DefaultButtons/ReplaceImageButton.tsx index d878532d87..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 { ImageToolbar } from "../../../ImageToolbar/mantine/ImageToolbar"; +import { ImagePanel } from "../../../ImagePanel/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/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 ef5b6fea49..04b83212d2 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"; @@ -11,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[] => [ - , + , , , , @@ -40,27 +39,26 @@ 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. */ export const FormattingToolbar = ( - props: FormattingToolbarProps & { children?: React.ReactNode } + props: FormattingToolbarProps & { children?: ReactNode } ) => { return ( - {props.children || - getFormattingToolbarItems(props.blockTypeDropdownItems)} + {props.children || getFormattingToolbarItems(props.blockTypeSelectItems)} ); }; 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/EditHyperlinkMenu.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu.tsx deleted file mode 100644 index d5a71daff6..0000000000 --- a/packages/react/src/components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { - ChangeEvent, - forwardRef, - HTMLAttributes, - KeyboardEvent, - useCallback, - useEffect, - useState, -} from "react"; -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; -}; - -/** - * 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); - - useEffect(() => { - setCurrentUrl(url); - setCurrentText(text); - }, [text, url]); - - const handleEnter = useCallback( - (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - update(currentUrl, currentText); - } - }, - [update, currentUrl, currentText] - ); - - const handleUrlChange = useCallback( - (event: ChangeEvent) => - setCurrentUrl(event.currentTarget.value), - [] - ); - - const handleTextChange = useCallback( - (event: ChangeEvent) => - setCurrentText(event.currentTarget.value), - [] - ); - - const handleSubmit = useCallback( - () => update(currentUrl, currentText), - [update, currentUrl, currentText] - ); - - return ( - - - - - ); -}); diff --git a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx b/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx deleted file mode 100644 index ba931b5d31..0000000000 --- a/packages/react/src/components/HyperlinkToolbar/mantine/HyperlinkToolbar.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useRef, useState } from "react"; -import { RiExternalLinkFill, RiLinkUnlink } from "react-icons/ri"; - -import { HyperlinkToolbarProps } from "../HyperlinkToolbarProps"; -import { EditHyperlinkMenu } from "./EditHyperlinkMenu/EditHyperlinkMenu"; -import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { ToolbarButton } from "../../mantine-shared/Toolbar/ToolbarButton"; - -/** - * 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 - * - 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 } -) => { - 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/ImageToolbarController.tsx b/packages/react/src/components/ImagePanel/ImagePanelController.tsx similarity index 78% rename from packages/react/src/components/ImageToolbar/ImageToolbarController.tsx rename to packages/react/src/components/ImagePanel/ImagePanelController.tsx index 67da6a8071..adadd8a364 100644 --- a/packages/react/src/components/ImageToolbar/ImageToolbarController.tsx +++ b/packages/react/src/components/ImagePanel/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"] }, @@ -26,14 +26,14 @@ export const ImageToolbarController = < 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, @@ -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/ImagePanel/ImagePanelProps.ts similarity index 69% rename from packages/react/src/components/ImageToolbar/ImageToolbarProps.ts rename to packages/react/src/components/ImagePanel/ImagePanelProps.ts index 3485227125..3ee608a3fa 100644 --- a/packages/react/src/components/ImageToolbar/ImageToolbarProps.ts +++ b/packages/react/src/components/ImagePanel/ImagePanelProps.ts @@ -1,13 +1,13 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, - ImageToolbarState, + ImagePanelState, InlineContentSchema, StyleSchema, 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/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx new file mode 100644 index 0000000000..da4db82ac9 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx @@ -0,0 +1,80 @@ +import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor"; +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react"; + +import { ImagePanelProps } from "../../ImagePanelProps"; +import { ImagePanelTab } from "../ImagePanelTab"; +import { ImagePanelTextInput } from "../ImagePanelTextInput"; +import { ImagePanelButton } from "../ImagePanelButton"; + +export const EmbedTab = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: ImagePanelProps +) => { + 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 ( + + + + Embed Image + + + ); +}; diff --git a/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx new file mode 100644 index 0000000000..cbc974eda7 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx @@ -0,0 +1,87 @@ +import { 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 { ImagePanelProps } from "../../ImagePanelProps"; +import { ImagePanelTab } from "../ImagePanelTab"; +import { ImagePanelFileInput } from "../ImagePanelFileInput"; + +export const UploadTab = < + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: ImagePanelProps & { + 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/ImagePanel/mantine/ImagePanel.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx new file mode 100644 index 0000000000..d5dd599bf8 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx @@ -0,0 +1,74 @@ +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { Group, LoadingOverlay, Tabs } from "@mantine/core"; +import { ReactNode, useState } from "react"; + +import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; +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/ImagePanel/mantine/ImagePanelButton.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx new file mode 100644 index 0000000000..56ea90afe6 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx @@ -0,0 +1,11 @@ +import { ComponentPropsWithoutRef, forwardRef } from "react"; +import { Button } from "@mantine/core"; + +export const ImagePanelButton = forwardRef< + HTMLButtonElement, + Omit, "size"> +>((props, ref) => ( + +)); diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx new file mode 100644 index 0000000000..3ec61c5e72 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx @@ -0,0 +1,17 @@ +import { FileInput } from "@mantine/core"; +import { ComponentPropsWithoutRef, forwardRef, ReactNode } from "react"; + +export const ImagePanelFileInput = 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/ImagePanel/mantine/ImagePanelTab.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx new file mode 100644 index 0000000000..29e4689c39 --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx @@ -0,0 +1,18 @@ +import { ComponentPropsWithoutRef, forwardRef } from "react"; +import { mergeCSSClasses } from "@blocknote/core"; + +export const ImagePanelTab = forwardRef< + HTMLDivElement, + ComponentPropsWithoutRef<"div"> +>((props, ref) => { + const { className, children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}); diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx new file mode 100644 index 0000000000..c008ef7f7f --- /dev/null +++ b/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx @@ -0,0 +1,9 @@ +import { TextInput } from "@mantine/core"; +import { ComponentPropsWithoutRef, forwardRef } from "react"; + +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 22a5f6c93a..0000000000 --- a/packages/react/src/components/ImageToolbar/mantine/ImageToolbar.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { - DefaultBlockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema, - InlineContentSchema, - StyleSchema, -} from "@blocknote/core"; -import { - Button, - FileInput, - LoadingOverlay, - Tabs, - Text, - TextInput, -} from "@mantine/core"; -import { - ChangeEvent, - KeyboardEvent, - useCallback, - useEffect, - useState, -} from "react"; - -import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor"; -import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; -import { ImageToolbarProps } from "../ImageToolbarProps"; - -export const ImageToolbar = < - I extends InlineContentSchema = DefaultInlineContentSchema, - S extends StyleSchema = DefaultStyleSchema ->( - props: ImageToolbarProps -) => { - const editor = useBlockNoteEditor< - { image: DefaultBlockSchema["image"] }, - I, - S - >(); - - 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 [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(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 && } - - - {editor.uploadFile !== undefined && ( - - Upload - - )} - - Embed - - - - {editor.uploadFile !== undefined && ( - -
- - {uploadFailed && ( - - Error: Upload failed - - )} -
-
- )} - -
- - -
-
-
-
- ); -}; 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/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx new file mode 100644 index 0000000000..f1ccf37fbb --- /dev/null +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton.tsx @@ -0,0 +1,14 @@ +import { RiLinkUnlink } from "react-icons/ri"; +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; + +export const DeleteLinkButton = ( + props: Pick +) => ( + +); diff --git a/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx new file mode 100644 index 0000000000..ae1c879ce5 --- /dev/null +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/EditLinkButton.tsx @@ -0,0 +1,17 @@ +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { ToolbarInputsMenu } from "../../../mantine-shared/Toolbar/ToolbarInputsMenu"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; +import { EditLinkMenuItems } from "../EditLinkMenuItems"; + +export const EditLinkButton = ( + props: Pick +) => ( + + Edit Link + + } + dropdownItems={} + /> +); diff --git a/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx new file mode 100644 index 0000000000..abe52b41de --- /dev/null +++ b/packages/react/src/components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton.tsx @@ -0,0 +1,14 @@ +import { RiExternalLinkFill } from "react-icons/ri"; +import { ToolbarButton } from "../../../mantine-shared/Toolbar/ToolbarButton"; +import { LinkToolbarProps } from "../../LinkToolbarProps"; + +export const OpenLinkButton = (props: Pick) => ( + { + window.open(props.url, "_blank"); + }} + icon={RiExternalLinkFill} + /> +); diff --git a/packages/react/src/components/LinkToolbar/mantine/EditLinkMenuItems.tsx b/packages/react/src/components/LinkToolbar/mantine/EditLinkMenuItems.tsx new file mode 100644 index 0000000000..b868ece55d --- /dev/null +++ b/packages/react/src/components/LinkToolbar/mantine/EditLinkMenuItems.tsx @@ -0,0 +1,75 @@ +import { + ChangeEvent, + KeyboardEvent, + useCallback, + useEffect, + useState, +} from "react"; +import { RiLink, RiText } from "react-icons/ri"; +import { LinkToolbarProps } from "../LinkToolbarProps"; +import { ToolbarInputsMenuItem } from "../../mantine-shared/Toolbar/ToolbarInputsMenuItem"; + +export const EditLinkMenuItems = ( + props: Pick +) => { + const { url, text, editLink } = 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(); + editLink(currentUrl, currentText); + } + }, + [editLink, currentUrl, currentText] + ); + + const handleUrlChange = useCallback( + (event: ChangeEvent) => + setCurrentUrl(event.currentTarget.value), + [] + ); + + const handleTextChange = useCallback( + (event: ChangeEvent) => + setCurrentText(event.currentTarget.value), + [] + ); + + const handleSubmit = useCallback( + () => editLink(currentUrl, currentText), + [editLink, currentUrl, currentText] + ); + + return ( + <> + + + + ); +}; diff --git a/packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx b/packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx new file mode 100644 index 0000000000..4cac275c65 --- /dev/null +++ b/packages/react/src/components/LinkToolbar/mantine/LinkToolbar.tsx @@ -0,0 +1,40 @@ +import { ReactNode } from "react"; + +import { LinkToolbarProps } from "../LinkToolbarProps"; +import { Toolbar } from "../../mantine-shared/Toolbar/Toolbar"; +import { EditLinkButton } from "./DefaultButtons/EditLinkButton"; +import { OpenLinkButton } from "./DefaultButtons/OpenLinkButton"; +import { DeleteLinkButton } from "./DefaultButtons/DeleteLinkButton"; + +/** + * 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: + * + * - Default buttons: Components found within the `/DefaultButtons` directory. + * - Custom selects: The `ToolbarSelect` component in the + * `components/mantine-shared/Toolbar` directory. + * - Custom buttons: The `ToolbarButton` component in the + * `components/mantine-shared/Toolbar` directory. + */ +export const LinkToolbar = ( + props: LinkToolbarProps & { children?: ReactNode } +) => { + if (props.children) { + return {props.children}; + } + + return ( + + + + + + ); +}; 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/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/TableHandles/mantine/TableHandle.tsx b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx index ed8dd67835..7d6945e6d5 100644 --- a/packages/react/src/components/TableHandles/mantine/TableHandle.tsx +++ b/packages/react/src/components/TableHandles/mantine/TableHandle.tsx @@ -2,20 +2,73 @@ 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"; +/** + * 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/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/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index df74cc0b45..3fd44a55dd 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 { LinkToolbarController } from "../components/LinkToolbar/LinkToolbarController"; +import { ImagePanelController } from "../components/ImagePanel/ImagePanelController"; import { SideMenuController } from "../components/SideMenu/SideMenuController"; import { getDefaultReactSlashMenuItems } from "../components/SuggestionMenu/getDefaultReactSlashMenuItems"; import { SuggestionMenuController } from "../components/SuggestionMenu/SuggestionMenuController"; @@ -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 && ( @@ -43,8 +43,8 @@ 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/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} div { - align-items: stretch; - display: flex; - flex-direction: column; - gap: 8px; -} - -.bn-container - .bn-image-toolbar - .bn-upload-image-panel - > div - > .mantine-Text-root { - text-align: center; -} - -.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 - .bn-embed-image-panel - > div - .mantine-TextInput-root { - width: 100%; -} - /* Slash Menu styling*/ .bn-container .bn-slash-menu { max-height: 100%; @@ -512,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/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 3f05fdbf12..05fbab957b 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -14,13 +14,16 @@ 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"; -export * from "./components/HyperlinkToolbar/HyperlinkToolbarProps"; -export * from "./components/HyperlinkToolbar/mantine/EditHyperlinkMenu/EditHyperlinkMenu"; -export * from "./components/HyperlinkToolbar/mantine/HyperlinkToolbar"; +export * from "./components/LinkToolbar/LinkToolbarController"; +export * from "./components/LinkToolbar/LinkToolbarProps"; +export * from "./components/LinkToolbar/mantine/DefaultButtons/DeleteLinkButton"; +export * from "./components/LinkToolbar/mantine/DefaultButtons/EditLinkButton"; +export * from "./components/LinkToolbar/mantine/DefaultButtons/OpenLinkButton"; +export * from "./components/LinkToolbar/mantine/LinkToolbar"; +export * from "./components/LinkToolbar/mantine/EditLinkMenuItems"; export * from "./components/SideMenu/SideMenuController"; export * from "./components/SideMenu/SideMenuProps"; @@ -45,17 +48,29 @@ 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/ImageToolbar"; +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/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"; +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..db0ab26cb0 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" @@ -330,16 +330,16 @@ } }, { - "projectSlug": "hyperlink-toolbar-buttons", - "fullSlug": "ui-components/hyperlink-toolbar-buttons", - "pathFromRoot": "examples/02-ui-components/hyperlink-toolbar-buttons", + "projectSlug": "link-toolbar-buttons", + "fullSlug": "ui-components/link-toolbar-buttons", + "pathFromRoot": "examples/02-ui-components/link-toolbar-buttons", "config": { "playground": true, "docs": false, "author": "matthewlipski", "tags": [] }, - "title": "Adding Hyperlink Toolbar Buttons", + "title": "Adding Link Toolbar Buttons", "group": { "pathFromRoot": "examples/02-ui-components", "slug": "ui-components" diff --git a/tests/src/end-to-end/theming/theming.test.ts b/tests/src/end-to-end/theming/theming.test.ts index 8c56f211a0..b346ea6617 100644 --- a/tests/src/end-to-end/theming/theming.test.ts +++ b/tests/src/end-to-end/theming/theming.test.ts @@ -35,7 +35,7 @@ test.describe("Check Dark Theme is Automatically Applied", () => { "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 3f49e9fab1..0000000000 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-chromium-linux.png and /dev/null differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-webkit-linux.png deleted file mode 100644 index 96983737cd..0000000000 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-webkit-linux.png and /dev/null differ 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 0000000000..a93bb3dba9 Binary files /dev/null and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-firefox-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-firefox-linux.png similarity index 51% rename from tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-firefox-linux.png rename to tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-firefox-linux.png index 236728b17b..c9a023d5f4 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-hyperlink-toolbar-firefox-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-firefox-linux.png differ 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 0000000000..16e64013a9 Binary files /dev/null and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-webkit-linux.png differ