import { BubbleMenu, BubbleMenuProps, isNodeSelection, useEditor, } from "@tiptap/react"; import { FC, useEffect, useRef, useState } from "react"; import { IconBold, IconCode, IconItalic, IconStrikethrough, IconUnderline, IconMessage, } from "@tabler/icons-react"; import clsx from "clsx"; import classes from "./bubble-menu.module.css"; import { ActionIcon, rem, Tooltip } from "@mantine/core"; import { ColorSelector } from "./color-selector"; import { NodeSelector } from "./node-selector"; import { TextAlignmentSelector } from "./text-alignment-selector"; import { draftCommentIdAtom, showCommentPopupAtom, } from "@/features/comment/atoms/comment-atom"; import { useAtom } from "jotai"; import { v7 as uuid7 } from "uuid"; import { isCellSelection, isTextSelected } from "@docmost/editor-ext"; import { LinkSelector } from "@/features/editor/components/bubble-menu/link-selector.tsx"; import { useTranslation } from "react-i18next"; export interface BubbleMenuItem { name: string; isActive: () => boolean; command: () => void; icon: typeof IconBold; } type EditorBubbleMenuProps = Omit & { editor: ReturnType; }; export const EditorBubbleMenu: FC = (props) => { const { t } = useTranslation(); const [showCommentPopup, setShowCommentPopup] = useAtom(showCommentPopupAtom); const [, setDraftCommentId] = useAtom(draftCommentIdAtom); const showCommentPopupRef = useRef(showCommentPopup); useEffect(() => { showCommentPopupRef.current = showCommentPopup; }, [showCommentPopup]); const items: BubbleMenuItem[] = [ { name: "Bold", isActive: () => props.editor.isActive("bold"), command: () => props.editor.chain().focus().toggleBold().run(), icon: IconBold, }, { name: "Italic", isActive: () => props.editor.isActive("italic"), command: () => props.editor.chain().focus().toggleItalic().run(), icon: IconItalic, }, { name: "Underline", isActive: () => props.editor.isActive("underline"), command: () => props.editor.chain().focus().toggleUnderline().run(), icon: IconUnderline, }, { name: "Strike", isActive: () => props.editor.isActive("strike"), command: () => props.editor.chain().focus().toggleStrike().run(), icon: IconStrikethrough, }, { name: "Code", isActive: () => props.editor.isActive("code"), command: () => props.editor.chain().focus().toggleCode().run(), icon: IconCode, }, ]; const commentItem: BubbleMenuItem = { name: "Comment", isActive: () => props.editor.isActive("comment"), command: () => { const commentId = uuid7(); props.editor.chain().focus().setCommentDecoration().run(); setDraftCommentId(commentId); setShowCommentPopup(true); }, icon: IconMessage, }; const bubbleMenuProps: EditorBubbleMenuProps = { ...props, shouldShow: ({ state, editor }) => { const { selection } = state; const { empty } = selection; if ( !editor.isEditable || editor.isActive("image") || empty || isNodeSelection(selection) || isCellSelection(selection) || showCommentPopupRef?.current ) { return false; } return isTextSelected(editor); }, tippyOptions: { moveTransition: "transform 0.15s ease-out", onHide: () => { setIsNodeSelectorOpen(false); setIsTextAlignmentOpen(false); setIsColorSelectorOpen(false); setIsLinkSelectorOpen(false); }, }, }; const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false); const [isTextAlignmentSelectorOpen, setIsTextAlignmentOpen] = useState(false); const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false); const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false); return (
{ setIsNodeSelectorOpen(!isNodeSelectorOpen); setIsTextAlignmentOpen(false); setIsColorSelectorOpen(false); setIsLinkSelectorOpen(false); }} /> { setIsTextAlignmentOpen(!isTextAlignmentSelectorOpen); setIsNodeSelectorOpen(false); setIsColorSelectorOpen(false); setIsLinkSelectorOpen(false); }} /> {items.map((item, index) => ( ))} { setIsLinkSelectorOpen(!isLinkSelectorOpen); setIsNodeSelectorOpen(false); setIsTextAlignmentOpen(false); setIsColorSelectorOpen(false); }} /> { setIsColorSelectorOpen(!isColorSelectorOpen); setIsNodeSelectorOpen(false); setIsTextAlignmentOpen(false); setIsLinkSelectorOpen(false); }} />
); };