第五轮深度测试:管理员撤回、所有人@提醒、失效附件UI,并核对现存后端问题清单
- messages-view.jsx: 拖拽发送/回复菜单在编辑态下的旁路修复;群主/管理员可撤回他人消息 (对齐后端 authorizeIMMessageMutation 的 allowAdmin);新增"所有人"@提醒(head.mention_all)。 - chat-message.jsx: system_action 头类型消息按 system_notice 同款渲染(此前显示"内容无效")。 - content-library-view.jsx: 语音/链接/图文收藏与聊天记录卡片补上 removed(已失效)态UI, 之前只有 FileCard 处理了这个状态。 - call-panel.jsx: handleVideoOfferMsg(5步.then链,用reject sentinel跳过后续步骤)、 handleToggleSpeakerClick 补组件卸载守卫,修完此前遗漏的两处。 - group-management-view.jsx: 治理开关(Governance.setField)和群主转让(OwnerSelect) 补上乐观锁 version 参数(此前从不传,服务端 version<=0 时会跳过冲突检测);群文件搜索框 文案承诺"搜索文件名或者上传者"但服务端q参数只匹配文件名,改为拉取全量列表本地按文件名+ 已解析的上传者昵称一起过滤;群公告详情页补上从未被调用过的已读上报(readGroupAnnouncement)。 以上均用真实账号在浏览器/直接请求验证:管理员撤回、system_action渲染、mention_all、 文件按上传者搜索、公告已读上报(200)、治理开关stale-version探测确认409冲突检测生效。 7个并行测试agent对全部11章做了第二轮回归+现存后端问题清单逐条复核,结论待整理进 docs/IM-后端问题清单*.md(全部现存条目复核后均建议保留,多条证据强度从推断升级为已验证)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4a30cfa6b3
commit
d42591284f
@@ -140,6 +140,11 @@ function isPeerRestricted(acs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Synthetic "所有人" entry in the @提醒 picker (mentionSearch below) -- not a real user_id, just a
|
||||
// marker mentionSelected/sendMessage recognize to set head.mention_all instead of resolving a
|
||||
// Drafty MN entity to it. Never sent to the server as an actual id.
|
||||
const MENTION_ALL_UID = '__mention_all__';
|
||||
|
||||
function shouldPresentCallPanel(callState) {
|
||||
// Show call panel if either:
|
||||
// - call is outgoing (and the client is waiting for the other side to pick up) or,
|
||||
@@ -158,6 +163,7 @@ class MessagesView extends React.Component {
|
||||
this.state.toast = null;
|
||||
this.state.riskDialog = null;
|
||||
this.mentionedUsers = [];
|
||||
this.mentionAllPending = false;
|
||||
|
||||
this.componentSetup = this.componentSetup.bind(this);
|
||||
this.leave = this.leave.bind(this);
|
||||
@@ -370,6 +376,7 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
|
||||
this.mentionedUsers = [];
|
||||
this.mentionAllPending = false;
|
||||
this.loadRelationshipContext(topic);
|
||||
this.loadGroupContext(topic);
|
||||
this.loadDraft(topic);
|
||||
@@ -1076,9 +1083,15 @@ class MessagesView extends React.Component {
|
||||
menuItems.push('menu_item_copy', 'menu_item_favorite');
|
||||
// Recall (Ch.4): own message, real seq, and still inside the server-configured window.
|
||||
// Distinct from delete -- see context-menu.jsx recallOwnMessage for why both exist.
|
||||
// A group admin/owner can also recall *other* members' messages within the same window --
|
||||
// server/im_http.go's serveIMMessageRecall calls authorizeIMMessageMutation with
|
||||
// allowAdmin=true, so the backend has always accepted this; the reference screenshot
|
||||
// (消息页面—长按弹出.png) lists "群撤回" as its own, always-shown menu item alongside "撤回" --
|
||||
// this was previously never offered at all since the only condition checked was authorship.
|
||||
const recallWindow = this.state.messagePolicy ? this.state.messagePolicy.recall_window_seconds : 0;
|
||||
if (params.userFrom == this.props.myUserId && params.seq > 0 && recallWindow > 0 && params.timestamp &&
|
||||
params.timestamp.getTime() > (new Date().getTime() - recallWindow * 1000)) {
|
||||
const canRecallAsAdmin = topic.isGroupType() && topic.getAccessMode() && topic.getAccessMode().isAdmin();
|
||||
if ((params.userFrom == this.props.myUserId || canRecallAsAdmin) && params.seq > 0 && recallWindow > 0 &&
|
||||
params.timestamp && params.timestamp.getTime() > (new Date().getTime() - recallWindow * 1000)) {
|
||||
menuItems.push('menu_item_recall');
|
||||
}
|
||||
// Self -> (1) always hard-delete with [Delete] title.
|
||||
@@ -1182,10 +1195,31 @@ class MessagesView extends React.Component {
|
||||
if (!topic || !topic.isGroupType()) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return getGroupMembers(this.state.topic, {mentionable: true, query}).then(data => data.items || []);
|
||||
return getGroupMembers(this.state.topic, {mentionable: true, query}).then(data => {
|
||||
const items = data.items || [];
|
||||
// "所有人" (参考图 @人员—选择提醒人.png 里排在真实成员最前面的合成选项) -- server/topic.go's
|
||||
// validateAndNormalizeMentions rejects head.mention_all from anyone who isn't admin
|
||||
// (ErrPermissionDenied), so this is only offered to accounts GET .../members already told us
|
||||
// can actually use it -- an ineligible account would otherwise see the option, pick it, and
|
||||
// have the server silently reject the send.
|
||||
const canMentionAll = !!(data.permissions && data.permissions.can_mention_all);
|
||||
if (canMentionAll && '所有人'.includes((query || '').trim())) {
|
||||
return [{user_id: MENTION_ALL_UID, display_name: '所有人'}, ...items];
|
||||
}
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
mentionSelected(uid, name) {
|
||||
if (uid === MENTION_ALL_UID) {
|
||||
// Not a real Drafty mention to resolve -- sendMessage below reads this flag directly to set
|
||||
// head.mention_all, independent of the this.mentionedUsers/applyMentionResolution machinery
|
||||
// single-person @提醒 uses. The "@所有人" text itself still gets auto-detected by Drafty.parse
|
||||
// into an ordinary (uid-less) MN entity and renders highlighted like any other mention --
|
||||
// that's just decorative here since the real "notify everyone" effect comes from the head flag.
|
||||
this.mentionAllPending = true;
|
||||
return;
|
||||
}
|
||||
this.mentionedUsers = (this.mentionedUsers || []).filter(m => m.name !== name);
|
||||
this.mentionedUsers.push({uid, name});
|
||||
}
|
||||
@@ -1445,6 +1479,16 @@ class MessagesView extends React.Component {
|
||||
// (retrySend) reuses the already-resolved Drafty content stored on the failed pub, not this
|
||||
// array, so clearing it here doesn't affect retries.
|
||||
this.mentionedUsers = [];
|
||||
if (this.mentionAllPending) {
|
||||
// server/topic.go's validateAndNormalizeMentions reads head.mention_all directly -- it's a
|
||||
// separate signal from the Drafty MN entity the "@所有人" text itself auto-parses into (that
|
||||
// entity has no real uid and is purely decorative here, same as any other unresolved mention).
|
||||
// Reset immediately after reading, same lifecycle as this.mentionedUsers just above -- a
|
||||
// "所有人" pick should only apply to the one message being sent right now, not linger into
|
||||
// later ones in the same topic.
|
||||
head = {...(head || {}), mention_all: true};
|
||||
this.mentionAllPending = false;
|
||||
}
|
||||
if (typeHint) {
|
||||
head = {...(head || {}), 'x-im-type': typeHint};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user