From 8da412c0d40884d1b4e4517b7a97705609de1e72 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:15:31 +0100 Subject: [PATCH] feat: create message context menu builder function --- .../src/components/chat/messageMenuItems.tsx | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 packages/web/src/components/chat/messageMenuItems.tsx diff --git a/packages/web/src/components/chat/messageMenuItems.tsx b/packages/web/src/components/chat/messageMenuItems.tsx new file mode 100644 index 00000000..534c781d --- /dev/null +++ b/packages/web/src/components/chat/messageMenuItems.tsx @@ -0,0 +1,183 @@ +import React from 'react'; +import type { ContextMenuItem } from '../../stores/contextMenuStore'; +import type { MessageWithUser } from '@backspace/shared'; + +const QUICK_EMOJIS = ['👍', '❤️', '😂', '😮']; + +interface MessageMenuParams { + message: MessageWithUser; + selectedText: string; + previousMessageId: string | null; + isAuthor: boolean; + isDm: boolean; + canAddReactions: boolean; + canSendMessages: boolean; + canManageMessages: boolean; + onReply: () => void; + onEdit: () => void; + onDelete: () => void; + onReaction: (emoji: string) => void; + onOpenEmojiPicker: () => void; + onMarkUnread: (messageId: string) => void; +} + +export function buildMessageMenuItems(params: MessageMenuParams): ContextMenuItem[] { + const { + message, + selectedText, + previousMessageId, + isAuthor, + isDm, + canAddReactions, + canSendMessages, + canManageMessages, + onReply, + onEdit, + onDelete, + onReaction, + onOpenEmojiPicker, + onMarkUnread, + } = params; + + const items: ContextMenuItem[] = []; + + // ── Quick Reaction Row ────────────────────────────────────────────────── + if (canAddReactions) { + items.push({ + key: 'quick-reactions', + type: 'custom', + render: () => ( +
+ {QUICK_EMOJIS.map(emoji => ( + + ))} + +
+ ), + }); + items.push({ key: 'reactions-sep', type: 'separator' }); + } + + // ── Reply ─────────────────────────────────────────────────────────────── + const canReply = isDm || canSendMessages; + if (canReply) { + items.push({ + key: 'reply', + type: 'action', + label: 'Reply', + icon: ( + + + + ), + onClick: onReply, + }); + items.push({ key: 'reply-sep', type: 'separator' }); + } + + // ── Clipboard group ───────────────────────────────────────────────────── + items.push({ + key: 'copy-selected', + type: 'action', + label: 'Copy Selected Text', + hidden: selectedText.length === 0, + icon: ( + + + + ), + onClick: () => { + navigator.clipboard.writeText(selectedText); + }, + }); + + items.push({ + key: 'copy-text', + type: 'action', + label: 'Copy Text', + hidden: !message.content, + icon: ( + + + + ), + onClick: () => { + navigator.clipboard.writeText(message.content ?? ''); + }, + }); + + items.push({ key: 'clipboard-sep', type: 'separator' }); + + // ── Mark Unread ───────────────────────────────────────────────────────── + items.push({ + key: 'mark-unread', + type: 'action', + label: 'Mark Unread', + icon: ( + + + + + ), + onClick: () => { + // '0' sentinel marks the entire channel as unread (when no previous message exists) + onMarkUnread(previousMessageId ?? '0'); + }, + }); + + items.push({ key: 'unread-sep', type: 'separator' }); + + // ── Edit Message (author only) ────────────────────────────────────────── + if (isAuthor) { + items.push({ + key: 'edit', + type: 'action', + label: 'Edit Message', + icon: ( + + + + ), + onClick: onEdit, + }); + } + + // ── Delete Message (author or moderator) ──────────────────────────────── + const canDelete = isAuthor || canManageMessages; + if (canDelete) { + items.push({ + key: 'delete', + type: 'action', + label: 'Delete Message', + icon: ( + + + + ), + onClick: onDelete, + danger: true, + }); + } + + return items; +}