feat: add remove message feature

This commit is contained in:
2026-09-18 22:45:15 +08:00
parent edf28006b2
commit 4d70805581
2 changed files with 336 additions and 10 deletions
+67 -10
View File
@@ -6,10 +6,12 @@ import {
ArrowLeftIcon,
CheckCheckIcon,
CheckIcon,
ChevronDown,
Loader2Icon,
PaperclipIcon,
PhoneCallIcon,
SendIcon,
Trash2Icon,
XIcon,
} from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
@@ -26,6 +28,13 @@ import Paragraph from "@/components/paragraph";
import { nanoid } from "nanoid";
import { Skeleton } from "@/components/ui/skeleton";
import SearchInput from "@/components/search-input";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
dayjs.extend(relativeTime);
@@ -34,20 +43,17 @@ type Props = {
maxCount?: number;
};
export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
export default function MessagesSection({ chatId, maxCount = 20 }: Props) {
const { client, isReady } = useWAClient();
const router = useHashRouter();
const msgContainerRef = useRef<HTMLDivElement>(null!);
const lastFetch = useRef<number>(0);
const queryClient = useQueryClient();
const { data: chats } = useChats();
const { data: contacts } = useContacts();
const [search, setSearch] = useState("");
useEffect(() => {
if (chatId) msgContainerRef.current.scrollTop = 0;
}, [chatId]);
const {
data: messages,
isLoading,
@@ -76,6 +82,10 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
select: (data) => data.pages.flat(),
});
useEffect(() => {
if (chatId) msgContainerRef.current.scrollTop = 0;
}, [chatId]);
useMessageEvent((data) => {
if (data.chatId !== chatId) return;
@@ -90,9 +100,15 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
});
const endScrollRef = useIntersection(() => {
if (hasNextPage && !isFetchingNextPage && !isLoading) {
fetchNextPage();
}
if (!hasNextPage || isFetchingNextPage || isLoading) return;
const t = Date.now() - lastFetch.current;
if (t < 1000) return;
fetchNextPage().then(() => {
lastFetch.current = Date.now();
msgContainerRef.current.scrollTop -= 10;
});
});
const chat = useMemo(
@@ -147,6 +163,18 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
});
}, [messages, contacts, search]);
async function onDeleteMsg(id: string) {
if (!window.confirm("Delete this message?")) return;
try {
await client.exec(
`return WPP.chat.deleteMessage('${chatId}', '${id}', true, true);`,
);
refetch();
} catch (err) {
console.error(err);
}
}
function getMentionLabel(username: string) {
const data = contacts?.find((c) => (c.id as any).user === username);
return data?.notifyName || data?.formattedName || data?.name || username;
@@ -232,7 +260,10 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
<div
id={item.id.replace(/^(?:false_|true_)/, "")}
key={`${item.timestamp}-${index}`}
className={`mb-3 flex ${item.fromMe ? "justify-end" : "justify-start"}`}
className={cn(
"mb-3 flex",
item.fromMe ? "justify-end" : "justify-start",
)}
>
<div className="max-w-[82%] sm:max-w-[65%]">
<a
@@ -248,8 +279,33 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
{getFormattedName(item.from) || item.notifyName}
</a>
<div
className={`rounded-2xl px-4 py-2.5 text-sm shadow-sm ${item.fromMe ? "rounded-br-md bg-[#d8f5e8] dark:bg-[#26855c] text-foreground" : "rounded-bl-md bg-card text-card-foreground"}`}
className={cn(
"rounded-2xl px-4 py-2.5 text-sm shadow-sm relative group",
item.fromMe
? "rounded-br-md bg-[#d8f5e8] dark:bg-[#26855c] text-foreground"
: "rounded-bl-md bg-card text-card-foreground",
)}
>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="default"
size="icon-sm"
className="absolute top-1 right-1 z-1 rounded-full opacity-0 group-hover:opacity-100"
/>
}
>
<ChevronDown />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onDeleteMsg(item.id)}>
<Trash2Icon />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{item.isForwarded ? (
<p className="text-xs italic mb-1 text-primary/80">
Forwarded
@@ -522,6 +578,7 @@ function MessageInputSection({
value={message}
onChange={(event) => setMessage(event.target.value)}
onPaste={onPaste}
enterKeyHint="enter"
onKeyDown={(event) => {
if (
event.key === "Enter" &&