修复全页面走查发现的问题(第三批:转发、i18n、自聊天显示名)

第4章 批量转发
- 确认弹层里那栏留言是个只读 <div>,需求 4.19 与对接文档要求的"附加留言"根本没法填,
  comment 永远是空串。改成真实输入框(方括号那段是占位文案不是内容),并补 input 的样式。
- 无条数上限:服务端硬卡 sources ≤ 100(im_http.go:3560),超了直接 400 MALFORMED_REQUEST,
  用户只看到一句无从下手的"转发失败"。本地先拦并说清超了多少。
- 无目标数上限:bootstrap 下发 limits.max_forward_targets=9、服务端硬限 20,前端两个都不读。
  读各视图已在写的同一份 bootstrap 缓存,不额外发请求;选择视图也补上错误显示。

i18n(这几条影响真实页面,不只是演示页)
- menu_item_forward "重定向" → "转发",forward_to "重定向消息" → "转发给"。Forward 被当网络
  术语直译了,第4章通篇和参考图用的都是"转发"。
- last_seen_timestamp 中英两侧都丢了 {timestamp} 占位符,离线时头部只显示"最后可见"没有时间。
- peers_messaging_disabled "成员间消息已禁用" → "对方已关闭消息"(原文是 Peer's)。
- search_no_results "搜索返回任何结果" 缺"无";icon_title_send "发信息" → "发送消息"。
- 已跑 build:i18n 同步 src/i18n.min/(那份才是打包进产物的)。

自聊天显示名
- topicDisplayName 不处理 slf,消息首页把"已保存的消息"显示成"未命名会话"。该显示名靠
  Tinode.isSelfTopicName 特判得出、不在 public 里。集中到这个共享函数,所有调用点都受益。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-03 18:45:13 +08:00
co-authored by Claude Opus 5
parent 02b98cceae
commit c9b8125013
11 changed files with 146 additions and 51 deletions
+43 -6
View File
@@ -12,6 +12,11 @@ import SearchContacts from './search-contacts.jsx';
import { forwardMessages } from '../lib/im-api.js';
import { topicDisplayName } from '../lib/utils.js';
import { readCachedData } from '../lib/use-cached-data.js';
// 与服务端 im_http.go:3560 的硬限一致。目标数另有一层更严的产品限制,见 maxTargets()。
const MAX_FORWARD_SOURCES = 100;
const MAX_FORWARD_TARGETS = 20;
export default class BatchForwardDialog extends React.Component {
constructor(props) {
@@ -22,6 +27,7 @@ export default class BatchForwardDialog extends React.Component {
multiSelect: false,
selectedTargets: [],
confirmTargets: null,
comment: '',
sending: false,
error: null
};
@@ -34,6 +40,7 @@ export default class BatchForwardDialog extends React.Component {
this.handleConfirmTargets = this.handleConfirmTargets.bind(this);
this.handleCancelConfirm = this.handleCancelConfirm.bind(this);
this.handleSend = this.handleSend.bind(this);
this.handleCommentChange = this.handleCommentChange.bind(this);
}
componentDidMount() {
@@ -73,10 +80,25 @@ export default class BatchForwardDialog extends React.Component {
this.setState({confirmTargets: [uid]});
return;
}
this.setState(s => ({
selectedTargets: s.selectedTargets.includes(uid) ?
s.selectedTargets.filter(t => t != uid) : s.selectedTargets.concat(uid)
}));
this.setState(s => {
if (s.selectedTargets.includes(uid)) {
return {selectedTargets: s.selectedTargets.filter(t => t != uid), error: null};
}
// 目标数上限:优先用 bootstrap 下发的 limits.max_forward_targets(服务端当前给 9),
// 拿不到就退到服务端硬限 20(im_http.go:3560)。原来两个都不读,选到 21 个才会撞 400。
const max = this.forwardTargetLimit();
if (s.selectedTargets.length >= max) {
return {error: `最多同时转发给 ${max} 个会话`};
}
return {selectedTargets: s.selectedTargets.concat(uid), error: null};
});
}
forwardTargetLimit() {
// 读各视图已经在写的同一份 bootstrap 缓存('bootstrap:'+myUserId),不额外发请求。
const boot = this.props.myUserId ? readCachedData('bootstrap:' + this.props.myUserId) : null;
const configured = boot && boot.limits && Number(boot.limits.max_forward_targets);
return configured > 0 ? Math.min(configured, MAX_FORWARD_TARGETS) : MAX_FORWARD_TARGETS;
}
handleConfirmTargets(e) {
@@ -90,11 +112,21 @@ export default class BatchForwardDialog extends React.Component {
this.setState({confirmTargets: null, error: null});
}
handleCommentChange(e) {
this.setState({comment: e.target.value});
}
handleSend() {
// seq must be a JSON string (server struct tag is `json:"seq,string"`), same as favoriteMessages.
const sources = this.props.seqs.map(seq => ({topic: this.props.topicName, seq: String(seq)}));
// 服务端硬卡 sources ≤ 100、target_topics ≤ 20(im_http.go:3560),超了直接 400 MALFORMED_REQUEST,
// 用户只会看到一句无从下手的"转发失败"。在本地先拦住并说清楚超了多少。
if (sources.length > MAX_FORWARD_SOURCES) {
this.setState({error: `一次最多转发 ${MAX_FORWARD_SOURCES} 条消息,当前已选 ${sources.length} 条`});
return;
}
this.setState({sending: true, error: null});
forwardMessages('separate', sources, this.state.confirmTargets).then(result => {
forwardMessages('separate', sources, this.state.confirmTargets, this.state.comment.trim()).then(result => {
const failed = (result && result.failed) || [];
if (failed.length) {
this.setState({sending: false, error: `${failed.length} 条转发失败,请重试`});
@@ -119,7 +151,11 @@ export default class BatchForwardDialog extends React.Component {
<i className="material-icons">chevron_right</i>
</li>)}
</ul>
<div className="batch-forward-comment">{'[逐条转发]共' + this.props.seqs.length + '条消息'}</div>
{/* 参考图这一栏是可输入的留言框,原来是个只读 <div>,需求 4.19 与对接文档要求的
"附加留言"根本没法填,comment 永远是空串。方括号那段是占位文案,不是内容。 */}
<input className="batch-forward-comment" type="text" value={this.state.comment}
onChange={this.handleCommentChange} disabled={this.state.sending}
placeholder={'[逐条转发]共' + this.props.seqs.length + '条消息'} />
{this.state.error ? <div className="auth-field-error">{this.state.error}</div> : null}
<div className="batch-forward-actions">
<button type="button" onClick={this.handleCancelConfirm} disabled={this.state.sending}>取消</button>
@@ -149,6 +185,7 @@ export default class BatchForwardDialog extends React.Component {
</div>
</div>
<SearchContacts placeholder="搜索联系人" onSearchContacts={this.handleSearchContacts} />
{this.state.error ? <div className="auth-field-error">{this.state.error}</div> : null}
<ContactList
tinode={this.props.tinode}
contacts={contacts}