(feat): add message editing
Some checks failed
/ build-web (push) Successful in 6m58s
/ release (push) Has been cancelled
/ build-desktop (linux) (push) Has been cancelled
/ build-mobile (push) Has been cancelled

(qol): update todos
This commit is contained in:
Alois 2026-07-07 18:13:43 +02:00
commit 8dd73568ce
8 changed files with 310 additions and 6 deletions

View file

@ -27,6 +27,10 @@ import MessageContextMenu from "./messageContextMenu";
import Media from "./media";
import { useStorage } from "@tensamin/storage/context";
import { useMTP } from "@tensamin/mtp";
import Input from "@tensamin/markdown/input";
import { useChat } from "../context";
import { encryptChatText } from "@tensamin/crypto/chatSecret";
import { log, toast } from "@tensamin/shared/log";
function MessageComponent({
grouped,
@ -138,6 +142,62 @@ function MessageComponent({
}
}, [message.Content]);
// Message editing
const { chatSecret, editMessageContent, userId } = useChat();
const [editing, setEditing] = useState(false);
const [editDraft, setEditDraft] = useState(message.Content);
useEffect(() => {
if (!editing) {
setEditDraft(message.Content);
}
}, [editing, message.Content]);
const editMessage = useCallback(
async (newContent: string) => {
if (!chatSecret) return;
const encryptedContent = await encryptChatText(
chatSecret,
newContent,
).catch((err) => {
toast("error", "Failed to encrypt edit", String(err));
});
if (!encryptedContent) return;
const previousContent = message.Content;
editMessageContent(message.SendTime, newContent);
try {
const response = await send("MessageEdit", {
ChatPartnerId: userId,
Content: encryptedContent,
SendTime: message.SendTime,
});
if (response.type.startsWith("Error")) {
throw new Error(response.type);
}
} catch (err) {
editMessageContent(
message.SendTime,
previousContent,
message.Edited ?? false,
);
log(1, "chat", "red", "Failed to edit message", err);
toast("error", "Failed to edit message", String(err));
}
},
[
chatSecret,
editMessageContent,
userId,
message.Content,
message.Edited,
message.SendTime,
send,
],
);
return (
<div
// pt-3 is to get a gap between messages
@ -147,6 +207,8 @@ function MessageComponent({
<MessageContextMenu
content={message.Content}
messageId={message.SendTime}
onSetEditing={setEditing}
senderId={message.SenderId}
>
<div
className={cn(
@ -221,7 +283,16 @@ function MessageComponent({
</div>
</div>
)}
{isValidURL ? (
{editing ? (
<Input
setValue={setEditDraft}
value={editDraft}
onSubmit={() => {
editMessage(editDraft);
setEditing(false);
}}
/>
) : isValidURL ? (
<Media link={message.Content} />
) : (
<Text value={message.Content} />

View file

@ -17,9 +17,10 @@ import {
useIsMobile,
} from "@tensamin/ui";
import { Pin, Clipboard, Pen, Reply, Forward, Trash } from "lucide-react";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import type { ReactElement, ReactNode } from "react";
import { useChat } from "../context";
import { useStorage } from "@tensamin/storage/context";
async function copyText(text: string) {
await navigator.clipboard.writeText(text);
@ -136,6 +137,8 @@ function MessageMenuContent({
messageId,
onAddReaction,
showReactionItems = true,
onSetEditing,
senderId,
}: {
components: MenuComponents;
content: string;
@ -143,12 +146,20 @@ function MessageMenuContent({
messageId: number;
onAddReaction?: () => void | Promise<void>;
showReactionItems?: boolean;
onSetEditing: (value: boolean) => void;
senderId: number;
}) {
const { Content, Group, Item, Separator, Sub, SubContent, SubTrigger } =
components;
const { load } = useStorage();
const { setReplyTo } = useChat();
const [ownId, setOwnId] = useState(0);
useEffect(() => {
load("user_id").then(setOwnId);
}, [load]);
return (
<Content>
<Group>
@ -172,9 +183,16 @@ function MessageMenuContent({
</Group>
<Separator />
<Group>
<Item disabled className="flex justify-between">
<p>Edit Message</p> <Pen />
</Item>
{senderId === ownId && (
<Item
className="flex justify-between"
onClick={() => {
onSetEditing(true);
}}
>
<p>Edit Message</p> <Pen />
</Item>
)}
<Item
className="flex justify-between"
onClick={() => {
@ -214,10 +232,14 @@ export default function MessageContextMenu({
children,
content,
messageId,
onSetEditing,
senderId,
}: {
children: ReactElement;
content: string;
messageId: number;
onSetEditing: (value: boolean) => void;
senderId: number;
}) {
const isMobile = useIsMobile();
const devEnabled = useMemo(
@ -257,6 +279,8 @@ export default function MessageContextMenu({
messageId={messageId}
onAddReaction={() => setReactionDrawerOpen(true)}
showReactionItems={false}
onSetEditing={onSetEditing}
senderId={senderId}
/>
</Drawer>
<Drawer open={reactionDrawerOpen} onOpenChange={setReactionDrawerOpen}>
@ -282,6 +306,8 @@ export default function MessageContextMenu({
content={content}
devEnabled={devEnabled}
messageId={messageId}
onSetEditing={onSetEditing}
senderId={senderId}
/>
</ContextMenu>
);