UNPKG

@blocknote/react

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

1 lines 397 kB
{"version":3,"file":"blocknote-react.cjs","names":[],"sources":["../src/components/Popovers/BlockPopover.tsx","../src/components/FilePanel/DefaultTabs/EmbedTab.tsx","../src/components/FilePanel/DefaultTabs/UploadTab.tsx","../src/components/FilePanel/FilePanel.tsx","../src/components/FilePanel/FilePanelController.tsx","../src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx","../src/components/ColorPicker/ColorIcon.tsx","../src/components/ColorPicker/ColorPicker.tsx","../src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx","../src/components/LinkToolbar/EditLinkMenuItems.tsx","../src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx","../src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx","../src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx","../src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx","../src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx","../src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx","../src/components/FormattingToolbar/DefaultSelects/BlockTypeSelect.tsx","../src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx","../src/components/FormattingToolbar/DefaultButtons/AddTiptapCommentButton.tsx","../src/util/sanitizeUrl.ts","../src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx","../src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx","../src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx","../src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx","../src/components/FormattingToolbar/FormattingToolbar.tsx","../src/components/FormattingToolbar/FormattingToolbarController.tsx","../src/components/LinkToolbar/DefaultButtons/DeleteLinkButton.tsx","../src/components/LinkToolbar/DefaultButtons/EditLinkButton.tsx","../src/components/LinkToolbar/DefaultButtons/OpenLinkButton.tsx","../src/components/LinkToolbar/LinkToolbar.tsx","../src/components/LinkToolbar/LinkToolbarController.tsx","../src/components/SideMenu/DefaultButtons/AddBlockButton.tsx","../src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx","../src/components/SideMenu/DragHandleMenu/DefaultItems/RemoveBlockItem.tsx","../src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx","../src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx","../src/components/SideMenu/DefaultButtons/DragHandleButton.tsx","../src/components/SideMenu/SideMenu.tsx","../src/components/SideMenu/SideMenuController.tsx","../src/components/SuggestionMenu/GridSuggestionMenu/getDefaultReactEmojiPickerItems.tsx","../src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenu.tsx","../src/components/SuggestionMenu/hooks/useCloseSuggestionMenuNoItems.ts","../src/components/SuggestionMenu/hooks/useLoadSuggestionMenuItems.ts","../src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts","../src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx","../src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx","../src/components/SuggestionMenu/SuggestionMenu.tsx","../src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardHandler.ts","../src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts","../src/components/SuggestionMenu/SuggestionMenuWrapper.tsx","../src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx","../src/components/SuggestionMenu/SuggestionMenuController.tsx","../src/components/TableHandles/ExtendButton/ExtendButton.tsx","../src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx","../src/components/TableHandles/TableCellMenu/DefaultButtons/SplitButton.tsx","../src/components/TableHandles/TableCellMenu/TableCellMenu.tsx","../src/components/TableHandles/TableCellButton.tsx","../src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx","../src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx","../src/components/TableHandles/TableHandleMenu/DefaultButtons/ColorPicker.tsx","../src/components/TableHandles/TableHandleMenu/DefaultButtons/TableHeaderButton.tsx","../src/components/TableHandles/TableHandleMenu/TableHandleMenu.tsx","../src/components/TableHandles/TableHandle.tsx","../src/components/TableHandles/TableHandlesController.tsx","../src/editor/portalElements.ts","../src/editor/BlockNoteDefaultUI.tsx","../src/hooks/useEditorChange.ts","../src/hooks/useEditorSelectionChange.ts","../src/hooks/usePrefersColorScheme.ts","../src/editor/BlockNoteViewContext.ts","../src/editor/EditorContent.tsx","../src/editor/ElementRenderer.tsx","../src/editor/BlockNoteView.tsx","../src/schema/@util/ReactRenderUtil.ts","../src/schema/ReactBlockSpec.tsx","../src/blocks/File/useResolveUrl.tsx","../src/blocks/File/helpers/toExternalHTML/FigureWithCaption.tsx","../src/hooks/useOnUploadEnd.ts","../src/hooks/useOnUploadStart.ts","../src/hooks/useUploadLoading.ts","../src/blocks/File/helpers/render/AddFileButton.tsx","../src/blocks/File/helpers/render/FileNameWithIcon.tsx","../src/blocks/File/helpers/render/FileBlockWrapper.tsx","../src/blocks/File/helpers/toExternalHTML/LinkWithCaption.tsx","../src/blocks/Audio/block.tsx","../src/blocks/File/block.tsx","../src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx","../src/blocks/Image/block.tsx","../src/blocks/PageBreak/getPageBreakReactSlashMenuItems.tsx","../src/blocks/Video/block.tsx","../src/blocks/ToggleWrapper/ToggleWrapper.tsx","../src/components/FormattingToolbar/ExperimentalMobileFormattingToolbarController.tsx","../src/components/TableHandles/hooks/useExtendButtonsPositioning.ts","../src/components/TableHandles/hooks/useTableHandlesPositioning.ts","../src/components/Comments/ThreadsSidebar.tsx","../src/hooks/useActiveStyles.ts","../src/hooks/useEditorSelectionBoundingBox.ts","../src/hooks/useFocusWithin.ts","../src/hooks/useSelectedBlocks.ts","../src/schema/ReactInlineContentSpec.tsx","../src/schema/ReactStyleSpec.tsx","../src/util/elementOverflow.ts","../src/util/mergeRefs.ts"],"sourcesContent":["import { getNodeById } from \"@blocknote/core\";\nimport { ReactNode, useMemo } from \"react\";\n\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { FloatingUIOptions } from \"./FloatingUIOptions.js\";\nimport { GenericPopover, GenericPopoverReference } from \"./GenericPopover.js\";\n\nexport const BlockPopover = (\n props: FloatingUIOptions & {\n blockId: string | undefined;\n children: ReactNode;\n portalElement?: HTMLElement | null;\n },\n) => {\n const { blockId, children, portalElement, ...floatingUIOptions } = props;\n\n const editor = useBlockNoteEditor<any, any, any>();\n\n const reference = useMemo<GenericPopoverReference | undefined>(\n () =>\n editor.transact((tr) => {\n if (!blockId) {\n return undefined;\n }\n\n // TODO use the location API for this\n const nodePosInfo = getNodeById(blockId, tr.doc);\n if (!nodePosInfo) {\n return undefined;\n }\n\n const { node } = editor.prosemirrorView.domAtPos(\n nodePosInfo.posBeforeNode + 1,\n );\n if (!(node instanceof Element)) {\n return undefined;\n }\n\n return {\n element: node,\n };\n }),\n [editor, blockId],\n );\n\n return (\n <GenericPopover\n reference={reference}\n portalElement={portalElement}\n {...floatingUIOptions}\n >\n {blockId !== undefined && children}\n </GenericPopover>\n );\n};\n","import {\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n filenameFromURL,\n} from \"@blocknote/core\";\nimport { ChangeEvent, KeyboardEvent, useCallback, useState } from \"react\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\nimport { FilePanelProps } from \"../FilePanelProps.js\";\n\nexport const EmbedTab = <\n B extends BlockSchema = DefaultBlockSchema,\n I extends InlineContentSchema = DefaultInlineContentSchema,\n S extends StyleSchema = DefaultStyleSchema,\n>(\n props: FilePanelProps,\n) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const editor = useBlockNoteEditor<B, I, S>();\n\n const block = editor.getBlock(props.blockId)!;\n\n const [currentURL, setCurrentURL] = useState<string>(\"\");\n\n const handleURLChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n setCurrentURL(event.currentTarget.value);\n },\n [],\n );\n\n const handleURLEnter = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing) {\n event.preventDefault();\n editor.updateBlock(block.id, {\n props: {\n name: filenameFromURL(currentURL),\n url: currentURL,\n } as any,\n });\n }\n },\n [editor, block.id, currentURL],\n );\n\n const handleURLClick = useCallback(() => {\n editor.updateBlock(block.id, {\n props: {\n name: filenameFromURL(currentURL),\n url: currentURL,\n } as any,\n });\n }, [editor, block.id, currentURL]);\n\n return (\n <Components.FilePanel.TabPanel className={\"bn-tab-panel\"}>\n <Components.FilePanel.TextInput\n className={\"bn-text-input\"}\n placeholder={dict.file_panel.embed.url_placeholder}\n value={currentURL}\n onChange={handleURLChange}\n onKeyDown={handleURLEnter}\n data-test={\"embed-input\"}\n />\n <Components.FilePanel.Button\n className={\"bn-button\"}\n onClick={handleURLClick}\n data-test=\"embed-input-button\"\n >\n {dict.file_panel.embed.embed_button[block.type] ||\n dict.file_panel.embed.embed_button[\"file\"]}\n </Components.FilePanel.Button>\n </Components.FilePanel.TabPanel>\n );\n};\n","import {\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\nimport { FilePanelProps } from \"../FilePanelProps.js\";\n\nexport const UploadTab = <\n B extends BlockSchema = DefaultBlockSchema,\n I extends InlineContentSchema = DefaultInlineContentSchema,\n S extends StyleSchema = DefaultStyleSchema,\n>(\n props: FilePanelProps & {\n setLoading: (loading: boolean) => void;\n },\n) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const { setLoading } = props;\n\n const editor = useBlockNoteEditor<B, I, S>();\n\n const block = editor.getBlock(props.blockId)!;\n\n const [uploadFailed, setUploadFailed] = useState<boolean>(false);\n\n useEffect(() => {\n if (uploadFailed) {\n setTimeout(() => {\n setUploadFailed(false);\n }, 3000);\n }\n }, [uploadFailed]);\n\n const handleFileChange = useCallback(\n (file: File | null) => {\n if (file === null) {\n return;\n }\n\n async function upload(file: File) {\n setLoading(true);\n\n if (editor.uploadFile !== undefined) {\n try {\n let updateData = await editor.uploadFile(file, props.blockId);\n if (typeof updateData === \"string\") {\n // received a url\n updateData = {\n props: {\n name: file.name,\n url: updateData,\n },\n };\n }\n editor.updateBlock(props.blockId, updateData);\n } catch (e) {\n setUploadFailed(true);\n } finally {\n setLoading(false);\n }\n }\n }\n\n upload(file);\n },\n [props.blockId, editor, setLoading],\n );\n\n const spec = editor.schema.blockSpecs[block.type];\n const accept = spec.implementation.meta?.fileBlockAccept?.length\n ? spec.implementation.meta.fileBlockAccept.join(\",\")\n : \"*/*\";\n\n return (\n <Components.FilePanel.TabPanel className={\"bn-tab-panel\"}>\n <Components.FilePanel.FileInput\n className=\"bn-file-input\"\n data-test=\"upload-input\"\n accept={accept}\n placeholder={\n dict.file_panel.upload.file_placeholder[block.type] ||\n dict.file_panel.upload.file_placeholder[\"file\"]\n }\n value={null}\n onChange={handleFileChange}\n />\n {uploadFailed && (\n <div className=\"bn-error-text\">\n {dict.file_panel.upload.upload_error}\n </div>\n )}\n </Components.FilePanel.TabPanel>\n );\n};\n","import {\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { useState } from \"react\";\n\nimport {\n ComponentProps,\n useComponentsContext,\n} from \"../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { EmbedTab } from \"./DefaultTabs/EmbedTab.js\";\nimport { UploadTab } from \"./DefaultTabs/UploadTab.js\";\nimport { FilePanelProps } from \"./FilePanelProps.js\";\n\ntype PanelProps = ComponentProps[\"FilePanel\"][\"Root\"];\n\n/**\n * By default, the FilePanel component will render with default tabs. However,\n * you can override the tabs to render by passing the `tabs` prop. You can use\n * the default tab panels in the `DefaultTabPanels` directory or make your own\n * using the `FilePanelPanel` component.\n */\nexport const FilePanel = <\n B extends BlockSchema = DefaultBlockSchema,\n I extends InlineContentSchema = DefaultInlineContentSchema,\n S extends StyleSchema = DefaultStyleSchema,\n>(\n props: FilePanelProps & Partial<Pick<PanelProps, \"defaultOpenTab\" | \"tabs\">>,\n) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const editor = useBlockNoteEditor<B, I, S>();\n\n const [loading, setLoading] = useState<boolean>(false);\n\n const tabs: PanelProps[\"tabs\"] = props.tabs ?? [\n ...(editor.uploadFile !== undefined\n ? [\n {\n name: dict.file_panel.upload.title,\n tabPanel: (\n <UploadTab blockId={props.blockId} setLoading={setLoading} />\n ),\n },\n ]\n : []),\n {\n name: dict.file_panel.embed.title,\n tabPanel: <EmbedTab blockId={props.blockId} />,\n },\n ];\n\n const [openTab, setOpenTab] = useState<string>(\n props.defaultOpenTab || tabs[0].name,\n );\n\n return (\n <Components.FilePanel.Root\n className={\"bn-panel\"}\n defaultOpenTab={openTab}\n openTab={openTab}\n setOpenTab={setOpenTab}\n tabs={tabs}\n loading={loading}\n />\n );\n};\n","import { FilePanelExtension } from \"@blocknote/core/extensions\";\nimport { flip, offset } from \"@floating-ui/react\";\nimport { FC, useMemo } from \"react\";\n\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { useExtension, useExtensionState } from \"../../hooks/useExtension.js\";\nimport { BlockPopover } from \"../Popovers/BlockPopover.js\";\nimport { FloatingUIOptions } from \"../Popovers/FloatingUIOptions.js\";\nimport { FilePanel } from \"./FilePanel.js\";\nimport { FilePanelProps } from \"./FilePanelProps.js\";\n\nexport const FilePanelController = (props: {\n filePanel?: FC<FilePanelProps>;\n floatingUIOptions?: FloatingUIOptions;\n /**\n * Override the DOM node this floating element portals into. Falls back to\n * `editor.portalElement` (which by default is mounted inside `bn-container`)\n * when omitted.\n */\n portalElement?: HTMLElement | null;\n}) => {\n const editor = useBlockNoteEditor<any, any, any>();\n\n const filePanel = useExtension(FilePanelExtension);\n const blockId = useExtensionState(FilePanelExtension);\n\n const floatingUIOptions = useMemo<FloatingUIOptions>(\n () => ({\n ...props.floatingUIOptions,\n useFloatingOptions: {\n open: !!blockId,\n // Needed as hooks like `useDismiss` call `onOpenChange` to change the\n // open state.\n onOpenChange: (open, _event, reason) => {\n if (!open) {\n filePanel.closeMenu();\n }\n\n if (reason === \"escape-key\") {\n editor.focus();\n }\n },\n middleware: [offset(10), flip()],\n ...props.floatingUIOptions?.useFloatingOptions,\n },\n focusManagerProps: {\n disabled: true,\n ...props.floatingUIOptions?.focusManagerProps,\n },\n elementProps: {\n style: {\n zIndex: 90,\n },\n ...props.floatingUIOptions?.elementProps,\n },\n }),\n [blockId, editor, filePanel, props.floatingUIOptions],\n );\n\n const Component = props.filePanel || FilePanel;\n\n return (\n <BlockPopover\n blockId={blockId}\n portalElement={props.portalElement}\n {...floatingUIOptions}\n >\n {blockId && <Component blockId={blockId} />}\n </BlockPopover>\n );\n};\n","import {\n BlockNoteEditor,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n formatKeyboardShortcut,\n} from \"@blocknote/core\";\nimport { useCallback } from \"react\";\nimport { IconType } from \"react-icons\";\nimport {\n RiBold,\n RiCodeFill,\n RiItalic,\n RiStrikethrough,\n RiUnderline,\n} from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\n\ntype BasicTextStyle = \"bold\" | \"italic\" | \"underline\" | \"strike\" | \"code\";\n\nconst icons = {\n bold: RiBold,\n italic: RiItalic,\n underline: RiUnderline,\n strike: RiStrikethrough,\n code: RiCodeFill,\n} satisfies Record<BasicTextStyle, IconType>;\n\nfunction checkBasicTextStyleInSchema<Style extends BasicTextStyle>(\n style: Style,\n editor: BlockNoteEditor<BlockSchema, InlineContentSchema, any>,\n): editor is BlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n {\n [k in Style]: {\n type: k;\n propSchema: \"boolean\";\n };\n }\n> {\n return (\n style in editor.schema.styleSchema &&\n editor.schema.styleSchema[style].type === style &&\n editor.schema.styleSchema[style].propSchema === \"boolean\"\n );\n}\n\nexport const BasicTextStyleButton = <Style extends BasicTextStyle>(props: {\n basicTextStyle: Style;\n}) => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const state = useEditorState({\n editor,\n selector: ({ editor }) => {\n // Do not show if:\n if (\n // The editor is read-only.\n !editor.isEditable ||\n // The style is not in the schema.\n !checkBasicTextStyleInSchema(props.basicTextStyle, editor) ||\n // None of the selected blocks have inline content\n !(\n editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ]\n ).find((block) => block.content !== undefined)\n ) {\n return undefined;\n }\n\n return props.basicTextStyle in editor.getActiveStyles()\n ? { active: true }\n : { active: false };\n },\n });\n\n const toggleStyle = useCallback(\n (style: typeof props.basicTextStyle) => {\n editor.focus();\n editor.toggleStyles({ [style]: true } as any);\n },\n [editor, props],\n );\n\n if (state === undefined) {\n return null;\n }\n\n const Icon = icons[props.basicTextStyle] as any; // TODO\n return (\n <Components.FormattingToolbar.Button\n className=\"bn-button\"\n data-test={props.basicTextStyle}\n onClick={() => toggleStyle(props.basicTextStyle)}\n isSelected={state.active}\n label={dict.formatting_toolbar[props.basicTextStyle].tooltip}\n mainTooltip={dict.formatting_toolbar[props.basicTextStyle].tooltip}\n secondaryTooltip={formatKeyboardShortcut(\n dict.formatting_toolbar[props.basicTextStyle].secondary_tooltip,\n dict.generic.ctrl_shortcut,\n )}\n icon={<Icon />}\n />\n );\n};\n","import { useMemo } from \"react\";\n\nexport const ColorIcon = (\n props: Partial<{\n textColor: string | undefined;\n backgroundColor: string | undefined;\n size: number | undefined;\n }>,\n) => {\n const textColor = props.textColor || \"default\";\n const backgroundColor = props.backgroundColor || \"default\";\n const size = props.size || 16;\n\n const style = useMemo(\n () =>\n ({\n pointerEvents: \"none\",\n fontSize: (size * 0.75).toString() + \"px\",\n height: size.toString() + \"px\",\n lineHeight: size.toString() + \"px\",\n textAlign: \"center\",\n width: size.toString() + \"px\",\n }) as const,\n [size],\n );\n\n return (\n <div\n className={\"bn-color-icon\"}\n data-background-color={backgroundColor}\n data-text-color={textColor}\n style={style}\n >\n A\n </div>\n );\n};\n","import { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { ColorIcon } from \"./ColorIcon.js\";\n\nconst colors = [\n \"default\",\n \"gray\",\n \"brown\",\n \"red\",\n \"orange\",\n \"yellow\",\n \"green\",\n \"blue\",\n \"purple\",\n \"pink\",\n] as const;\n\nexport const ColorPicker = (props: {\n onClick?: () => void;\n iconSize?: number;\n text?: {\n color: string;\n setColor: (color: string) => void;\n };\n background?: {\n color: string;\n setColor: (color: string) => void;\n };\n}) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n const TextColorSection = () =>\n props.text ? (\n <>\n <Components.Generic.Menu.Label>\n {dict.color_picker.text_title}\n </Components.Generic.Menu.Label>\n {colors.map((color) => (\n <Components.Generic.Menu.Item\n onClick={() => {\n props.onClick && props.onClick();\n props.text!.setColor(color);\n }}\n data-test={\"text-color-\" + color}\n icon={<ColorIcon textColor={color} size={props.iconSize} />}\n checked={props.text!.color === color}\n key={\"text-color-\" + color}\n >\n {dict.color_picker.colors[color]}\n </Components.Generic.Menu.Item>\n ))}\n </>\n ) : null;\n\n const BackgroundColorSection = () =>\n props.background ? (\n <>\n <Components.Generic.Menu.Label>\n {dict.color_picker.background_title}\n </Components.Generic.Menu.Label>\n {colors.map((color) => (\n <Components.Generic.Menu.Item\n onClick={() => {\n props.onClick && props.onClick();\n props.background!.setColor(color);\n }}\n data-test={\"background-color-\" + color}\n icon={<ColorIcon backgroundColor={color} size={props.iconSize} />}\n key={\"background-color-\" + color}\n checked={props.background!.color === color}\n >\n {dict.color_picker.colors[color]}\n </Components.Generic.Menu.Item>\n ))}\n </>\n ) : null;\n\n return (\n <>\n <TextColorSection />\n <BackgroundColorSection />\n </>\n );\n};\n","import {\n BlockNoteEditor,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { useCallback } from \"react\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\nimport { ColorIcon } from \"../../ColorPicker/ColorIcon.js\";\nimport { ColorPicker } from \"../../ColorPicker/ColorPicker.js\";\n\nfunction checkColorInSchema<Color extends \"text\" | \"background\">(\n color: Color,\n editor: BlockNoteEditor<any, any, any>,\n): editor is BlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n Color extends \"text\"\n ? {\n textColor: {\n type: \"textColor\";\n propSchema: \"string\";\n };\n }\n : {\n backgroundColor: {\n type: \"backgroundColor\";\n propSchema: \"string\";\n };\n }\n> {\n return (\n `${color}Color` in editor.schema.styleSchema &&\n editor.schema.styleSchema[`${color}Color`].type === `${color}Color` &&\n editor.schema.styleSchema[`${color}Color`].propSchema === \"string\"\n );\n}\n\nexport const ColorStyleButton = () => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const textColorInSchema = checkColorInSchema(\"text\", editor);\n const backgroundColorInSchema = checkColorInSchema(\"background\", editor);\n\n const state = useEditorState({\n editor,\n selector: ({ editor }) => {\n // Do not show if:\n if (\n // The editor is read-only.\n !editor.isEditable ||\n // None of the selected blocks have inline content\n !(\n editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ]\n ).find((block) => block.content !== undefined)\n ) {\n return undefined;\n }\n\n const textColorInSchema = checkColorInSchema(\"text\", editor);\n const backgroundColorInSchema = checkColorInSchema(\"background\", editor);\n\n if (!textColorInSchema && !backgroundColorInSchema) {\n return undefined;\n }\n\n return {\n textColor: (textColorInSchema\n ? editor.getActiveStyles().textColor || \"default\"\n : undefined) as string | undefined,\n backgroundColor: (backgroundColorInSchema\n ? editor.getActiveStyles().backgroundColor || \"default\"\n : undefined) as string | undefined,\n };\n },\n });\n\n const setTextColor = useCallback(\n (color: string) => {\n if (!textColorInSchema) {\n throw Error(\n \"Tried to set text color, but style does not exist in editor schema.\",\n );\n }\n\n color === \"default\"\n ? editor.removeStyles({ textColor: color })\n : editor.addStyles({ textColor: color });\n\n setTimeout(() => {\n // timeout needed to ensure compatibility with Mantine Toolbar useFocusTrap\n editor.focus();\n });\n },\n [editor, textColorInSchema],\n );\n\n const setBackgroundColor = useCallback(\n (color: string) => {\n if (!backgroundColorInSchema) {\n throw Error(\n \"Tried to set background color, but style does not exist in editor schema.\",\n );\n }\n\n color === \"default\"\n ? editor.removeStyles({ backgroundColor: color })\n : editor.addStyles({ backgroundColor: color });\n\n setTimeout(() => {\n // timeout needed to ensure compatibility with Mantine Toolbar useFocusTrap\n editor.focus();\n });\n },\n [backgroundColorInSchema, editor],\n );\n\n if (state === undefined) {\n return null;\n }\n\n return (\n <Components.Generic.Menu.Root>\n <Components.Generic.Menu.Trigger>\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n data-test=\"colors\"\n label={dict.formatting_toolbar.colors.tooltip}\n mainTooltip={dict.formatting_toolbar.colors.tooltip}\n icon={\n <ColorIcon\n textColor={state.textColor}\n backgroundColor={state.backgroundColor}\n size={20}\n />\n }\n />\n </Components.Generic.Menu.Trigger>\n <Components.Generic.Menu.Dropdown\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\n >\n <ColorPicker\n text={\n state.textColor\n ? {\n color: state.textColor,\n setColor: setTextColor,\n }\n : undefined\n }\n background={\n state.backgroundColor\n ? {\n color: state.backgroundColor,\n setColor: setBackgroundColor,\n }\n : undefined\n }\n />\n </Components.Generic.Menu.Dropdown>\n </Components.Generic.Menu.Root>\n );\n};\n","import {\n DEFAULT_LINK_PROTOCOL,\n LinkToolbarExtension,\n VALID_LINK_PROTOCOLS,\n} from \"@blocknote/core/extensions\";\nimport {\n ChangeEvent,\n KeyboardEvent,\n useCallback,\n useEffect,\n useState,\n} from \"react\";\nimport { RiLink, RiText } from \"react-icons/ri\";\nimport { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useExtension } from \"../../hooks/useExtension.js\";\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { LinkToolbarProps } from \"./LinkToolbarProps.js\";\n\nconst validateUrl = (url: string) => {\n for (const protocol of VALID_LINK_PROTOCOLS) {\n if (url.startsWith(protocol)) {\n return url;\n }\n }\n\n return `${DEFAULT_LINK_PROTOCOL}://${url}`;\n};\n\nexport const EditLinkMenuItems = (\n props: Pick<\n LinkToolbarProps,\n \"url\" | \"text\" | \"range\" | \"setToolbarOpen\" | \"setToolbarPositionFrozen\"\n > & {\n showTextField?: boolean;\n },\n) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const { editLink } = useExtension(LinkToolbarExtension);\n\n const { url, text, showTextField } = props;\n\n const [currentUrl, setCurrentUrl] = useState<string>(url);\n const [currentText, setCurrentText] = useState<string>(text);\n\n useEffect(() => {\n setCurrentUrl(url);\n setCurrentText(text);\n }, [text, url]);\n\n const handleEnter = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing) {\n event.preventDefault();\n editLink(validateUrl(currentUrl), currentText, props.range.from);\n props.setToolbarOpen?.(false);\n props.setToolbarPositionFrozen?.(false);\n }\n },\n [editLink, currentUrl, currentText, props],\n );\n\n const handleUrlChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) =>\n setCurrentUrl(event.currentTarget.value),\n [],\n );\n\n const handleTextChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) =>\n setCurrentText(event.currentTarget.value),\n [],\n );\n\n const handleSubmit = useCallback(() => {\n editLink(validateUrl(currentUrl), currentText, props.range.from);\n props.setToolbarOpen?.(false);\n props.setToolbarPositionFrozen?.(false);\n }, [editLink, currentUrl, currentText, props]);\n\n return (\n <Components.Generic.Form.Root>\n {/* // TODO: add labels? */}\n <Components.Generic.Form.TextInput\n className={\"bn-text-input\"}\n name=\"url\"\n icon={<RiLink />}\n autoFocus={true}\n placeholder={dict.link_toolbar.form.url_placeholder}\n value={currentUrl}\n onKeyDown={handleEnter}\n onChange={handleUrlChange}\n onSubmit={handleSubmit}\n />\n {showTextField !== false && (\n <Components.Generic.Form.TextInput\n className={\"bn-text-input\"}\n name=\"title\"\n icon={<RiText />}\n placeholder={dict.link_toolbar.form.title_placeholder}\n value={currentText}\n onKeyDown={handleEnter}\n onChange={handleTextChange}\n onSubmit={handleSubmit}\n />\n )}\n </Components.Generic.Form.Root>\n );\n};\n","import { useEffect, useState } from \"react\";\nimport { RiLink } from \"react-icons/ri\";\n\nimport {\n BlockNoteEditor,\n BlockSchema,\n formatKeyboardShortcut,\n isTableCellSelection,\n StyleSchema,\n} from \"@blocknote/core\";\nimport {\n FormattingToolbarExtension,\n ShowSelectionExtension,\n} from \"@blocknote/core/extensions\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorDOMElement } from \"../../../hooks/useEditorDomElement.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useExtension } from \"../../../hooks/useExtension.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\nimport { EditLinkMenuItems } from \"../../LinkToolbar/EditLinkMenuItems.js\";\n\nfunction checkLinkInSchema(\n editor: BlockNoteEditor<BlockSchema, any, StyleSchema>,\n): editor is BlockNoteEditor<\n BlockSchema,\n {\n link: {\n type: \"link\";\n propSchema: any;\n content: \"styled\";\n };\n },\n StyleSchema\n> {\n return (\n \"link\" in editor.schema.inlineContentSchema &&\n editor.schema.inlineContentSchema[\"link\"] === \"link\"\n );\n}\n\nexport const CreateLinkButton = () => {\n const editor = useBlockNoteEditor<any, any, any>();\n const editorDOMElement = useEditorDOMElement();\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const formattingToolbar = useExtension(FormattingToolbarExtension);\n const { showSelection } = useExtension(ShowSelectionExtension);\n\n const [showPopover, setShowPopover] = useState(false);\n useEffect(() => {\n showSelection(showPopover, \"createLinkButton\");\n return () => showSelection(false, \"createLinkButton\");\n }, [showPopover, showSelection]);\n\n const state = useEditorState({\n editor,\n selector: ({ editor }) => {\n // Do not show if:\n if (\n // The editor is read-only.\n !editor.isEditable ||\n // Links are not in the schema.\n !checkLinkInSchema(editor) ||\n // Table cells are selected.\n isTableCellSelection(editor.prosemirrorState.selection) ||\n // None of the selected blocks have inline content\n !(\n editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ]\n ).find((block) => block.content !== undefined)\n ) {\n return undefined;\n }\n\n return {\n url: editor.getSelectedLinkUrl(),\n text: editor.getSelectedText(),\n range: {\n from: editor.prosemirrorState.selection.from,\n to: editor.prosemirrorState.selection.to,\n },\n };\n },\n });\n useEffect(() => {\n setShowPopover(false);\n }, [state]);\n\n // Makes Ctrl+K/Meta+K open link creation popover.\n useEffect(() => {\n const callback = (event: KeyboardEvent) => {\n if ((event.ctrlKey || event.metaKey) && event.key === \"k\") {\n setShowPopover(true);\n event.preventDefault();\n }\n };\n\n editorDOMElement?.addEventListener(\"keydown\", callback);\n\n return () => {\n editorDOMElement?.removeEventListener(\"keydown\", callback);\n };\n }, [editorDOMElement]);\n\n if (state === undefined) {\n return null;\n }\n\n return (\n <Components.Generic.Popover.Root\n open={showPopover}\n onOpenChange={setShowPopover}\n >\n <Components.Generic.Popover.Trigger>\n {/* TODO: hide tooltip on click */}\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n data-test=\"createLink\"\n label={dict.formatting_toolbar.link.tooltip}\n mainTooltip={dict.formatting_toolbar.link.tooltip}\n secondaryTooltip={formatKeyboardShortcut(\n dict.formatting_toolbar.link.secondary_tooltip,\n dict.generic.ctrl_shortcut,\n )}\n icon={<RiLink />}\n onClick={() => setShowPopover((open) => !open)}\n />\n </Components.Generic.Popover.Trigger>\n <Components.Generic.Popover.Content\n className={\"bn-popover-content bn-form-popover\"}\n variant={\"form-popover\"}\n >\n <EditLinkMenuItems\n url={state.url || \"\"}\n text={state.text}\n range={state.range}\n showTextField={false}\n setToolbarOpen={(open) => formattingToolbar.store.setState(open)}\n />\n </Components.Generic.Popover.Content>\n </Components.Generic.Popover.Root>\n );\n};\n","import {\n blockHasType,\n BlockSchema,\n editorHasBlockWithType,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport {\n ChangeEvent,\n KeyboardEvent,\n useCallback,\n useState,\n} from \"react\";\nimport { RiInputField } from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\n\nexport const FileCaptionButton = () => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const block = useEditorState({\n editor,\n selector: ({ editor }) => {\n if (!editor.isEditable) {\n return undefined;\n }\n\n const selectedBlocks = editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ];\n\n if (selectedBlocks.length !== 1) {\n return undefined;\n }\n\n const block = selectedBlocks[0];\n\n if (\n !blockHasType(block, editor, block.type, {\n url: \"string\",\n caption: \"string\",\n })\n ) {\n return undefined;\n }\n\n return block;\n },\n });\n\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (\n block !== undefined &&\n editorHasBlockWithType(editor, block.type, {\n caption: \"string\",\n })\n ) {\n editor.updateBlock(block.id, {\n props: {\n caption: event.currentTarget.value,\n },\n });\n }\n },\n [block, editor],\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing) {\n event.preventDefault();\n setPopoverOpen(false);\n }\n }, []);\n\n if (block === undefined) {\n return null;\n }\n\n return (\n <Components.Generic.Popover.Root\n open={popoverOpen}\n onOpenChange={setPopoverOpen}\n >\n <Components.Generic.Popover.Trigger>\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n label={dict.formatting_toolbar.file_caption.tooltip}\n mainTooltip={dict.formatting_toolbar.file_caption.tooltip}\n icon={<RiInputField />}\n onClick={() => setPopoverOpen((open) => !open)}\n />\n </Components.Generic.Popover.Trigger>\n <Components.Generic.Popover.Content\n className={\"bn-popover-content bn-form-popover\"}\n variant={\"form-popover\"}\n >\n <Components.Generic.Form.Root>\n <Components.Generic.Form.TextInput\n name={\"file-caption\"}\n icon={<RiInputField />}\n value={block.props.caption}\n autoFocus={true}\n placeholder={dict.formatting_toolbar.file_caption.input_placeholder}\n onKeyDown={handleKeyDown}\n onChange={handleChange}\n />\n </Components.Generic.Form.Root>\n </Components.Generic.Popover.Content>\n </Components.Generic.Popover.Root>\n );\n};\n","import {\n blockHasType,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { useCallback } from \"react\";\nimport { RiDeleteBin7Line } from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\n\nexport const FileDeleteButton = () => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const block = useEditorState({\n editor,\n selector: ({ editor }) => {\n if (!editor.isEditable) {\n return undefined;\n }\n\n const selectedBlocks = editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ];\n\n if (selectedBlocks.length !== 1) {\n return undefined;\n }\n\n const block = selectedBlocks[0];\n\n if (\n !blockHasType(block, editor, block.type, {\n url: \"string\",\n })\n ) {\n return undefined;\n }\n\n return block;\n },\n });\n\n const onClick = useCallback(() => {\n if (block !== undefined) {\n editor.focus();\n editor.removeBlocks([block.id]);\n }\n }, [block, editor]);\n\n if (block === undefined) {\n return null;\n }\n\n return (\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n label={\n dict.formatting_toolbar.file_delete.tooltip[block.type] ||\n dict.formatting_toolbar.file_delete.tooltip[\"file\"]\n }\n mainTooltip={\n dict.formatting_toolbar.file_delete.tooltip[block.type] ||\n dict.formatting_toolbar.file_delete.tooltip[\"file\"]\n }\n icon={<RiDeleteBin7Line />}\n onClick={onClick}\n />\n );\n};\n","import {\n blockHasType,\n BlockSchema,\n editorHasBlockWithType,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport {\n ChangeEvent,\n KeyboardEvent,\n useCallback,\n useState,\n} from \"react\";\nimport { RiFontFamily } from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\n\nexport const FileRenameButton = () => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const block = useEditorState({\n editor,\n selector: ({ editor }) => {\n if (!editor.isEditable) {\n return undefined;\n }\n\n const selectedBlocks = editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ];\n\n if (selectedBlocks.length !== 1) {\n return undefined;\n }\n\n const block = selectedBlocks[0];\n\n if (\n !blockHasType(block, editor, block.type, {\n url: \"string\",\n name: \"string\",\n })\n ) {\n return undefined;\n }\n\n return block;\n },\n });\n\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (\n block !== undefined &&\n editorHasBlockWithType(editor, block.type, {\n name: \"string\",\n })\n ) {\n editor.updateBlock(block.id, {\n props: {\n name: event.currentTarget.value,\n },\n });\n }\n },\n [block, editor],\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing) {\n event.preventDefault();\n setPopoverOpen(false);\n }\n }, []);\n\n if (block === undefined) {\n return null;\n }\n\n return (\n <Components.Generic.Popover.Root\n open={popoverOpen}\n onOpenChange={setPopoverOpen}\n >\n <Components.Generic.Popover.Trigger>\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n label={\n dict.formatting_toolbar.file_rename.tooltip[block.type] ||\n dict.formatting_toolbar.file_rename.tooltip[\"file\"]\n }\n mainTooltip={\n dict.formatting_toolbar.file_rename.tooltip[block.type] ||\n dict.formatting_toolbar.file_rename.tooltip[\"file\"]\n }\n icon={<RiFontFamily />}\n onClick={() => setPopoverOpen((open) => !open)}\n />\n </Components.Generic.Popover.Trigger>\n <Components.Generic.Popover.Content\n className={\"bn-popover-content bn-form-popover\"}\n variant={\"form-popover\"}\n >\n <Components.Generic.Form.Root>\n <Components.Generic.Form.TextInput\n name={\"file-name\"}\n icon={<RiFontFamily />}\n value={block.props.name}\n autoFocus={true}\n placeholder={\n dict.formatting_toolbar.file_rename.input_placeholder[\n block.type\n ] || dict.formatting_toolbar.file_rename.input_placeholder[\"file\"]\n }\n onKeyDown={handleKeyDown}\n onChange={handleChange}\n />\n </Components.Generic.Form.Root>\n </Components.Generic.Popover.Content>\n </Components.Generic.Popover.Root>\n );\n};\n","import {\n blockHasType,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { RiImageEditFill } from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\nimport { FilePanel } from \"../../FilePanel/FilePanel.js\";\n\nexport const FileReplaceButton = () => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const block = useEditorState({\n editor,\n selector: ({ editor }) => {\n if (!editor.isEditable) {\n return undefined;\n }\n\n const selectedBlocks = editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ];\n\n if (selectedBlocks.length !== 1) {\n return undefined;\n }\n\n const block = selectedBlocks[0];\n\n if (\n !blockHasType(block, editor, block.type, {\n url: \"string\",\n })\n ) {\n return undefined;\n }\n\n return block;\n },\n });\n\n if (block === undefined) {\n return null;\n }\n\n return (\n <Components.Generic.Popover.Root position={\"bottom\"}>\n <Components.Generic.Popover.Trigger>\n <Components.FormattingToolbar.Button\n className={\"bn-button\"}\n mainTooltip={\n dict.formatting_toolbar.file_replace.tooltip[block.type] ||\n dict.formatting_toolbar.file_replace.tooltip[\"file\"]\n }\n label={\n dict.formatting_toolbar.file_replace.tooltip[block.type] ||\n dict.formatting_toolbar.file_replace.tooltip[\"file\"]\n }\n icon={<RiImageEditFill />}\n />\n </Components.Generic.Popover.Trigger>\n <Components.Generic.Popover.Content\n className={\"bn-popover-content bn-panel-popover\"}\n variant={\"panel-popover\"}\n >\n <FilePanel blockId={block.id} />\n </Components.Generic.Popover.Content>\n </Components.Generic.Popover.Root>\n );\n};\n","import {\n BlockSchema,\n formatKeyboardShortcut,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { useCallback } from \"react\";\nimport { RiIndentDecrease, RiIndentIncrease } from \"react-icons/ri\";\n\nimport { useComponentsContext } from \"../../../editor/ComponentsContext.js\";\nimport { useBlockNoteEditor } from \"../../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../../hooks/useEditorState.js\";\nimport { useDictionary } from \"../../../i18n/dictionary.js\";\n\nexport const NestBlockButton = () => {\n const dict = useDictionary();\n const Components = useComponentsContext()!;\n\n const editor = useBlockNoteEditor<\n BlockSchema,\n InlineContentSchema,\n StyleSchema\n >();\n\n const state = useEditorState({\n editor,\n selector: ({ editor }) => {\n // Do not show if:\n if (\n // The editor is read-only.\n !editor.isEditable ||\n // None of the selected blocks have inline content\n !(\n editor.getSel