@blocknote/react
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 8.7 kB
Source Map (JSON)
{"version":3,"file":"FloatingComposerController-B4zPPPwD.cjs","names":[],"sources":["../src/components/Comments/FloatingComposer.tsx","../src/components/Comments/FloatingComposerController.tsx"],"sourcesContent":["import {\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n Dictionary,\n InlineContentSchema,\n mergeCSSClasses,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { CommentsExtension } from \"@blocknote/core/comments\";\nimport { memo, useCallback } from \"react\";\n\nimport { Components, useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useCreateBlockNote } from \"../../hooks/useCreateBlockNote.js\";\nimport { useExtension } from \"../../hooks/useExtension.js\";\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { CommentEditor } from \"./CommentEditor.js\";\nimport { defaultCommentEditorSchema } from \"./defaultCommentEditorSchema.js\";\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { TextSelection } from \"@tiptap/pm/state\";\n\ntype FloatingComposerActionsProps = {\n isFocused: boolean;\n isEmpty: boolean;\n onSave: () => Promise<void>;\n Components: Components;\n dict: Dictionary;\n};\n\nconst FloatingComposerActionsComponent = memo(\n ({ isEmpty, onSave, Components, dict }: FloatingComposerActionsProps) => (\n <Components.Generic.Toolbar.Root\n className={mergeCSSClasses(\"bn-action-toolbar\", \"bn-comment-actions\")}\n variant=\"action-toolbar\"\n >\n <Components.Generic.Toolbar.Button\n className={\"bn-button\"}\n mainTooltip={dict.comments.save_button_text}\n variant=\"compact\"\n isDisabled={isEmpty}\n onClick={onSave}\n >\n {dict.comments.save_button_text}\n </Components.Generic.Toolbar.Button>\n </Components.Generic.Toolbar.Root>\n ),\n);\n\n/**\n * The FloatingComposer component displays a comment editor \"floating\" card.\n *\n * It's used when the user highlights a parts of the document to create a new comment / thread.\n */\nexport function FloatingComposer<\n B extends BlockSchema = DefaultBlockSchema,\n I extends InlineContentSchema = DefaultInlineContentSchema,\n S extends StyleSchema = DefaultStyleSchema,\n>() {\n const editor = useBlockNoteEditor<B, I, S>();\n\n const comments = useExtension(CommentsExtension);\n\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const newCommentEditor = useCreateBlockNote({\n trailingBlock: false,\n dictionary: {\n ...dict,\n placeholders: {\n emptyDocument: dict.placeholders.new_comment,\n },\n },\n schema: comments.commentEditorSchema || defaultCommentEditorSchema,\n });\n\n const onSave = useCallback(async () => {\n // (later) For REST API, we should implement a loading state and error state\n await comments.createThread({\n initialComment: {\n body: newCommentEditor.document,\n },\n });\n comments.stopPendingComment();\n editor.transact((tr) => {\n tr.setSelection(TextSelection.create(tr.doc, tr.selection.to));\n });\n editor.focus();\n }, [comments, newCommentEditor, editor]);\n\n return (\n <Components.Comments.Card className={\"bn-thread\"}>\n <CommentEditor\n autoFocus={true}\n editable={true}\n editor={newCommentEditor}\n actions={({ isFocused, isEmpty }) => (\n <FloatingComposerActionsComponent\n isFocused={isFocused}\n isEmpty={isEmpty}\n onSave={onSave}\n Components={Components}\n dict={dict}\n />\n )}\n />\n </Components.Comments.Card>\n );\n}\n","import {\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { CommentsExtension } from \"@blocknote/core/comments\";\nimport { flip, offset, shift } from \"@floating-ui/react\";\nimport { ComponentProps, FC, useMemo } from \"react\";\n\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { useEditorState } from \"../../hooks/useEditorState.js\";\nimport { useExtension, useExtensionState } from \"../../hooks/useExtension.js\";\nimport { FloatingUIOptions } from \"../Popovers/FloatingUIOptions.js\";\nimport { PositionPopover } from \"../Popovers/PositionPopover.js\";\nimport { FloatingComposer } from \"./FloatingComposer.js\";\n\nexport default function FloatingComposerController<\n B extends BlockSchema = DefaultBlockSchema,\n I extends InlineContentSchema = DefaultInlineContentSchema,\n S extends StyleSchema = DefaultStyleSchema,\n>(props: {\n floatingComposer?: FC<ComponentProps<typeof FloatingComposer>>;\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<B, I, S>();\n\n const comments = useExtension(CommentsExtension);\n\n const pendingComment = useExtensionState(CommentsExtension, {\n editor,\n selector: (state) => state.pendingComment,\n });\n\n const position = useEditorState({\n editor,\n selector: ({ editor }) =>\n pendingComment\n ? {\n from: editor.prosemirrorState.selection.from,\n to: editor.prosemirrorState.selection.to,\n }\n : undefined,\n });\n\n const floatingUIOptions = useMemo<FloatingUIOptions>(\n () => ({\n ...props.floatingUIOptions,\n useFloatingOptions: {\n open: !!pendingComment,\n // Needed as hooks like `useDismiss` call `onOpenChange` to change the\n // open state.\n onOpenChange: (open) => {\n if (!open) {\n comments.stopPendingComment();\n editor.focus();\n }\n },\n placement: \"bottom\",\n middleware: [offset(10), shift(), flip()],\n ...props.floatingUIOptions?.useFloatingOptions,\n },\n focusManagerProps: {\n disabled: false,\n },\n elementProps: {\n style: {\n zIndex: 60,\n },\n ...props.floatingUIOptions?.elementProps,\n },\n }),\n [comments, editor, pendingComment, props.floatingUIOptions],\n );\n\n // nice to have improvements would be:\n // - transition transform property so composer box animates when remote document is changed\n // - fade out on close\n\n const Component = props.floatingComposer || FloatingComposer;\n\n return (\n <PositionPopover\n position={position}\n portalElement={props.portalElement}\n {...floatingUIOptions}\n >\n <Component />\n </PositionPopover>\n );\n}\n"],"mappings":"oPA8BA,IAAM,GAAA,EAAA,EAAA,OACH,CAAE,UAAS,SAAQ,aAAY,WAC9B,EAAA,EAAA,KAAC,EAAW,QAAQ,QAAQ,KAA5B,CACE,WAAA,EAAA,EAAA,iBAA2B,oBAAqB,qBAAqB,CACrE,QAAQ,2BAER,EAAA,EAAA,KAAC,EAAW,QAAQ,QAAQ,OAA5B,CACE,UAAW,YACX,YAAa,EAAK,SAAS,iBAC3B,QAAQ,UACR,WAAY,EACZ,QAAS,WAER,EAAK,SAAS,iBACmB,CAAA,CACJ,CAAA,CAErC,CAOD,SAAgB,GAIZ,CACF,IAAM,EAAS,EAAA,GAA6B,CAEtC,EAAW,EAAA,EAAa,EAAA,kBAAkB,CAE1C,EAAa,EAAA,GAAsB,CACnC,EAAO,EAAA,GAAe,CAEtB,EAAmB,EAAA,EAAmB,CAC1C,cAAe,GACf,WAAY,CACV,GAAG,EACH,aAAc,CACZ,cAAe,EAAK,aAAa,YAClC,CACF,CACD,OAAQ,EAAS,qBAAuB,EAAA,EACzC,CAAC,CAEI,GAAA,EAAA,EAAA,aAAqB,SAAY,CAErC,MAAM,EAAS,aAAa,CAC1B,eAAgB,CACd,KAAM,EAAiB,SACxB,CACF,CAAC,CACF,EAAS,oBAAoB,CAC7B,EAAO,SAAU,GAAO,CACtB,EAAG,aAAa,EAAA,cAAc,OAAO,EAAG,IAAK,EAAG,UAAU,GAAG,CAAC,EAC9D,CACF,EAAO,OAAO,EACb,CAAC,EAAU,EAAkB,EAAO,CAAC,CAExC,OACE,EAAA,EAAA,KAAC,EAAW,SAAS,KAArB,CAA0B,UAAW,sBACnC,EAAA,EAAA,KAAC,EAAA,EAAD,CACE,UAAW,GACX,SAAU,GACV,OAAQ,EACR,SAAU,CAAE,YAAW,cACrB,EAAA,EAAA,KAAC,EAAD,CACa,YACF,UACD,SACI,aACN,OACN,CAAA,CAEJ,CAAA,CACuB,CAAA,4BCxF/B,SAAwB,EAItB,EASC,CACD,IAAM,EAAS,EAAA,GAA6B,CAEtC,EAAW,EAAA,EAAa,EAAA,kBAAkB,CAE1C,EAAiB,EAAA,EAAkB,EAAA,kBAAmB,CAC1D,SACA,SAAW,GAAU,EAAM,eAC5B,CAAC,CAEI,EAAW,EAAA,EAAe,CAC9B,SACA,UAAW,CAAE,YACX,EACI,CACE,KAAM,EAAO,iBAAiB,UAAU,KACxC,GAAI,EAAO,iBAAiB,UAAU,GACvC,CACD,IAAA,GACP,CAAC,CAEI,GAAA,EAAA,EAAA,cACG,CACL,GAAG,EAAM,kBACT,mBAAoB,CAClB,KAAM,CAAC,CAAC,EAGR,aAAe,GAAS,CACjB,IACH,EAAS,oBAAoB,CAC7B,EAAO,OAAO,GAGlB,UAAW,SACX,WAAY,cAAQ,GAAG,cAAS,aAAQ,CAAC,CACzC,GAAG,EAAM,mBAAmB,mBAC7B,CACD,kBAAmB,CACjB,SAAU,GACX,CACD,aAAc,CACZ,MAAO,CACL,OAAQ,GACT,CACD,GAAG,EAAM,mBAAmB,aAC7B,CACF,EACD,CAAC,EAAU,EAAQ,EAAgB,EAAM,kBAAkB,CAC5D,CAMK,EAAY,EAAM,kBAAoB,EAE5C,OACE,EAAA,EAAA,KAAC,EAAA,EAAD,CACY,WACV,cAAe,EAAM,cACrB,GAAI,YAEJ,EAAA,EAAA,KAAC,EAAD,EAAa,CAAA,CACG,CAAA"}