From d5440b962bd409f95948dd93fe8351fea6b3c8c7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 8 Sep 2026 02:51:29 +0800 Subject: [PATCH] feat(web): bulk forward/favorite in multi-select mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rounds out the multi-select bottom bar (README §3.6) with 转发/收藏 alongside the existing 删除 -- reuses the single-message ForwardPickerModal/ favoriteMessages/forwardMessage machinery from the context-menu actions, just applied to the whole selected set. One "转发" button (not separate 逐条/合并 buttons) matching the old app's real dialog-internal mode toggle, which this app's simplified single-target picker doesn't model -- already noted as a scope cut in ForwardPickerModal's own docstring. Verified live: entering multi-select via a message's context menu shows 转发/收藏/删除, and bulk-favorite POSTs a real /v1/im/favorites/batch request. Co-Authored-By: Claude Sonnet 5 --- .../features/messages/ConversationView.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/web/src/features/messages/ConversationView.tsx b/web/src/features/messages/ConversationView.tsx index d3534dc9..4b7dd8b7 100644 --- a/web/src/features/messages/ConversationView.tsx +++ b/web/src/features/messages/ConversationView.tsx @@ -47,6 +47,7 @@ export function ConversationView() { const [selectedSeqs, setSelectedSeqs] = useState>(new Set()); const [loadingOlder, setLoadingOlder] = useState(false); const [recording, setRecording] = useState(false); + const [bulkForwardOpen, setBulkForwardOpen] = useState(false); const [replyTo, setReplyTo] = useState(null); const [forwardMsg, setForwardMsg] = useState(null); const [emojiOpen, setEmojiOpen] = useState(false); @@ -161,6 +162,22 @@ export function ConversationView() { exitSelectMode(); }; + const bulkFavorite = () => { + if (selectedSeqs.size === 0) return; + favoriteMessages([...selectedSeqs].map((seq) => ({ topic: topicId, seq }))).catch(() => {}); + exitSelectMode(); + }; + + const bulkForward = (targetTopic: string) => { + const target = tinode.getTopic(targetTopic); + for (const seq of selectedSeqs) { + const msg = topic.findMessage(seq); + if (msg) forwardMessage(topicId, msg, target); + } + setBulkForwardOpen(false); + exitSelectMode(); + }; + const handleAction = (action: MessageAction) => { const msg = menu?.msg; setMenu(null); @@ -275,6 +292,24 @@ export function ConversationView() { {selectMode ? (
+ +
); }