feat(core): clear saved chat and add message deletion scopes
Some checks failed
CI / test (push) Failing after 26s
Some checks failed
CI / test (push) Failing after 26s
backend:
- add message_hidden table for per-user message hiding
- support DELETE /messages/{id}?for_all=true|false
- implement delete-for-me vs delete-for-all logic by chat type/permissions
- add POST /chats/{chat_id}/clear and route saved chat deletion to clear
web:
- saved messages action changed from delete to clear
- message context menu now supports delete modal: for me / for everyone
- add local store helpers removeMessage/clearChatMessages
- include realtime stability improvements and app error boundary
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { deleteChat } from "../api/chats";
|
||||
import { clearChat, deleteChat } from "../api/chats";
|
||||
import { updateMyProfile } from "../api/users";
|
||||
import { useAuthStore } from "../store/authStore";
|
||||
import { useChatStore } from "../store/chatStore";
|
||||
@@ -27,7 +27,11 @@ export function ChatList() {
|
||||
const [profileError, setProfileError] = useState<string | null>(null);
|
||||
const [profileSaving, setProfileSaving] = useState(false);
|
||||
const deleteModalChat = chats.find((chat) => chat.id === deleteModalChatId) ?? null;
|
||||
const canDeleteForEveryone = Boolean(deleteModalChat && !deleteModalChat.is_saved && deleteModalChat.type === "group");
|
||||
const canDeleteForEveryone = Boolean(
|
||||
deleteModalChat &&
|
||||
!deleteModalChat.is_saved &&
|
||||
(deleteModalChat.type === "group" || deleteModalChat.type === "private")
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -36,6 +40,20 @@ export function ChatList() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [search, loadChats]);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
setCtxChatId(null);
|
||||
setCtxPos(null);
|
||||
setDeleteModalChatId(null);
|
||||
setProfileOpen(false);
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!me) {
|
||||
return;
|
||||
@@ -141,7 +159,7 @@ export function ChatList() {
|
||||
setDeleteForAll(false);
|
||||
}}
|
||||
>
|
||||
Delete chat
|
||||
{chats.find((c) => c.id === ctxChatId)?.is_saved ? "Clear chat" : "Delete chat"}
|
||||
</button>
|
||||
</div>,
|
||||
document.body
|
||||
@@ -151,10 +169,15 @@ export function ChatList() {
|
||||
{deleteModalChatId ? (
|
||||
<div className="absolute inset-0 z-50 flex items-end justify-center bg-slate-950/55 p-3">
|
||||
<div className="w-full rounded-xl border border-slate-700/80 bg-slate-900 p-3">
|
||||
<p className="mb-2 text-sm font-semibold">Delete chat: {deleteModalChat ? chatLabel(deleteModalChat) : "selected chat"}</p>
|
||||
<p className="mb-2 text-sm font-semibold">
|
||||
{deleteModalChat?.is_saved ? "Clear chat" : "Delete chat"}: {deleteModalChat ? chatLabel(deleteModalChat) : "selected chat"}
|
||||
</p>
|
||||
{deleteModalChat?.type === "channel" ? (
|
||||
<p className="mb-3 text-xs text-slate-400">Channels are removed for all subscribers.</p>
|
||||
) : null}
|
||||
{deleteModalChat?.is_saved ? (
|
||||
<p className="mb-3 text-xs text-slate-400">This will clear all messages in Saved Messages.</p>
|
||||
) : null}
|
||||
{canDeleteForEveryone ? (
|
||||
<label className="mb-3 flex items-center gap-2 text-sm">
|
||||
<input checked={deleteForAll} onChange={(e) => setDeleteForAll(e.target.checked)} type="checkbox" />
|
||||
@@ -165,6 +188,12 @@ export function ChatList() {
|
||||
<button
|
||||
className="flex-1 rounded bg-red-500 px-3 py-2 text-sm font-semibold text-white"
|
||||
onClick={async () => {
|
||||
if (deleteModalChat?.is_saved) {
|
||||
await clearChat(deleteModalChatId);
|
||||
useChatStore.getState().clearChatMessages(deleteModalChatId);
|
||||
setDeleteModalChatId(null);
|
||||
return;
|
||||
}
|
||||
await deleteChat(deleteModalChatId, canDeleteForEveryone && deleteForAll);
|
||||
await loadChats(search.trim() ? search : undefined);
|
||||
if (activeChatId === deleteModalChatId) {
|
||||
@@ -173,7 +202,7 @@ export function ChatList() {
|
||||
setDeleteModalChatId(null);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
{deleteModalChat?.is_saved ? "Clear" : "Delete"}
|
||||
</button>
|
||||
<button className="flex-1 rounded bg-slate-700 px-3 py-2 text-sm" onClick={() => setDeleteModalChatId(null)}>
|
||||
Cancel
|
||||
|
||||
Reference in New Issue
Block a user