feat(phase2): bulk delete + audit log; update roadmap/parity docs
This commit is contained in:
@@ -114,9 +114,18 @@ export function ChatArea() {
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [mentionQuery, setMentionQuery] = useState<string | null>(null);
|
||||
const [slowmodeRemaining, setSlowmodeRemaining] = useState(0);
|
||||
const [selectMode, setSelectMode] = useState(false);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const toggleSelectedMessage = useMessageStore((s) => s.toggleSelectedMessage);
|
||||
const clearSelectedMessages = useMessageStore((s) => s.clearSelectedMessages);
|
||||
const bulkDeleteMessages = useMessageStore((s) => s.bulkDeleteMessages);
|
||||
const selectedIds = useMessageStore((s) =>
|
||||
activeChannelId ? s.selectedMessageIds[activeChannelId] || new Set<string>() : new Set<string>(),
|
||||
);
|
||||
const canBulkDelete = true; // ponytail: permission enforced server-side; server-side is the source of truth
|
||||
|
||||
const channels = activeServerId
|
||||
? channelsByServer[activeServerId] || []
|
||||
: [];
|
||||
@@ -241,13 +250,22 @@ export function ChatArea() {
|
||||
: "[NO CHANNEL SELECTED]"}
|
||||
</span>
|
||||
{activeChannelId && (
|
||||
<button
|
||||
onClick={() => setShowSearch((prev) => !prev)}
|
||||
className="text-xs text-gb-fg-f hover:text-gb-orange font-mono"
|
||||
title="Search messages"
|
||||
>
|
||||
[SEARCH]
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setSelectMode((prev) => !prev)}
|
||||
className={`text-xs font-mono ${selectMode ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
|
||||
title="Toggle bulk delete selection"
|
||||
>
|
||||
[SELECT]
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowSearch((prev) => !prev)}
|
||||
className="text-xs text-gb-fg-f hover:text-gb-orange font-mono"
|
||||
title="Search messages"
|
||||
>
|
||||
[SEARCH]
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showSearch && activeChannelId && activeChannel && (
|
||||
@@ -257,13 +275,50 @@ export function ChatArea() {
|
||||
onClose={() => setShowSearch(false)}
|
||||
/>
|
||||
)}
|
||||
{selectMode && activeChannelId && (
|
||||
<div className="px-3 py-1 text-xs font-mono text-gb-fg-f flex items-center justify-between bg-gb-bg-s border-b border-gb-bg-t">
|
||||
<span>{selectedIds.size} selected</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => clearSelectedMessages(activeChannelId)}
|
||||
className="text-gb-fg-f hover:text-gb-orange"
|
||||
>
|
||||
[CLEAR]
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!activeChannelId || selectedIds.size === 0) return;
|
||||
if (!confirm(`Delete ${selectedIds.size} messages?`)) return;
|
||||
try {
|
||||
await bulkDeleteMessages(activeChannelId, Array.from(selectedIds));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Bulk delete failed');
|
||||
}
|
||||
}}
|
||||
disabled={selectedIds.size === 0 || !canBulkDelete}
|
||||
className="text-gb-red hover:text-gb-orange disabled:opacity-50"
|
||||
>
|
||||
[DELETE]
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-y-auto p-3 space-y-1 font-mono text-sm">
|
||||
{isLoading && <p className="text-gb-fg-f">[loading...]</p>}
|
||||
{!isLoading && messages.length === 0 && (
|
||||
<p className="text-gb-fg-f">[no messages in this channel]</p>
|
||||
)}
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} className="break-words">
|
||||
<div
|
||||
key={message.id}
|
||||
onClick={() => selectMode && activeChannelId && toggleSelectedMessage(activeChannelId, message.id)}
|
||||
className={`break-words ${selectMode ? 'cursor-pointer hover:bg-gb-bg-s' : ''} ${selectedIds.has(message.id) ? 'bg-gb-bg-s' : ''}`}
|
||||
>
|
||||
{selectMode && activeChannelId && (
|
||||
<span className="text-gb-fg-f mr-2">
|
||||
{selectedIds.has(message.id) ? '[x]' : '[ ]'}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-gb-fg-f">
|
||||
[{formatTime(message.created_at)}]
|
||||
</span>{" "}
|
||||
|
||||
Reference in New Issue
Block a user