diff --git a/src/components/page/channel/TextChannel.tsx b/src/components/page/channel/TextChannel.tsx
index 55ccff4..27c2151 100644
--- a/src/components/page/channel/TextChannel.tsx
+++ b/src/components/page/channel/TextChannel.tsx
@@ -14,6 +14,7 @@ import {
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
+import { Textarea } from "@/components/ui/textarea";
export default function TextChannel({
appRef,
@@ -28,10 +29,9 @@ export default function TextChannel({
const account = appRef.current?.getAccount();
- const userPubKey =
- (account &&
- base58.encode(ed.getPublicKey(base58.decode(account.privateKey)))) ||
- null;
+ const secretKey = (account && base58.decode(account.privateKey)) || null;
+
+ const userPubKey = secretKey && base58.encode(ed.getPublicKey(secretKey));
useEffect(() => {
if (!intersectionRef.current) return;
@@ -88,6 +88,7 @@ export default function TextChannel({
message={message}
channel={channel}
userPubKey={userPubKey}
+ secretKey={secretKey}
/>
))}
@@ -116,28 +117,36 @@ export default function TextChannel({
export function TextMessage({
appRef,
message,
-
channel,
userPubKey,
+ secretKey,
}: {
appRef: React.RefObject | null>;
message: StoredMessage;
channel: Channel;
userPubKey: string | null;
+ secretKey: Uint8Array | null;
}) {
const time = new Date(message.timestamp).toLocaleTimeString([], {
hour: "numeric",
minute: "2-digit",
});
+
const author = appRef.current?.server?.users[message.author];
+
const [verified, setVerified] = useState(true);
+ const [editing, setEditing] = useState(false);
+ const [draft, setDraft] = useState(message.content);
useEffect(() => {
const serverPubKey =
appRef.current?.server?.serverPublicKey &&
- base58.encode(appRef.current?.server?.serverPublicKey);
+ base58.encode(appRef.current.server.serverPublicKey);
+
if (!serverPubKey) return;
+
const authorPubKey = base58.decode(message.author);
+
setVerified(
ed.verify(
base58.decode(message.signature),
@@ -159,6 +168,56 @@ export function TextMessage({
});
}
+ function startEditing() {
+ setDraft(message.content);
+ setEditing(true);
+ }
+
+ function cancelEditing() {
+ setDraft(message.content);
+ setEditing(false);
+ }
+
+ function handleSave() {
+ const content = draft.trim();
+
+ if (
+ !content ||
+ content === message.content ||
+ !secretKey ||
+ !appRef.current?.server?.serverPublicKey
+ ) {
+ cancelEditing();
+ return;
+ }
+
+ const signature = new TextEncoder().encode(
+ `${message.timestamp}@${base58.encode(appRef.current.server?.serverPublicKey)}@${content}`,
+ );
+
+ appRef.current?.server?.websocket?.send({
+ method: "EditMessage",
+ message_id: message.id,
+ channel_id: channel.id,
+ signature: base58.encode(ed.sign(signature, secretKey)),
+ content,
+ });
+
+ setEditing(false);
+ }
+
+ function handleKeyDown(e: React.KeyboardEvent) {
+ if (e.key === "Escape") {
+ e.preventDefault();
+ cancelEditing();
+ }
+
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ handleSave();
+ }
+ }
+
const content = (
@@ -168,6 +227,7 @@ export function TextMessage({
message.author.slice(0, 2).toUpperCase()}
+
@@ -176,30 +236,62 @@ export function TextMessage({
)}
{author?.displayName || message.author.slice(0, 8)}
+
{time}
- {message.is_edited && (
+
+ {message.is_edited && !editing && (
(edited)
)}
-
- {message.content}
-
+
+ {editing ? (
+
+ ) : (
+
+ {message.content}
+
+ )}
);
- if (!isOwnMessage) {
+ // Don't allow the context menu while editing
+ if (!isOwnMessage || editing) {
return content;
}
return (
{content}
+
- {}}>
+
Edit
+
Delete