排查转发bug同类问题:修复查找聊天记录崩溃 + 复制功能复制出"undefined"

审查了所有"读取消息content做数据处理"的地方,找到两处和转发bug同一根因
(手机端扁平非Drafty内容格式)的真实问题:

1. 查找聊天记录(HistorySearchModal)的搜索结果渲染,对手机端发送的图片/文件/
   语音/视频/名片消息命中关键词时会让整个应用崩溃——Drafty.toPlainText在这
   类扁平内容上返回undefined,highlightMatches对undefined调用.toLowerCase()
   直接抛异常,而这个应用完全没有错误边界(ErrorBoundary)兜底,等于白屏。
   已用真实搜索接口返回确认snapshot里其实带着head字段(此前SearchHit类型
   没建模),据此在渲染前识别这几种扁平内容并显示[图片]/[文件]/[语音]/[名片]
   这样的占位文案,不再无脑喂给renderDrafty;顺手给highlightMatches补了防御
   性判空,避免类似疏漏再次崩溃整个应用。

2. 消息右键菜单"复制"功能,对手机端发送的扁平文本消息(如群公告自动发布的
   那条)和图片/文件/语音/视频消息,复制到剪贴板的是字面字符串"undefined"。
   文本类修成正确提取真实文本;附件类直接从"复制"这个操作里去掉(参考名片/
   通话记录消息已有的排除逻辑),复制一段JSON式的附件数据本来就没有意义。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-25 00:06:10 +08:00
co-authored by Claude Sonnet 5
parent eb0a6eba7e
commit 105ff76959
5 changed files with 60 additions and 6 deletions
+10 -3
View File
@@ -24,7 +24,7 @@ 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 { isRecalled } from '@/lib/messages/dispatch';
import { flatTextContentOf, isRecalled } from '@/lib/messages/dispatch';
import { buildMentionedContent, detectMentionTrigger, type PendingMention } from '@/lib/messages/mentions';
import { buildReplyInfo, replySnapshotText } from '@/lib/messages/reply';
import { groupSenderLabel } from '@/lib/messages/sender-name';
@@ -846,9 +846,16 @@ export function ConversationView() {
setMenu(null);
if (!msg || typeof msg.seq !== 'number') return;
switch (action) {
case 'copy':
navigator.clipboard.writeText(Drafty.toPlainText(msg.content ?? '')).catch(() => {});
case 'copy': {
// Real, live-confirmed bug: a flat non-Drafty text message (e.g. the group-announcement
// auto-post, `{text:"..."}` not `{txt:"..."}`) isn't excluded from 'copy' by availableActions
// (it reads fine in the bubble via renderDrafty's own flatTextContentOf fallback -- only this
// handler's `Drafty.toPlainText` didn't know about it), so "复制" silently put the literal
// string "undefined" on the clipboard instead of the text visibly shown on screen.
const flatText = flatTextContentOf(msg.content);
navigator.clipboard.writeText(flatText ?? Drafty.toPlainText(msg.content ?? '')).catch(() => {});
break;
}
case 'recall':
recallMessage(topicId, msg.seq)
.then(() => topic.delMessagesList([msg.seq], true))