Msg delete

This commit is contained in:
2026-08-22 12:54:38 +02:00
parent 0dc30233e9
commit e5ffc51189
2 changed files with 62 additions and 11 deletions
+61 -11
View File
@@ -3,11 +3,17 @@ import { ChannelPageProps } from "@/components/page/PageView";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { ClientMeta, StoredMessage } from "@/lib/types"; import { Channel, StoredMessage } from "@/lib/types";
import { base58 } from "@scure/base"; import { base58 } from "@scure/base";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import * as ed from "@noble/ed25519"; import * as ed from "@noble/ed25519";
import { CircleXIcon } from "lucide-react"; import { CircleXIcon, PencilIcon, Trash2Icon } from "lucide-react";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
export default function TextChannel({ export default function TextChannel({
appRef, appRef,
@@ -20,6 +26,13 @@ export default function TextChannel({
const intersectionRef = useRef<HTMLDivElement | null>(null); const intersectionRef = useRef<HTMLDivElement | null>(null);
const chunkRef = useRef(0); const chunkRef = useRef(0);
const account = appRef.current?.getAccount();
const userPubKey =
(account &&
base58.encode(ed.getPublicKey(base58.decode(account.privateKey)))) ||
null;
useEffect(() => { useEffect(() => {
if (!intersectionRef.current) return; if (!intersectionRef.current) return;
@@ -69,7 +82,13 @@ export default function TextChannel({
Object.entries(messages) Object.entries(messages)
.sort((a, b) => b[1].timestamp - a[1].timestamp) .sort((a, b) => b[1].timestamp - a[1].timestamp)
.map(([_, message]) => ( .map(([_, message]) => (
<TextMessage key={message.id} appRef={appRef} message={message} /> <TextMessage
key={message.id}
appRef={appRef}
message={message}
channel={channel}
userPubKey={userPubKey}
/>
))} ))}
<div ref={intersectionRef} /> <div ref={intersectionRef} />
</div> </div>
@@ -97,28 +116,28 @@ export default function TextChannel({
export function TextMessage({ export function TextMessage({
appRef, appRef,
message, message,
channel,
userPubKey,
}: { }: {
appRef: React.RefObject<Enclave<ChannelPageProps> | null>; appRef: React.RefObject<Enclave<ChannelPageProps> | null>;
message: StoredMessage; message: StoredMessage;
channel: Channel;
userPubKey: string | null;
}) { }) {
const time = new Date(message.timestamp).toLocaleTimeString([], { const time = new Date(message.timestamp).toLocaleTimeString([], {
hour: "numeric", hour: "numeric",
minute: "2-digit", minute: "2-digit",
}); });
const author = appRef.current?.server?.users[message.author]; const author = appRef.current?.server?.users[message.author];
const [verified, setVerified] = useState(true); const [verified, setVerified] = useState(true);
useEffect(() => { useEffect(() => {
const serverPubKey = const serverPubKey =
appRef.current?.server?.serverPublicKey && appRef.current?.server?.serverPublicKey &&
base58.encode(appRef.current?.server?.serverPublicKey); base58.encode(appRef.current?.server?.serverPublicKey);
if (!serverPubKey) return; if (!serverPubKey) return;
const authorPubKey = base58.decode(message.author); const authorPubKey = base58.decode(message.author);
setVerified( setVerified(
ed.verify( ed.verify(
base58.decode(message.signature), base58.decode(message.signature),
@@ -130,17 +149,25 @@ export function TextMessage({
); );
}, []); }, []);
return ( const isOwnMessage = message.author === userPubKey;
function handleDelete() {
appRef.current?.server?.websocket?.send({
method: "DeleteMessage",
message_id: message.id,
channel_id: channel.id,
});
}
const content = (
<div className="rounded-lg flex gap-3 px-3 py-3 hover:bg-muted/40"> <div className="rounded-lg flex gap-3 px-3 py-3 hover:bg-muted/40">
<Avatar className="h-10 w-10"> <Avatar className="h-10 w-10">
<AvatarImage src={author?.avatar} /> <AvatarImage src={author?.avatar} />
<AvatarFallback> <AvatarFallback>
{author?.displayName.slice(0, 1).toUpperCase() || {author?.displayName.slice(0, 1).toUpperCase() ||
message.author.slice(0, 2).toUpperCase()} message.author.slice(0, 2).toUpperCase()}
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium flex items-center gap-1"> <span className="text-sm font-medium flex items-center gap-1">
@@ -150,6 +177,9 @@ export function TextMessage({
{author?.displayName || message.author.slice(0, 8)} {author?.displayName || message.author.slice(0, 8)}
</span> </span>
<span className="text-xs text-muted-foreground">{time}</span> <span className="text-xs text-muted-foreground">{time}</span>
{message.is_edited && (
<span className="text-xs text-muted-foreground">(edited)</span>
)}
</div> </div>
<p className="whitespace-pre-wrap break-words text-sm leading-snug"> <p className="whitespace-pre-wrap break-words text-sm leading-snug">
{message.content} {message.content}
@@ -157,4 +187,24 @@ export function TextMessage({
</div> </div>
</div> </div>
); );
if (!isOwnMessage) {
return content;
}
return (
<ContextMenu>
<ContextMenuTrigger>{content}</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onClick={() => {}}>
<PencilIcon className="mr-2 h-4 w-4" />
Edit
</ContextMenuItem>
<ContextMenuItem onClick={handleDelete} variant="destructive">
<Trash2Icon className="mr-2 h-4 w-4" />
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
} }
+1
View File
@@ -18,6 +18,7 @@ export interface MessageData {
export interface StoredMessage extends MessageData { export interface StoredMessage extends MessageData {
id: string; id: string;
author: string; author: string;
is_edited: boolean;
} }
export type Channel = { id: string; name: string } & ChannelKind; export type Channel = { id: string; name: string } & ChannelKind;