restore/undo for deleted messages — backend + UI
- POST /api/channels/{id}/messages/{msg_id}/restore — undeletes a message
- Any user can restore (not just the deleter)
- Broadcasts restored message via WebSocket
- UI: "undo" button in floating pill on deleted messages
- API: restoreMessage() in api.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { Channel } from "@/types/Channel";
|
||||
import type { Message } from "@/types/Message";
|
||||
import { getChannels, getMessages, getCurrentUsername, deleteMessage } from "@/api";
|
||||
import { getChannels, getMessages, getCurrentUsername, deleteMessage, restoreMessage } from "@/api";
|
||||
import { ChannelSidebar } from "@/components/ChannelSidebar";
|
||||
import { MessageItem } from "@/components/MessageItem";
|
||||
import { ComposeBox } from "@/components/ComposeBox";
|
||||
@@ -251,6 +251,14 @@ export default function App() {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
onRestore={async (chId, msgId) => {
|
||||
try {
|
||||
await restoreMessage(chId, msgId);
|
||||
loadMessages();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -85,3 +85,14 @@ export async function deleteMessage(
|
||||
);
|
||||
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
|
||||
}
|
||||
|
||||
export async function restoreMessage(
|
||||
channelId: string,
|
||||
msgId: string,
|
||||
): Promise<Message> {
|
||||
return json(
|
||||
await fetch(`${BASE}/channels/${channelId}/messages/${msgId}/restore`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ interface Props {
|
||||
replyTarget?: Message;
|
||||
onSelect: (id: string) => void;
|
||||
onDelete: (channelId: string, msgId: string) => void;
|
||||
onRestore: (channelId: string, msgId: string) => void;
|
||||
currentUsername: string;
|
||||
selected: boolean;
|
||||
}
|
||||
@@ -72,7 +73,7 @@ function userHue(username: string): number {
|
||||
return Math.abs(hash) % 360;
|
||||
}
|
||||
|
||||
export function MessageItem({ message, compact, replyTarget, onSelect, onDelete, currentUsername, selected }: Props) {
|
||||
export function MessageItem({ message, compact, replyTarget, onSelect, onDelete, onRestore, currentUsername, selected }: Props) {
|
||||
const [metaOpen, setMetaOpen] = useState(false);
|
||||
const isAgent = message.user.role === "agent";
|
||||
const isDeleted = !!message.deleted_at;
|
||||
@@ -193,6 +194,16 @@ export function MessageItem({ message, compact, replyTarget, onSelect, onDelete,
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
{isDeleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); onRestore(message.channel_id, message.id); }}
|
||||
className="px-2.5 py-1.5 text-[10px] font-mono text-muted-foreground hover:text-primary hover:bg-muted/50 transition-colors border-l-2 border-border"
|
||||
title="Restore deleted message"
|
||||
>
|
||||
undo
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
Reference in New Issue
Block a user