feat: add forward msg
This commit is contained in:
+117
-54
@@ -7,7 +7,9 @@ import {
|
||||
CheckCheckIcon,
|
||||
CheckIcon,
|
||||
ChevronDown,
|
||||
CornerUpRightIcon,
|
||||
Loader2Icon,
|
||||
MousePointer2Icon,
|
||||
PaperclipIcon,
|
||||
PhoneCallIcon,
|
||||
SendIcon,
|
||||
@@ -32,9 +34,11 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { forwardDlg } from "./stores";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -53,6 +57,7 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
const { data: chats } = useChats();
|
||||
const { data: contacts } = useContacts();
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string> | null>(null);
|
||||
|
||||
const {
|
||||
data: messages,
|
||||
@@ -83,7 +88,10 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (chatId) msgContainerRef.current.scrollTop = 0;
|
||||
if (chatId) {
|
||||
msgContainerRef.current.scrollTop = 0;
|
||||
setSelectedIds(null);
|
||||
}
|
||||
}, [chatId]);
|
||||
|
||||
useMessageEvent((data) => {
|
||||
@@ -163,11 +171,23 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
});
|
||||
}, [messages, contacts, search]);
|
||||
|
||||
async function onDeleteMsg(id: string) {
|
||||
function onSelectMsg(id: string) {
|
||||
setSelectedIds((s) => {
|
||||
const set = new Set(s || []);
|
||||
if (!set.has(id)) {
|
||||
set.add(id);
|
||||
} else {
|
||||
set.delete(id);
|
||||
}
|
||||
return !set.size ? null : set;
|
||||
});
|
||||
}
|
||||
|
||||
async function onDeleteMsg(id: string | string[]) {
|
||||
if (!window.confirm("Delete this message?")) return;
|
||||
try {
|
||||
await client.exec(
|
||||
`return WPP.chat.deleteMessage('${chatId}', '${id}', true, true);`,
|
||||
`return WPP.chat.deleteMessage('${chatId}', ${JSON.stringify(id)}, true, true);`,
|
||||
);
|
||||
refetch();
|
||||
} catch (err) {
|
||||
@@ -192,63 +212,82 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
className={`flex min-w-0 h-full flex-1 flex-col bg-muted ${chatId ? "flex" : "hidden md:flex"}`}
|
||||
>
|
||||
<header className="flex items-center justify-between border-b border-border bg-card px-4 py-3 sm:px-6">
|
||||
<div className="flex min-w-0 items-center gap-3 w-full">
|
||||
<button
|
||||
aria-label="Back to conversations"
|
||||
onClick={() => router.goBack()}
|
||||
className="grid size-9 place-items-center rounded-full text-muted-foreground hover:bg-muted md:hidden cursor-pointer -ml-2"
|
||||
>
|
||||
<ArrowLeftIcon size={19} />
|
||||
</button>
|
||||
{selectedIds?.size ? (
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<button
|
||||
aria-label="Cancel selection"
|
||||
onClick={() => setSelectedIds(null)}
|
||||
className="grid size-10 place-items-center rounded-full text-muted-foreground hover:bg-muted cursor-pointer -ml-2"
|
||||
>
|
||||
<XIcon size={19} />
|
||||
</button>
|
||||
<p className="text-sm font-medium truncate">{`${selectedIds.size} selected`}</p>
|
||||
<div className="flex-1" />
|
||||
|
||||
<Avatar
|
||||
name={chat?.formattedTitle || chat?.name}
|
||||
className="size-10"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-lg"
|
||||
className="rounded-full"
|
||||
title="Forward"
|
||||
onClick={() => {
|
||||
forwardDlg.onOpen({ messageIds: [...selectedIds] });
|
||||
setSelectedIds(null);
|
||||
}}
|
||||
>
|
||||
<CornerUpRightIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-lg"
|
||||
className="rounded-full"
|
||||
title="Delete"
|
||||
onClick={() => {
|
||||
onDeleteMsg([...selectedIds]);
|
||||
setSelectedIds(null);
|
||||
}}
|
||||
>
|
||||
<Trash2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 items-center gap-3 w-full">
|
||||
<button
|
||||
aria-label="Back to conversations"
|
||||
onClick={() => router.goBack()}
|
||||
className="grid size-9 place-items-center rounded-full text-muted-foreground hover:bg-muted md:hidden cursor-pointer -ml-2"
|
||||
>
|
||||
<ArrowLeftIcon size={19} />
|
||||
</button>
|
||||
|
||||
<div className="min-w-0 flex-1 truncate">
|
||||
<h2 className="truncate text-sm font-semibold">
|
||||
{chat?.formattedTitle || chat?.name}
|
||||
</h2>
|
||||
<Avatar
|
||||
name={chat?.formattedTitle || chat?.name}
|
||||
className="size-10 -ml-2"
|
||||
/>
|
||||
|
||||
{/* <p className="text-xs text-[#1ea66a]">
|
||||
<div className="min-w-0 flex-1 truncate">
|
||||
<h2 className="truncate text-sm font-semibold">
|
||||
{chat?.formattedTitle || chat?.name}
|
||||
</h2>
|
||||
|
||||
{/* <p className="text-xs text-[#1ea66a]">
|
||||
{selected.online ? "Online now" : "Active recently"}
|
||||
</p> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SearchInput
|
||||
className="w-full max-w-40 md:max-w-sm"
|
||||
inputClassName="text-xs placeholder:text-xs"
|
||||
placeholder="Search..."
|
||||
defaultValue={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="flex items-center gap-1 text-muted-foreground">
|
||||
<button
|
||||
aria-label="Start video call"
|
||||
className="grid size-9 place-items-center rounded-full hover:bg-muted"
|
||||
>
|
||||
<VideoIcon size={18} />
|
||||
</button>
|
||||
<button
|
||||
aria-label="Start voice call"
|
||||
className="grid size-9 place-items-center rounded-full hover:bg-muted"
|
||||
>
|
||||
<Phone size={18} />
|
||||
</button>
|
||||
<button
|
||||
aria-label="Chat details"
|
||||
className="grid size-9 place-items-center rounded-full hover:bg-muted"
|
||||
>
|
||||
<EllipsisVertical size={18} />
|
||||
</button>
|
||||
</div> */}
|
||||
<SearchInput
|
||||
className="w-full max-w-40 md:max-w-sm"
|
||||
inputClassName="text-xs placeholder:text-xs"
|
||||
placeholder="Search..."
|
||||
defaultValue={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div
|
||||
ref={msgContainerRef}
|
||||
className="message-pattern flex-1 px-4 py-6 sm:px-8 flex flex-col-reverse overflow-y-auto"
|
||||
className="message-pattern flex-1 py-6 flex flex-col-reverse overflow-y-auto"
|
||||
>
|
||||
{/* <div className="mb-6 flex items-center justify-center gap-2 text-[11px] text-muted-foreground">
|
||||
<span className="h-px flex-1 bg-border" />
|
||||
@@ -261,9 +300,20 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
id={item.id.replace(/^(?:false_|true_)/, "")}
|
||||
key={`${item.timestamp}-${index}`}
|
||||
className={cn(
|
||||
"mb-3 flex",
|
||||
"flex px-4 sm:px-8 py-1.5",
|
||||
item.fromMe ? "justify-end" : "justify-start",
|
||||
selectedIds != null && "cursor-pointer hover:bg-[#26855c]/10",
|
||||
selectedIds?.has(item.id) && "bg-[#26855c]/30!",
|
||||
)}
|
||||
role={selectedIds != null ? "button" : undefined}
|
||||
onClick={
|
||||
selectedIds != null
|
||||
? (e) => {
|
||||
if (e.target !== e.currentTarget) return;
|
||||
onSelectMsg(item.id);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className="max-w-[82%] sm:max-w-[65%]">
|
||||
<a
|
||||
@@ -280,7 +330,7 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
</a>
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl px-4 py-2.5 text-sm shadow-sm relative group",
|
||||
"rounded-2xl px-4 py-2.5 text-sm shadow-sm relative group rounded-tr-md",
|
||||
item.fromMe
|
||||
? "rounded-br-md bg-[#d8f5e8] dark:bg-[#26855c] text-foreground"
|
||||
: "rounded-bl-md bg-card text-card-foreground",
|
||||
@@ -290,15 +340,28 @@ export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="default"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="absolute top-1 right-1 z-1 rounded-full opacity-0 group-hover:opacity-100"
|
||||
className="absolute top-0 right-0 z-1 md:opacity-0 group-hover:opacity-100"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ChevronDown />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onSelectMsg(item.id)}>
|
||||
<MousePointer2Icon />
|
||||
Select
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
forwardDlg.onOpen({ messageIds: [item.id] })
|
||||
}
|
||||
>
|
||||
<CornerUpRightIcon />
|
||||
Forward
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onDeleteMsg(item.id)}>
|
||||
<Trash2Icon />
|
||||
Delete
|
||||
|
||||
Reference in New Issue
Block a user