import { BubbleMenu as BaseBubbleMenu, findParentNode, posToDOMRect, useEditorState, } from "@tiptap/react"; import React, { useCallback } from "react"; import { Node as PMNode } from "prosemirror-model"; import { EditorMenuProps, ShouldShowProps, } from "@/features/editor/components/table/types/types.ts"; import { ActionIcon, Tooltip } from "@mantine/core"; import { IconAlertTriangleFilled, IconCircleCheckFilled, IconCircleXFilled, IconInfoCircleFilled, IconMoodSmile, } from "@tabler/icons-react"; import { CalloutType } from "@docmost/editor-ext"; import { useTranslation } from "react-i18next"; import EmojiPicker from "@/components/ui/emoji-picker.tsx"; export function CalloutMenu({ editor }: EditorMenuProps) { const { t } = useTranslation(); const shouldShow = useCallback( ({ state }: ShouldShowProps) => { if (!state) { return false; } return editor.isActive("callout"); }, [editor], ); const editorState = useEditorState({ editor, selector: (ctx) => { if (!ctx.editor) { return null; } return { isCallout: ctx.editor.isActive("callout"), isInfo: ctx.editor.isActive("callout", { type: "info" }), isSuccess: ctx.editor.isActive("callout", { type: "success" }), isWarning: ctx.editor.isActive("callout", { type: "warning" }), isDanger: ctx.editor.isActive("callout", { type: "danger" }), }; }, }); const getReferenceClientRect = useCallback(() => { const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "callout"; const parent = findParentNode(predicate)(selection); if (parent) { const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; return dom.getBoundingClientRect(); } return posToDOMRect(editor.view, selection.from, selection.to); }, [editor]); const setCalloutType = useCallback( (calloutType: CalloutType) => { editor .chain() .focus(undefined, { scrollIntoView: false }) .updateCalloutType(calloutType) .run(); }, [editor], ); const setCalloutIcon = useCallback( (emoji: any) => { const emojiChar = emoji?.native || emoji?.emoji || emoji; editor .chain() .focus(undefined, { scrollIntoView: false }) .updateCalloutIcon(emojiChar) .run(); }, [editor], ); const removeCalloutIcon = useCallback(() => { editor .chain() .focus(undefined, { scrollIntoView: false }) .updateCalloutIcon("") .run(); }, [editor]); const getCurrentIcon = () => { const { selection } = editor.state; const predicate = (node: PMNode) => node.type.name === "callout"; const parent = findParentNode(predicate)(selection); const icon = parent?.node.attrs.icon; return icon || null; }; const currentIcon = getCurrentIcon(); return ( setCalloutType("info")} size="lg" aria-label={t("Info")} variant={editorState?.isInfo ? "light" : "default"} > setCalloutType("success")} size="lg" aria-label={t("Success")} variant={editorState?.isSuccess ? "light" : "default"} > setCalloutType("warning")} size="lg" aria-label={t("Warning")} variant={editorState?.isWarning ? "light" : "default"} > setCalloutType("danger")} size="lg" aria-label={t("Danger")} variant={editorState?.isDanger ? "light" : "default"} > } actionIconProps={{ size: "lg", variant: "default", c: undefined, }} /> ); } export default CalloutMenu;