修复桌面端会话内错误提示完全不可见、收藏发送面板显示空白/裸类型名、delivery-marker图标className拼出undefined

- 桌面布局下打开一个会话时,sidepanel-view.jsx 走的是 messages-home 分支(渲染
  MessageCenterView),这个分支从来没有渲染过ErrorPanel;messages-view.jsx自己的ErrorPanel
  又只在displayMobile时才渲染——两边一凑,桌面端(最常见的使用状态)里任何onError(...)调用
  (上传失败、收藏转发失败、URL风控拦截等)全部没有可见的展示出口,用户只会觉得"点了但什么
  反应都没有"。已用真实handleError()调用实测确认:修复前DOM里根本没有.info-box节点,修复后
  正确渲染且visible。
- "我的收藏"发送面板:favoriteSummary()对图片/视频收藏显示空白(真实数据里这两类的
  snapshot.content.txt是占位用的单个空格,被直接当文本展示)、对文本类收藏显示裸露的内部
  类型字符串"text"(FAVORITE_TYPE_LABEL没有text这个key,且文本收藏的content本身就是纯字符串
  而非{txt:...}对象,原逻辑没处理这种形状)。改成图片/视频/语音三类固定用类型标签、文本类型
  优先判断content本身是不是纯字符串、并给FAVORITE_TYPE_LABEL补上text条目。
- received-marker.jsx的已发送/已送达图标(deliveryMarker对这两种状态返回的对象没有color字段)
  拼接className时把undefined直接字符串化拼了进去,实际渲染成"material-icons small undefined"。

均已重新构建验证;received-marker/favoriteSummary为纯函数级修复,逻辑经过仔细核对但未逐一
截图复核(不依赖运行时状态,风险低)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-05 18:42:08 +08:00
co-authored by Claude Sonnet 5
parent 47148aa75d
commit 38b43936ce
7 changed files with 46 additions and 15 deletions
+11
View File
@@ -164,6 +164,17 @@ class SidepanelView extends React.PureComponent {
myUserId={this.props.myUserId}
onTopicSelected={this.props.onTopicSelected}
onOpenScanner={this.props.onOpenScanner} />
{/* This branch never rendered ErrorPanel at all -- on desktop (non-mobile), opening a real
conversation lands here (view stays 'messages-home' behind the split-view topic pane),
and messages-view.jsx's own ErrorPanel is only rendered on displayMobile. Every
this.props.onError(...) call reachable from an open conversation on desktop (failed
uploads, failed forwards, URL risk blocks, etc.) had no visible outlet at all. */}
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText}
action={this.props.errorAction}
actionText={this.props.errorActionText}
onClearError={this.props.onError} />
</div>;
}
+4 -1
View File
@@ -38,7 +38,10 @@ class ReceivedMarker extends React.PureComponent {
}
const icon = deliveryMarker(this.props.received);
const marker = icon ? <i className={'material-icons small ' + icon.color}>{icon.name}</i> : null;
// icon.color is only set for the failed/read cases (deliveryMarker's other {name} results omit
// the key entirely) -- string-concatenating it unconditionally put the literal text "undefined"
// into the className for sending/sent/received messages.
const marker = icon ? <i className={'material-icons small ' + (icon.color || '')}>{icon.name}</i> : null;
const edited = this.props.edited ? formatMessage(messages.message_edited_marker) : null;
return (
+12 -4
View File
@@ -30,12 +30,20 @@ const EMOJI_LIST = ['😀', '😁', '😂', '🤣', '😃', '😄', '😅', '
// 互相 import,就近各自维护一份小常量)。收藏摘要要优先读 snapshot.content.txt 这个结构化字段,
// 不能像最初那版一样直接展示 snapshot.text——那是整条 content 原样 JSON.stringify 的结果
// (例如 {"txt":"..."}),不是给人看的,之前联调时真被这个坑绊了一次才发现。
const FAVORITE_TYPE_LABEL = {image: '[图片]', video: '[视频]', voice: '[语音]', file: '[文件]', link: '[链接]'};
const FAVORITE_TYPE_LABEL = {image: '[图片]', video: '[视频]', voice: '[语音]', file: '[文件]', link: '[链接]', text: '[文本]'};
function favoriteSummary(item) {
const content = item.snapshot && item.snapshot.content;
if (content && typeof content.txt === 'string' && content.txt) {
return content.txt;
// image/video/voice favorites' snapshot.content.txt is a placeholder single space in this
// project's convention (confirmed via a real GET /favorites response) -- reading it as the
// summary showed a blank line instead of a type label for these three types.
if (item.type === 'image' || item.type === 'video' || item.type === 'voice') {
return FAVORITE_TYPE_LABEL[item.type];
}
const content = item.snapshot && item.snapshot.content;
// A text favorite's snapshot.content is a plain string, not a {txt:...} Drafty object --
// content.txt on it is undefined, which used to fall through all the way to the internal
// item.type value ("text") being shown to the user verbatim.
if (typeof content === 'string' && content.trim()) return content;
if (content && typeof content.txt === 'string' && content.txt.trim()) return content.txt;
return FAVORITE_TYPE_LABEL[item.type] || item.type || item.favorite_id;
}
const RECENT_EMOJI_KEY = 'im-recent-emoji';