修复全页面走查发现的问题(第三批:转发、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:
co-authored by
Claude Opus 5
parent
02b98cceae
commit
c9b8125013
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -614,7 +614,7 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"last_seen_timestamp": {
|
||||
"translation": "Last seen",
|
||||
"translation": "Last seen: {timestamp}",
|
||||
"defaultMessage": "Last seen: {timestamp}",
|
||||
"description": "Label for the timestamp of when the user or topic was last online",
|
||||
"missing": false,
|
||||
|
||||
+6
-6
@@ -614,7 +614,7 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"last_seen_timestamp": {
|
||||
"translation": "最后可见",
|
||||
"translation": "最后在线:{timestamp}",
|
||||
"defaultMessage": "Last seen: {timestamp}",
|
||||
"description": "Label for the timestamp of when the user or topic was last online",
|
||||
"missing": false,
|
||||
@@ -852,7 +852,7 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"peers_messaging_disabled": {
|
||||
"translation": "成员间消息已禁用。",
|
||||
"translation": "对方已关闭消息。",
|
||||
"defaultMessage": "Peer's messaging is disabled.",
|
||||
"description": "Shown when the p2p peer's messaging is disabled",
|
||||
"missing": false,
|
||||
@@ -985,7 +985,7 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"search_no_results": {
|
||||
"translation": "搜索返回任何结果",
|
||||
"translation": "搜索无结果",
|
||||
"defaultMessage": "Search returned no results",
|
||||
"description": "Text shown in contacts view when query returned no results.",
|
||||
"missing": false,
|
||||
@@ -1314,14 +1314,14 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"menu_item_forward": {
|
||||
"translation": "重定向",
|
||||
"translation": "转发",
|
||||
"defaultMessage": "Forward",
|
||||
"description": "Forward message",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"forward_to": {
|
||||
"translation": "重定向消息",
|
||||
"translation": "转发给",
|
||||
"defaultMessage": "Forward to",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1495,7 +1495,7 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"icon_title_send": {
|
||||
"translation": "发信息",
|
||||
"translation": "发送消息",
|
||||
"defaultMessage": "Send message",
|
||||
"description": "Icon tool tip for sending a message",
|
||||
"missing": false,
|
||||
|
||||
@@ -15,6 +15,10 @@ export function topicDisplayName(topicName, publicData, fallback) {
|
||||
const fn = publicData && publicData.fn;
|
||||
if (fn) return fn;
|
||||
if (publicData && Tinode.isGroupTopicName(topicName) && publicData.name) return publicData.name;
|
||||
// 自聊天(slf)没有 public,原来会一路落到 fallback —— 消息首页因此把它显示成"未命名会话"。
|
||||
// 它的显示名靠 Tinode.isSelfTopicName 特判得出,不在 public 里。这里集中处理,所有调用点都受益;
|
||||
// 已经自己判过的地方(batch-forward-dialog 等)在调用前就短路了,不受影响。
|
||||
if (Tinode.isSelfTopicName(topicName)) return '已保存的消息';
|
||||
return fallback;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user