@blocknote/react
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 43 kB
Source Map (JSON)
{"version":3,"file":"FloatingThreadController-BDiAihkl.cjs","names":[],"sources":["../src/components/Comments/EmojiMartPicker.tsx","../src/components/Comments/EmojiPicker.tsx","../src/components/Comments/useUsers.ts","../src/components/Comments/ReactionBadge.tsx","../src/components/Comments/Comment.tsx","../src/components/Comments/Comments.tsx","../src/components/Comments/Thread.tsx","../src/components/Comments/useThreads.ts","../src/components/Comments/FloatingThreadController.tsx"],"sourcesContent":["// From https://github.com/missive/emoji-mart/blob/main/packages/emoji-mart-react/react.tsx\nimport type { EmojiMartData } from \"@emoji-mart/data\";\nimport React, { useEffect, useRef } from \"react\";\n\n// Temporary fix for https://github.com/missive/emoji-mart/pull/929\nlet emojiLoadingPromise:\n | Promise<{\n emojiMart: typeof import(\"emoji-mart\");\n emojiData: EmojiMartData;\n }>\n | undefined;\n\nasync function loadEmojiMart() {\n if (emojiLoadingPromise) {\n return emojiLoadingPromise;\n }\n\n emojiLoadingPromise = (async () => {\n // load dynamically because emoji-mart doesn't specify type: module and breaks in nodejs\n const [emojiMartModule, emojiDataModule] = await Promise.all([\n import(\"emoji-mart\"),\n // use a dynamic import to encourage bundle-splitting\n // and a smaller initial client bundle size\n import(\"@emoji-mart/data\"),\n ]);\n\n const emojiMart =\n \"default\" in emojiMartModule ? emojiMartModule.default : emojiMartModule;\n const emojiData =\n \"default\" in emojiDataModule\n ? (emojiDataModule.default as EmojiMartData)\n : (emojiDataModule as EmojiMartData);\n\n await emojiMart.init({ data: emojiData });\n\n return { emojiMart, emojiData };\n })();\n\n return emojiLoadingPromise;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport default function EmojiPicker(props: any) {\n const ref = useRef(null);\n const instance = useRef(null) as any;\n\n if (instance.current) {\n instance.current.update(props);\n }\n\n useEffect(() => {\n (async () => {\n const { emojiMart } = await loadEmojiMart();\n\n instance.current = new emojiMart.Picker({ ...props, ref });\n })();\n\n return () => {\n instance.current = null;\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return React.createElement(\"div\", { ref });\n}\n","import { ReactNode, useState } from \"react\";\n\nimport { useBlockNoteContext } from \"../../editor/BlockNoteContext.js\";\nimport { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport Picker from \"./EmojiMartPicker.js\";\n\nexport const EmojiPicker = (props: {\n onEmojiSelect: (emoji: { native: string }) => void;\n onOpenChange?: (open: boolean) => void;\n children: ReactNode;\n}) => {\n const [open, setOpen] = useState(false);\n\n const Components = useComponentsContext()!;\n const blockNoteContext = useBlockNoteContext()!;\n const portalRoot = blockNoteContext.editor?.portalElement;\n\n if (!portalRoot) {\n throw new Error(\"Portal root not found\");\n }\n\n return (\n <Components.Generic.Popover.Root open={open} portalRoot={portalRoot}>\n <Components.Generic.Popover.Trigger>\n <div\n onClick={(event) => {\n // Needed as the Picker component's onClickOutside handler\n // fires immediately after otherwise, preventing the popover\n // from opening.\n event.preventDefault();\n event.stopPropagation();\n setOpen(!open);\n props.onOpenChange?.(!open);\n }}\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n }}\n >\n {props.children}\n </div>\n </Components.Generic.Popover.Trigger>\n <Components.Generic.Popover.Content\n className={\"bn-emoji-picker-popover\"}\n variant={\"panel-popover\"}\n >\n <Picker\n perLine={7}\n onClickOutside={() => {\n setOpen(false);\n props.onOpenChange?.(false);\n }}\n onEmojiSelect={(emoji: { native: string }) => {\n props.onEmojiSelect(emoji);\n setOpen(false);\n props.onOpenChange?.(false);\n }}\n theme={blockNoteContext?.colorSchemePreference}\n />\n </Components.Generic.Popover.Content>\n </Components.Generic.Popover.Root>\n );\n};\n","import { CommentsExtension } from \"@blocknote/core/comments\";\nimport { User } from \"@blocknote/core/comments\";\nimport { useCallback, useMemo, useSyncExternalStore } from \"react\";\n\nimport { useExtension } from \"../../hooks/useExtension.js\";\n\nexport function useUser(userId: string) {\n return useUsers([userId]).get(userId);\n}\n\n/**\n * Bridges the UserStore to React using useSyncExternalStore.\n */\nexport function useUsers(userIds: string[]) {\n const comments = useExtension(CommentsExtension);\n\n const store = comments.userStore;\n\n const getUpdatedSnapshot = useCallback(() => {\n const map = new Map<string, User>();\n for (const id of userIds) {\n const user = store.getUser(id);\n if (user) {\n map.set(id, user);\n }\n }\n return map;\n }, [store, userIds]);\n\n // this ref / memoworks around this error:\n // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached\n // however, might not be a good practice to work around it this way\n\n // We need to use a memo instead of a ref to make sure the snapshot is updated when the userIds change\n const ref = useMemo(() => {\n return {\n current: getUpdatedSnapshot(),\n };\n }, [getUpdatedSnapshot]);\n\n // note: this is inefficient as it will trigger a re-render even if other users (not in userIds) are updated\n const subscribe = useCallback(\n (cb: () => void) => {\n const ret = store.subscribe((_users) => {\n // update ref when changed\n ref.current = getUpdatedSnapshot();\n\n // calling cb() will make sure `useSyncExternalStore` will fetch the latest snapshot (which is ref.current)\n cb();\n });\n store.loadUsers(userIds);\n return ret;\n },\n [store, getUpdatedSnapshot, userIds, ref],\n );\n\n return useSyncExternalStore(subscribe, () => ref.current!);\n}\n","import { mergeCSSClasses } from \"@blocknote/core\";\nimport { CommentsExtension } from \"@blocknote/core/comments\";\nimport { CommentData } from \"@blocknote/core/comments\";\nimport { useState } from \"react\";\n\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useUsers } from \"./useUsers.js\";\nimport { useExtension } from \"../../hooks/useExtension.js\";\n\nexport const ReactionBadge = (props: {\n comment: CommentData;\n emoji: string;\n onReactionSelect: (emoji: string) => void;\n}) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const comments = useExtension(CommentsExtension);\n\n const reaction = props.comment.reactions.find(\n (reaction) => reaction.emoji === props.emoji,\n );\n if (!reaction) {\n throw new Error(\n \"Trying to render reaction badge for non-existing reaction\",\n );\n }\n\n const [userIds, setUserIds] = useState<string[]>([]);\n const users = useUsers(userIds);\n\n return (\n <Components.Generic.Badge.Root\n key={reaction.emoji}\n className={mergeCSSClasses(\"bn-badge\", \"bn-comment-reaction\")}\n text={reaction.userIds.length.toString()}\n icon={reaction.emoji}\n isSelected={comments.threadStore.auth.canDeleteReaction(\n props.comment,\n reaction.emoji,\n )}\n onClick={() => props.onReactionSelect(reaction.emoji)}\n onMouseEnter={() => setUserIds(reaction.userIds)}\n mainTooltip={dict.comments.reactions.reacted_by}\n secondaryTooltip={`${Array.from(users.values())\n .map((user) => user.username)\n .join(\"\\n\")}`}\n />\n );\n};\n","\"use client\";\n\nimport { Dictionary, mergeCSSClasses } from \"@blocknote/core\";\nimport { CommentsExtension } from \"@blocknote/core/comments\";\nimport type { CommentData, ThreadData } from \"@blocknote/core/comments\";\nimport { ThreadStore } from \"@blocknote/core/comments\";\nimport { MouseEvent, ReactNode, memo, useCallback, useState } from \"react\";\nimport {\n RiArrowGoBackFill,\n RiCheckFill,\n RiDeleteBinFill,\n RiEditFill,\n RiEmotionLine,\n RiMoreFill,\n} from \"react-icons/ri\";\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 { EmojiPicker } from \"./EmojiPicker.js\";\nimport { ReactionBadge } from \"./ReactionBadge.js\";\nimport { defaultCommentEditorSchema } from \"./defaultCommentEditorSchema.js\";\nimport { useUser } from \"./useUsers.js\";\n\ntype CommentEditorActionsProps = {\n isFocused: boolean;\n isEmpty: boolean;\n comment: CommentData;\n isEditing: boolean;\n threadStore: ThreadStore;\n onReactionSelect: (emoji: string) => Promise<void>;\n onEditSubmit: (event: MouseEvent) => Promise<void>;\n onEditCancel: () => void;\n onEmojiPickerOpenChange: (open: boolean) => void;\n Components: Components;\n dict: Dictionary;\n};\n\nconst CommentEditorActionsComponent = memo(\n ({\n isEmpty: _isEmpty,\n comment,\n isEditing,\n threadStore,\n onReactionSelect,\n onEditSubmit,\n onEditCancel,\n onEmojiPickerOpenChange,\n Components,\n dict,\n }: CommentEditorActionsProps) => {\n const canAddReaction = threadStore.auth.canAddReaction(comment);\n\n return (\n <>\n {comment.reactions.length > 0 && !isEditing && (\n <Components.Generic.Badge.Group\n className={mergeCSSClasses(\n \"bn-badge-group\",\n \"bn-comment-reactions\",\n )}\n >\n {comment.reactions.map((reaction) => (\n <ReactionBadge\n key={reaction.emoji}\n comment={comment}\n emoji={reaction.emoji}\n onReactionSelect={onReactionSelect}\n />\n ))}\n {canAddReaction && (\n <EmojiPicker\n onEmojiSelect={(emoji: { native: string }) =>\n onReactionSelect(emoji.native)\n }\n onOpenChange={onEmojiPickerOpenChange}\n >\n <Components.Generic.Badge.Root\n className={mergeCSSClasses(\n \"bn-badge\",\n \"bn-comment-add-reaction\",\n )}\n text={\"+\"}\n icon={<RiEmotionLine size={16} />}\n mainTooltip={dict.comments.actions.add_reaction}\n />\n </EmojiPicker>\n )}\n </Components.Generic.Badge.Group>\n )}\n {isEditing && (\n <Components.Generic.Toolbar.Root\n variant=\"action-toolbar\"\n className={mergeCSSClasses(\n \"bn-action-toolbar\",\n \"bn-comment-actions\",\n )}\n >\n <Components.Generic.Toolbar.Button\n mainTooltip={dict.comments.save_button_text}\n variant=\"compact\"\n onClick={onEditSubmit}\n isDisabled={_isEmpty}\n >\n {dict.comments.save_button_text}\n </Components.Generic.Toolbar.Button>\n <Components.Generic.Toolbar.Button\n className={\"bn-button\"}\n mainTooltip={dict.comments.cancel_button_text}\n variant=\"compact\"\n onClick={onEditCancel}\n >\n {dict.comments.cancel_button_text}\n </Components.Generic.Toolbar.Button>\n </Components.Generic.Toolbar.Root>\n )}\n </>\n );\n },\n);\n\nexport type CommentProps = {\n comment: CommentData;\n thread: ThreadData;\n showResolveButton?: boolean;\n};\n\n/**\n * The Comment component displays a single comment with actions,\n * a reaction list and an editor when editing.\n *\n * It's generally used in the `Thread` component for comments that have already been created.\n *\n */\nexport const Comment = ({\n comment,\n thread,\n showResolveButton,\n}: CommentProps) => {\n // TODO: if REST API becomes popular, all interactions (click handlers) should implement a loading state and error state\n // (or optimistic local updates)\n const comments = useExtension(CommentsExtension);\n\n const dict = useDictionary();\n\n const commentEditor = useCreateBlockNote({\n initialContent: comment.body,\n trailingBlock: false,\n dictionary: {\n ...dict,\n placeholders: {\n emptyDocument: dict.placeholders.edit_comment,\n },\n },\n schema: comments.commentEditorSchema || defaultCommentEditorSchema,\n });\n\n const Components = useComponentsContext()!;\n\n const [isEditing, setEditing] = useState(false);\n const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);\n\n const threadStore = comments.threadStore;\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const onEditCancel = useCallback(() => {\n commentEditor.replaceBlocks(commentEditor.document, comment.body);\n setEditing(false);\n }, [commentEditor, comment.body]);\n\n const onEditSubmit = useCallback(\n async (_event: MouseEvent) => {\n await threadStore.updateComment({\n commentId: comment.id,\n comment: {\n body: commentEditor.document,\n },\n threadId: thread.id,\n });\n\n setEditing(false);\n },\n [comment, thread.id, commentEditor, threadStore],\n );\n\n const onDelete = useCallback(async () => {\n await threadStore.deleteComment({\n commentId: comment.id,\n threadId: thread.id,\n });\n }, [comment, thread.id, threadStore]);\n\n const onReactionSelect = useCallback(\n async (emoji: string) => {\n if (threadStore.auth.canAddReaction(comment, emoji)) {\n await threadStore.addReaction({\n threadId: thread.id,\n commentId: comment.id,\n emoji,\n });\n } else if (threadStore.auth.canDeleteReaction(comment, emoji)) {\n await threadStore.deleteReaction({\n threadId: thread.id,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [threadStore, comment, thread.id],\n );\n\n const onResolve = useCallback(async () => {\n await threadStore.resolveThread({\n threadId: thread.id,\n });\n }, [thread.id, threadStore]);\n\n const onReopen = useCallback(async () => {\n await threadStore.unresolveThread({\n threadId: thread.id,\n });\n }, [thread.id, threadStore]);\n\n const user = useUser(comment.userId);\n\n if (!comment.body) {\n return null;\n }\n\n let actions: ReactNode | undefined = undefined;\n const canAddReaction = threadStore.auth.canAddReaction(comment);\n const canDeleteComment = threadStore.auth.canDeleteComment(comment);\n const canEditComment = threadStore.auth.canUpdateComment(comment);\n\n const showResolveOrReopen =\n showResolveButton &&\n (thread.resolved\n ? threadStore.auth.canUnresolveThread(thread)\n : threadStore.auth.canResolveThread(thread));\n\n if (!isEditing) {\n actions = (\n <Components.Generic.Toolbar.Root\n className={mergeCSSClasses(\"bn-action-toolbar\", \"bn-comment-actions\")}\n variant={\"action-toolbar\"}\n >\n {canAddReaction && (\n <EmojiPicker\n onEmojiSelect={(emoji: { native: string }) =>\n onReactionSelect(emoji.native)\n }\n onOpenChange={setEmojiPickerOpen}\n >\n <Components.Generic.Toolbar.Button\n key={\"add-reaction\"}\n mainTooltip={dict.comments.actions.add_reaction}\n variant=\"compact\"\n >\n <RiEmotionLine size={16} />\n </Components.Generic.Toolbar.Button>\n </EmojiPicker>\n )}\n {showResolveOrReopen &&\n (thread.resolved ? (\n <Components.Generic.Toolbar.Button\n key={\"reopen\"}\n mainTooltip={dict.comments.actions.reopen}\n variant=\"compact\"\n onClick={onReopen}\n >\n <RiArrowGoBackFill size={16} />\n </Components.Generic.Toolbar.Button>\n ) : (\n <Components.Generic.Toolbar.Button\n key={\"resolve\"}\n mainTooltip={dict.comments.actions.resolve}\n variant=\"compact\"\n onClick={onResolve}\n >\n <RiCheckFill size={16} />\n </Components.Generic.Toolbar.Button>\n ))}\n {(canDeleteComment || canEditComment) && (\n <Components.Generic.Menu.Root position={\"bottom-start\"}>\n <Components.Generic.Menu.Trigger>\n <Components.Generic.Toolbar.Button\n key={\"more-actions\"}\n mainTooltip={dict.comments.actions.more_actions}\n variant=\"compact\"\n >\n <RiMoreFill size={16} />\n </Components.Generic.Toolbar.Button>\n </Components.Generic.Menu.Trigger>\n <Components.Generic.Menu.Dropdown className={\"bn-menu-dropdown\"}>\n {canEditComment && (\n <Components.Generic.Menu.Item\n key={\"edit-comment\"}\n icon={<RiEditFill />}\n onClick={handleEdit}\n >\n {dict.comments.actions.edit_comment}\n </Components.Generic.Menu.Item>\n )}\n {canDeleteComment && (\n <Components.Generic.Menu.Item\n key={\"delete-comment\"}\n icon={<RiDeleteBinFill />}\n onClick={onDelete}\n >\n {dict.comments.actions.delete_comment}\n </Components.Generic.Menu.Item>\n )}\n </Components.Generic.Menu.Dropdown>\n </Components.Generic.Menu.Root>\n )}\n </Components.Generic.Toolbar.Root>\n );\n }\n\n const timeString = comment.createdAt.toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n });\n\n if (!comment.body) {\n throw new Error(\"soft deletes are not yet supported\");\n }\n\n return (\n <Components.Comments.Comment\n authorInfo={user ?? \"loading\"}\n timeString={timeString}\n edited={comment.updatedAt.getTime() !== comment.createdAt.getTime()}\n showActions={\"hover\"}\n actions={actions}\n className={\"bn-thread-comment\"}\n emojiPickerOpen={emojiPickerOpen}\n >\n <CommentEditor\n autoFocus={isEditing}\n editor={commentEditor}\n editable={isEditing}\n actions={\n comment.reactions.length > 0 || isEditing\n ? ({ isFocused, isEmpty }) => (\n <CommentEditorActionsComponent\n isFocused={isFocused}\n isEmpty={isEmpty}\n comment={comment}\n isEditing={isEditing}\n threadStore={threadStore}\n onReactionSelect={onReactionSelect}\n onEditSubmit={onEditSubmit}\n onEditCancel={onEditCancel}\n onEmojiPickerOpenChange={setEmojiPickerOpen}\n Components={Components}\n dict={dict}\n />\n )\n : undefined\n }\n />\n </Components.Comments.Comment>\n );\n};\n","import { ThreadData } from \"@blocknote/core/comments\";\n\nimport { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useDictionary } from \"../../i18n/dictionary.js\";\nimport { Comment } from \"./Comment.js\";\nimport { useUsers } from \"./useUsers.js\";\n\nexport type CommentsProps = {\n thread: ThreadData;\n maxCommentsBeforeCollapse?: number;\n};\n\nexport const Comments = ({\n thread,\n maxCommentsBeforeCollapse,\n}: CommentsProps) => {\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n const users = useUsers(thread.resolvedBy ? [thread.resolvedBy] : []);\n\n // Maps all comments to elements.\n const comments = thread.comments.map((comment, index) => (\n <Comment\n key={comment.id + JSON.stringify(comment.body || \"{}\")}\n thread={thread}\n comment={comment}\n showResolveButton={index === 0}\n />\n ));\n\n // Adds \"resolved by\" comment if needed.\n if (thread.resolved && thread.resolvedUpdatedAt && thread.resolvedBy) {\n const resolvedByUser = users.get(thread.resolvedBy);\n if (!resolvedByUser) {\n throw new Error(\n `User ${thread.resolvedBy} resolved thread ${thread.id}, but their data could not be found.`,\n );\n }\n\n const resolvedCommentIndex =\n thread.comments.findLastIndex(\n (comment) =>\n thread.resolvedUpdatedAt!.getTime() > comment.createdAt.getTime(),\n ) + 1;\n\n comments.splice(\n resolvedCommentIndex,\n 0,\n <Components.Comments.Comment\n key={\"resolved-comment\"}\n className={\"bn-thread-comment\"}\n authorInfo={\n (thread.resolvedBy && users.get(thread.resolvedBy)) || \"loading\"\n }\n timeString={thread.resolvedUpdatedAt.toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n })}\n edited={false}\n showActions={false}\n >\n <div className={\"bn-resolved-text\"}>\n {dict.comments.sidebar.marked_as_resolved}\n </div>\n </Components.Comments.Comment>,\n );\n }\n\n // Collapses replies if needed.\n if (\n maxCommentsBeforeCollapse &&\n comments.length > maxCommentsBeforeCollapse\n ) {\n comments.splice(\n 1,\n comments.length - 2,\n <Components.Comments.ExpandSectionsPrompt\n key={\"expand-prompt\"}\n className={\"bn-thread-expand-prompt\"}\n >\n {dict.comments.sidebar.more_replies(thread.comments.length - 2)}\n </Components.Comments.ExpandSectionsPrompt>,\n );\n }\n\n return comments;\n};\n","import { Dictionary, mergeCSSClasses } from \"@blocknote/core\";\nimport { CommentsExtension } from \"@blocknote/core/comments\";\nimport { ThreadData } from \"@blocknote/core/comments\";\nimport { FocusEvent, 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 { Comments } from \"./Comments.js\";\nimport { defaultCommentEditorSchema } from \"./defaultCommentEditorSchema.js\";\n\ntype ReplyActionsProps = {\n isFocused: boolean;\n isEmpty: boolean;\n onNewCommentSave: () => Promise<void>;\n Components: Components;\n dict: Dictionary;\n};\n\nconst ReplyActionsComponent = memo(\n ({ isEmpty, onNewCommentSave, Components, dict }: ReplyActionsProps) => {\n if (isEmpty) {\n return null;\n }\n\n return (\n <Components.Generic.Toolbar.Root\n variant=\"action-toolbar\"\n className={mergeCSSClasses(\"bn-action-toolbar\", \"bn-comment-actions\")}\n >\n <Components.Generic.Toolbar.Button\n mainTooltip={dict.comments.save_button_text}\n variant=\"compact\"\n isDisabled={isEmpty}\n onClick={onNewCommentSave}\n >\n {dict.comments.save_button_text}\n </Components.Generic.Toolbar.Button>\n </Components.Generic.Toolbar.Root>\n );\n },\n);\n\nexport type ThreadProps = {\n /**\n * The thread to display - you can use the `useThreads` hook to retrieve a\n * `Map` of all threads in the editor, mapped by their IDs.\n */\n thread: ThreadData;\n /**\n * A boolean flag for whether the thread is selected. Selected threads show an\n * editor for replies, and add a `selected` CSS class to the thread.\n */\n selected?: boolean;\n /**\n * A boolean flag for whether the thread is orphaned (i.e. the referenced text\n * has been deleted from the document). Adds a `bn-thread-orphaned` CSS class\n * to the thread.\n */\n orphaned?: boolean;\n /**\n * The text in the editor that the thread refers to. See the\n * [`ThreadsSidebar`](https://github.com/TypeCellOS/BlockNote/tree/main/packages/react/src/components/Comments/ThreadsSidebar.tsx#L137)\n * component to find out how to get this.\n */\n referenceText?: string;\n /**\n * The maximum number of comments that can be in a thread before the replies\n * get collapsed.\n */\n maxCommentsBeforeCollapse?: number;\n /**\n * A function to call when the thread is focused.\n */\n onFocus?: (event: FocusEvent) => void;\n /**\n * A function to call when the thread is blurred.\n */\n onBlur?: (event: FocusEvent) => void;\n /**\n * The tab index for the thread.\n */\n tabIndex?: number;\n};\n\n/**\n * The Thread component displays a (main) comment with a list of replies (other comments).\n *\n * It also includes a composer to reply to the thread.\n */\nexport const Thread = ({\n thread,\n selected,\n orphaned,\n referenceText,\n maxCommentsBeforeCollapse,\n onFocus,\n onBlur,\n tabIndex,\n}: ThreadProps) => {\n // TODO: if REST API becomes popular, all interactions (click handlers) should implement a loading state and error state\n // (or optimistic local updates)\n\n const Components = useComponentsContext()!;\n const dict = useDictionary();\n\n const comments = useExtension(CommentsExtension);\n\n const newCommentEditor = useCreateBlockNote({\n trailingBlock: false,\n dictionary: {\n ...dict,\n placeholders: {\n emptyDocument: dict.placeholders.comment_reply,\n },\n },\n schema: comments.commentEditorSchema || defaultCommentEditorSchema,\n });\n\n const onNewCommentSave = useCallback(async () => {\n await comments.threadStore.addComment({\n comment: {\n body: newCommentEditor.document,\n },\n threadId: thread.id,\n });\n\n // reset editor\n newCommentEditor.removeBlocks(newCommentEditor.document);\n }, [comments, newCommentEditor, thread.id]);\n\n return (\n <Components.Comments.Card\n className={mergeCSSClasses(\"bn-thread\", orphaned && \"bn-thread-orphaned\")}\n headerText={referenceText}\n onFocus={onFocus}\n onBlur={onBlur}\n selected={selected}\n tabIndex={tabIndex}\n >\n <Components.Comments.CardSection className=\"bn-thread-comments\">\n <Comments\n thread={thread}\n maxCommentsBeforeCollapse={\n !selected ? maxCommentsBeforeCollapse || 5 : undefined\n }\n />\n </Components.Comments.CardSection>\n {selected && (\n <Components.Comments.CardSection className={\"bn-thread-composer\"}>\n <CommentEditor\n autoFocus={false}\n editable={true}\n editor={newCommentEditor}\n actions={({ isFocused, isEmpty }) => (\n <ReplyActionsComponent\n isFocused={isFocused}\n isEmpty={isEmpty}\n onNewCommentSave={onNewCommentSave}\n Components={Components}\n dict={dict}\n />\n )}\n />\n </Components.Comments.CardSection>\n )}\n </Components.Comments.Card>\n );\n};\n","import { CommentsExtension } from \"@blocknote/core/comments\";\nimport { ThreadData } from \"@blocknote/core/comments\";\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\n\nimport { useExtension } from \"../../hooks/useExtension.js\";\n\n/**\n * Bridges the ThreadStore to React using useSyncExternalStore.\n */\nexport function useThreads() {\n const comments = useExtension(CommentsExtension);\n\n const store = comments.threadStore;\n\n // this ref works around this error:\n // https://react.dev/reference/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached\n // however, might not be a good practice to work around it this way\n const threadsRef = useRef<Map<string, ThreadData> | undefined>(undefined);\n\n if (!threadsRef.current) {\n threadsRef.current = store.getThreads();\n }\n\n const subscribe = useCallback(\n (cb: () => void) => {\n return store.subscribe((threads) => {\n // update ref when changed\n threadsRef.current = threads;\n cb();\n });\n },\n [store],\n );\n\n return useSyncExternalStore(subscribe, () => threadsRef.current!);\n}\n","import { 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 { useExtension, useExtensionState } from \"../../hooks/useExtension.js\";\nimport { FloatingUIOptions } from \"../Popovers/FloatingUIOptions.js\";\nimport { PositionPopover } from \"../Popovers/PositionPopover.js\";\nimport { Thread } from \"./Thread.js\";\nimport { useThreads } from \"./useThreads.js\";\n\n/**\n * This component is used to display a thread in a floating card.\n * It can be used when the user clicks on a thread / comment in the document.\n */\nexport default function FloatingThreadController(props: {\n floatingThread?: FC<ComponentProps<typeof Thread>>;\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 comments = useExtension(CommentsExtension);\n const selectedThread = useExtensionState(CommentsExtension, {\n editor,\n selector: (state) =>\n state.selectedThreadId\n ? {\n id: state.selectedThreadId,\n position: state.threadPositions.get(state.selectedThreadId),\n }\n : undefined,\n });\n\n const threads = useThreads();\n\n const thread = useMemo(\n () => (selectedThread ? threads.get(selectedThread.id) : undefined),\n [selectedThread, threads],\n );\n\n const floatingUIOptions = useMemo<FloatingUIOptions>(\n () => ({\n ...props.floatingUIOptions,\n useFloatingOptions: {\n open: !!selectedThread,\n // Needed as hooks like `useDismiss` call `onOpenChange` to change the\n // open state.\n onOpenChange: (open, _event, reason) => {\n if (reason === \"escape-key\") {\n editor.focus();\n }\n\n if (!open) {\n comments.selectThread(undefined);\n }\n },\n placement: \"bottom\",\n middleware: [offset(10), shift(), flip()],\n ...props.floatingUIOptions?.useFloatingOptions,\n },\n focusManagerProps: {\n disabled: true,\n ...props.floatingUIOptions?.focusManagerProps,\n },\n elementProps: {\n style: {\n zIndex: 30,\n },\n ...props.floatingUIOptions?.elementProps,\n },\n }),\n [comments, editor, props.floatingUIOptions, selectedThread],\n );\n\n // nice to have improvements:\n // - transition transform property so composer box animates when remote document is changed\n\n const Component = props.floatingThread || Thread;\n\n return (\n <PositionPopover\n position={selectedThread?.position}\n portalElement={props.portalElement}\n {...floatingUIOptions}\n >\n {thread && <Component thread={thread} selected={true} />}\n </PositionPopover>\n );\n}\n"],"mappings":"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"}