diff --git a/apps/client/src/components/ui/emoji-picker.tsx b/apps/client/src/components/ui/emoji-picker.tsx index 952c1171..112c2d9c 100644 --- a/apps/client/src/components/ui/emoji-picker.tsx +++ b/apps/client/src/components/ui/emoji-picker.tsx @@ -15,6 +15,11 @@ export interface EmojiPickerInterface { icon: ReactNode; removeEmojiAction: () => void; readOnly: boolean; + actionIconProps?: { + size?: string; + variant?: string; + c?: string; + }; } function EmojiPicker({ @@ -22,6 +27,7 @@ function EmojiPicker({ icon, removeEmojiAction, readOnly, + actionIconProps, }: EmojiPickerInterface) { const { t } = useTranslation(); const [opened, handlers] = useDisclosure(false); @@ -64,7 +70,12 @@ function EmojiPicker({ closeOnEscape={true} > - + {icon} diff --git a/apps/client/src/features/editor/components/callout/callout-menu.tsx b/apps/client/src/features/editor/components/callout/callout-menu.tsx index 56dea233..400460e7 100644 --- a/apps/client/src/features/editor/components/callout/callout-menu.tsx +++ b/apps/client/src/features/editor/components/callout/callout-menu.tsx @@ -9,18 +9,21 @@ import { EditorMenuProps, ShouldShowProps, } from "@/features/editor/components/table/types/types.ts"; -import { ActionIcon, Tooltip } from "@mantine/core"; +import { ActionIcon, Tooltip, Divider } 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) { @@ -56,6 +59,36 @@ export function CalloutMenu({ editor }: EditorMenuProps) { [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 ( + + + } + actionIconProps={{ + size: "lg", + variant: "default", + c: undefined + }} + /> + ); diff --git a/apps/client/src/features/editor/components/callout/callout-view.tsx b/apps/client/src/features/editor/components/callout/callout-view.tsx index 0df410a3..5583bd87 100644 --- a/apps/client/src/features/editor/components/callout/callout-view.tsx +++ b/apps/client/src/features/editor/components/callout/callout-view.tsx @@ -11,7 +11,7 @@ import { CalloutType } from "@docmost/editor-ext"; export default function CalloutView(props: NodeViewProps) { const { node } = props; - const { type } = node.attrs; + const { type, icon } = node.attrs; return ( @@ -19,7 +19,7 @@ export default function CalloutView(props: NodeViewProps) { variant="light" title="" color={getCalloutColor(type)} - icon={getCalloutIcon(type)} + icon={getCalloutIcon(type, icon)} p="xs" classNames={{ message: classes.message, @@ -32,7 +32,11 @@ export default function CalloutView(props: NodeViewProps) { ); } -function getCalloutIcon(type: CalloutType) { +function getCalloutIcon(type: CalloutType, customIcon?: string) { + if (customIcon && customIcon.trim() !== "") { + return {customIcon}; + } + switch (type) { case "info": return ; diff --git a/packages/editor-ext/src/lib/callout/callout.ts b/packages/editor-ext/src/lib/callout/callout.ts index c756917b..97c5dfcc 100644 --- a/packages/editor-ext/src/lib/callout/callout.ts +++ b/packages/editor-ext/src/lib/callout/callout.ts @@ -18,6 +18,10 @@ export interface CalloutAttributes { * The type of callout. */ type: CalloutType; + /** + * The custom icon name for the callout. + */ + icon?: string; } declare module "@tiptap/core" { @@ -27,6 +31,7 @@ declare module "@tiptap/core" { unsetCallout: () => ReturnType; toggleCallout: (attributes?: CalloutAttributes) => ReturnType; updateCalloutType: (type: CalloutType) => ReturnType; + updateCalloutIcon: (icon: string) => ReturnType; }; } } @@ -58,6 +63,13 @@ export const Callout = Node.create({ "data-callout-type": attributes.type, }), }, + icon: { + default: null, + parseHTML: (element) => element.getAttribute("data-callout-icon"), + renderHTML: (attributes) => ({ + "data-callout-icon": attributes.icon, + }), + }, }; }, @@ -107,6 +119,13 @@ export const Callout = Node.create({ commands.updateAttributes("callout", { type: getValidCalloutType(type), }), + + updateCalloutIcon: + (icon: string) => + ({ commands }) => + commands.updateAttributes("callout", { + icon: icon || null, + }), }; },