修复codereview复核确认的多选/转发/收藏页/资料页等前端bug
- 多选模式下Drafty链接、名片消息不再意外触发跳转/开新标签页 - 语音/视频消息在多选模式下点击可正常冒泡选中该行 - 转发排除未确认/发送失败消息,识别uncertain转发结果并单独提示 - 群文件"更多操作"菜单竞态、群成员移出状态竞态 - 收藏页链接卡片new URL()崩溃点加防御,并给登录后应用区域加错误边界 - 个人资料页乐观锁版本冲突时清空草稿避免静默覆盖并发修改 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e3eed8bdd4
commit
9abae5030b
@@ -23,7 +23,7 @@ import { getConversationContext, getUserProfile } from '@/lib/api/friends';
|
||||
import { getMe } from '@/lib/api/profile';
|
||||
import { FETCH_PAGE_TRIGGER, MESSAGES_PAGE } from '@/lib/config';
|
||||
import { useClickOutside } from '@/lib/hooks';
|
||||
import { availableActions, recallRemainingSeconds, type MessageAction } from '@/lib/messages/actions';
|
||||
import { availableActions, isConfirmedSeq, recallRemainingSeconds, type MessageAction } from '@/lib/messages/actions';
|
||||
import { flatTextContentOf, isRecalled } from '@/lib/messages/dispatch';
|
||||
import { buildMentionedContent, detectMentionTrigger, type PendingMention } from '@/lib/messages/mentions';
|
||||
import { buildReplyInfo, replySnapshotText } from '@/lib/messages/reply';
|
||||
@@ -821,10 +821,12 @@ export function ConversationView() {
|
||||
// A message can be recalled by its sender AFTER it was already selected (the checkbox itself
|
||||
// refuses a NEW selection of a recalled message, but can't retroactively un-select one recalled
|
||||
// mid-session) -- forwarding null content would silently send an empty bubble with a misleading
|
||||
// "forwarded from" tag rather than the message the user actually picked.
|
||||
// "forwarded from" tag rather than the message the user actually picked. Checkbox selection also
|
||||
// doesn't refuse a still-sending/failed message (its placeholder seq is a plain `number`, same as
|
||||
// a real one) -- that message has no server-side (topic, seq) yet for forwardMessages() to find.
|
||||
const sources = orderedSeqs
|
||||
.map((seq) => topic.findMessage(seq))
|
||||
.filter((msg): msg is StoredMessage => !!msg && !isRecalled(msg))
|
||||
.filter((msg): msg is StoredMessage => !!msg && !isRecalled(msg) && isConfirmedSeq(msg))
|
||||
.map((msg) => ({ topic: topicId, seq: msg.seq as number }));
|
||||
// One REST call now covers every (source, target) pair -- forwardMessages()'s own comment
|
||||
// explains why this replaced per-target `ensureTopicAttached()` + per-message client-side
|
||||
@@ -848,6 +850,11 @@ export function ConversationView() {
|
||||
? forwardMessages(sources, targetTopics)
|
||||
.then((res) => {
|
||||
if (res.failed.length > 0) showError(undefined, `${res.failed.length} 条消息转发失败`);
|
||||
// 'sent'/'pending' both mean the server has it; 'uncertain' (e.g. a timeout mid-write)
|
||||
// does not -- silently lumping it in with success would leave the user with no signal
|
||||
// that this particular copy may never actually land in the target conversation.
|
||||
const uncertain = res.items.filter((item) => item.status === 'uncertain').length;
|
||||
if (uncertain > 0) notify(`${uncertain} 条转发结果暂不确定,请稍后在目标会话中确认`);
|
||||
})
|
||||
.catch((err) => showError(err, '转发失败'))
|
||||
: Promise.resolve();
|
||||
@@ -1247,10 +1254,12 @@ export function ConversationView() {
|
||||
// "pending" persistence, landing the comment BEFORE the forwarded content it's meant to
|
||||
// follow).
|
||||
const forwardDone =
|
||||
typeof forwardMsg.seq === 'number'
|
||||
? forwardMessages([{ topic: topicId, seq: forwardMsg.seq }], targetTopics)
|
||||
isConfirmedSeq(forwardMsg)
|
||||
? forwardMessages([{ topic: topicId, seq: forwardMsg.seq as number }], targetTopics)
|
||||
.then((res) => {
|
||||
if (res.failed.length > 0) showError(undefined, `${res.failed.length} 个目标转发失败`);
|
||||
const uncertain = res.items.filter((item) => item.status === 'uncertain').length;
|
||||
if (uncertain > 0) notify(`${uncertain} 个目标转发结果暂不确定,请稍后在目标会话中确认`);
|
||||
})
|
||||
.catch((err) => showError(err, '转发失败'))
|
||||
: Promise.resolve();
|
||||
|
||||
Reference in New Issue
Block a user