diff --git a/plane-src/apps/web/core/components/editor/lite-text/toolbar.tsx b/plane-src/apps/web/core/components/editor/lite-text/toolbar.tsx index c66f142..42d450e 100644 --- a/plane-src/apps/web/core/components/editor/lite-text/toolbar.tsx +++ b/plane-src/apps/web/core/components/editor/lite-text/toolbar.tsx @@ -4,7 +4,7 @@ * See the LICENSE file for details. */ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback, useMemo } from "react"; import { ArrowUp, Paperclip, SmilePlus } from "lucide-react"; import type { LucideIcon } from "lucide-react"; @@ -17,6 +17,7 @@ import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; import { GlobeIcon, LockIcon } from "@plane/propel/icons"; import type { ISvgIcons } from "@plane/propel/icons"; +import { Popover } from "@plane/propel/popover"; import { Tooltip } from "@plane/propel/tooltip"; // constants import { cn } from "@plane/utils"; @@ -58,12 +59,177 @@ const COMMENT_ACCESS_SPECIFIERS: TCommentAccessType[] = [ ]; const toolbarItems = TOOLBAR_ITEMS.lite; -const EMOJI_ITEM: ToolbarMenuItem<"emoji"> = { - itemKey: "emoji", - renderKey: "emoji", - name: "Emoji", - icon: SmilePlus, - editors: ["lite", "document"], +const COMMENT_RECENT_EMOJI_STORAGE_KEY = "nodedc-comment-emoji-recent"; +const COMMENT_RECENT_EMOJI_LIMIT = 8; +const COMMENT_EMOJIS = [ + "๐Ÿ˜€", + "๐Ÿ˜ƒ", + "๐Ÿ˜„", + "๐Ÿ˜", + "๐Ÿ˜†", + "๐Ÿ˜…", + "๐Ÿคฃ", + "๐Ÿ˜‚", + "๐Ÿ™‚", + "๐Ÿ˜‰", + "๐Ÿ˜Š", + "๐Ÿ˜‡", + "๐Ÿฅฐ", + "๐Ÿ˜", + "๐Ÿคฉ", + "๐Ÿ˜˜", + "๐Ÿ˜—", + "๐Ÿ˜š", + "๐Ÿ˜‹", + "๐Ÿ˜›", + "๐Ÿ˜œ", + "๐Ÿคช", + "๐Ÿซ ", + "๐Ÿค—", + "๐Ÿค”", + "๐Ÿซก", + "๐Ÿค", + "๐Ÿ‘", + "๐Ÿ™Œ", + "๐Ÿ‘", + "๐Ÿ‘Ž", + "๐Ÿ”ฅ", + "๐Ÿ’ฏ", + "โœจ", + "๐ŸŽ‰", + "โค๏ธ", + "๐Ÿงก", + "๐Ÿ’›", + "๐Ÿ’š", + "๐Ÿ’™", + "๐Ÿ’œ", + "๐Ÿค", + "๐ŸคŽ", + "๐Ÿ–ค", + "๐Ÿ˜Ž", + "๐Ÿค“", + "๐Ÿฅณ", + "๐Ÿ˜ด", + "๐Ÿคฏ", + "๐Ÿ˜ฌ", + "๐Ÿ˜Œ", + "๐Ÿฅฒ", + "๐Ÿ˜ญ", + "๐Ÿ˜ก", + "๐Ÿคฎ", + "๐Ÿคข", + "๐Ÿคก", + "๐Ÿ‘€", + "๐Ÿ™", + "๐Ÿ‘Œ", + "โœ…", + "โŒ", +]; + +const readRecentCommentEmojis = (storageKey: string) => { + if (typeof window === "undefined") return []; + + try { + const value = window.localStorage.getItem(storageKey); + if (!value) return []; + + const parsed = JSON.parse(value); + return Array.isArray(parsed) ? parsed.filter((emoji): emoji is string => typeof emoji === "string") : []; + } catch { + return []; + } +}; + +const writeRecentCommentEmoji = (storageKey: string, emoji: string, limit: number) => { + if (typeof window === "undefined") return []; + + const next = [emoji, ...readRecentCommentEmojis(storageKey).filter((value) => value !== emoji)].slice(0, limit); + + try { + window.localStorage.setItem(storageKey, JSON.stringify(next)); + } catch { + return next; + } + + return next; +}; + +type CompactCommentEmojiPickerProps = { + isOpen: boolean; + onEmojiSelect: (emoji: string) => void; + onOpenChange: (value: boolean) => void; +}; + +const CompactCommentEmojiPicker: React.FC = ({ + isOpen, + onEmojiSelect, + onOpenChange, +}) => { + const [recentEmojis, setRecentEmojis] = useState(() => + readRecentCommentEmojis(COMMENT_RECENT_EMOJI_STORAGE_KEY) + ); + + const handleEmojiSelect = useCallback( + (emoji: string) => { + setRecentEmojis( + writeRecentCommentEmoji(COMMENT_RECENT_EMOJI_STORAGE_KEY, emoji, COMMENT_RECENT_EMOJI_LIMIT) + ); + onEmojiSelect(emoji); + onOpenChange(false); + }, + [onEmojiSelect, onOpenChange] + ); + + const mainEmojis = useMemo( + () => COMMENT_EMOJIS.filter((emoji) => !recentEmojis.includes(emoji)), + [recentEmojis] + ); + + const renderEmojiButton = (emoji: string, isRecent = false) => ( + + ); + + return ( + + + +
+ +
+
+
+ event.stopPropagation()} + onClick={(event) => event.stopPropagation()} + > +
+ {recentEmojis.length > 0 && ( +
+ {recentEmojis.map((emoji) => renderEmojiButton(emoji, true))} +
+ )} +
{mainEmojis.map((emoji) => renderEmojiButton(emoji))}
+
+
+
+ ); }; export function IssueCommentToolbar(props: Props) { @@ -83,6 +249,7 @@ export function IssueCommentToolbar(props: Props) { } = props; // State to manage active states of toolbar items const [activeStates, setActiveStates] = useState>({}); + const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false); // Function to update active states const updateActiveStates = useCallback(() => { @@ -125,15 +292,11 @@ export function IssueCommentToolbar(props: Props) { - - - + editorRef?.setEditorValueAtCursorPosition(emoji)} + /> {showSubmitButton && ( + ))} + + + )} ( -
- {category.label} -
- ), + CategoryHeader: categoryHeadersHidden + ? ({ ...props }) => ( +