mirror of
https://github.com/docmost/docmost.git
synced 2025-11-10 06:22:07 +10:00
feat: emoji callout icon (#1323)
This commit is contained in:
@ -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}
|
||||
>
|
||||
<Popover.Target ref={setTarget}>
|
||||
<ActionIcon c="gray" variant="transparent" onClick={handlers.toggle}>
|
||||
<ActionIcon
|
||||
c={actionIconProps?.c || "gray"}
|
||||
variant={actionIconProps?.variant || "transparent"}
|
||||
size={actionIconProps?.size}
|
||||
onClick={handlers.toggle}
|
||||
>
|
||||
{icon}
|
||||
</ActionIcon>
|
||||
</Popover.Target>
|
||||
|
||||
@ -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 (
|
||||
<BaseBubbleMenu
|
||||
editor={editor}
|
||||
@ -130,6 +163,20 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
||||
<IconCircleXFilled size={18} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip position="top" label={t("Custom emoji")}>
|
||||
<EmojiPicker
|
||||
onEmojiSelect={setCalloutIcon}
|
||||
removeEmojiAction={removeCalloutIcon}
|
||||
readOnly={false}
|
||||
icon={currentIcon || <IconMoodSmile size={18} />}
|
||||
actionIconProps={{
|
||||
size: "lg",
|
||||
variant: "default",
|
||||
c: undefined
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ActionIcon.Group>
|
||||
</BaseBubbleMenu>
|
||||
);
|
||||
|
||||
@ -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 (
|
||||
<NodeViewWrapper>
|
||||
@ -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 <span style={{ fontSize: '18px' }}>{customIcon}</span>;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "info":
|
||||
return <IconInfoCircleFilled />;
|
||||
|
||||
@ -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<CalloutOptions>({
|
||||
"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<CalloutOptions>({
|
||||
commands.updateAttributes("callout", {
|
||||
type: getValidCalloutType(type),
|
||||
}),
|
||||
|
||||
updateCalloutIcon:
|
||||
(icon: string) =>
|
||||
({ commands }) =>
|
||||
commands.updateAttributes("callout", {
|
||||
icon: icon || null,
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user