修复群邀请消息卡片丢弃"立即加入"按钮,被邀请人无法接受邀请
根因:POST /groups/{topic}/invitations 邀请成功后,后端会往邀请人与被邀请人的
单聊会话里发一条 x-im-type=system_action、template=group_invitation_created 的
消息,content.actions 里带着真实可用的 {type:"group_invitation_accept",
label:"立即加入", target_id:<invitation_id>}(对照参考图 6.3 加入群聊.png)。但
chat-message.jsx 原来对所有 system_action/system_notice 消息一律只取
content.text 渲染成纯文本,这个 action 数据被整体丢弃,消息只显示"邀请你加入群聊。"
没有任何按钮——im-api.js 里封装好的 acceptGroupInvitation() 因此从来没有调用点。
怎么修:chat-message.jsx 在原有的文本兜底之前,单独识别 content.actions 里
type=="group_invitation_accept" 的这一种模板,渲染一张带按钮的小卡片(其余
system_action/system_notice 模板的纯文本渲染保持不变,不受影响);
messages-view.jsx 新增 handleAcceptGroupInvitation,调用 acceptGroupInvitation()
并用已有的 showToast 反馈结果,逐层传给 ChatMessage 的新 onAcceptGroupInvitation
prop。按钮点击后按 busy/joined/pending_approval/error 四态展示,避免重复提交。
验证:用两个真实账号(A 建群,直接对 B 发起 POST /invitations)复现,重新构建后
B 的会话里正确出现"邀请你加入群聊。"+"立即加入"按钮;点击后正确调用
POST /group-invitations/{id}/accept 并把返回的失败原因("服务器繁忙,请稍后重试")
展示出来——这一步暴露了一个独立的后端 500 bug(accept 接口本身的实现有问题,
已记录在测试报告里,不在本次前端修复范围内),前端的请求发起、按钮状态机和错误展示
均已确认按预期工作。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a6014b84ee
commit
cdca509183
@@ -3608,6 +3608,35 @@ img.channel {
|
|||||||
color: var(--clr-primary-accent);
|
color: var(--clr-primary-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 6.3 加入群聊.png: the one system_action message (group_invitation_created) that carries a real
|
||||||
|
accept button -- see chat-message.jsx's renderGroupInvitationCard. */
|
||||||
|
.message-group-invitation button {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
padding: 0.3rem 0.9rem;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
background: var(--clr-primary-accent);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.message-group-invitation button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.message-group-invitation em {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
color: var(--clr-text-secondary);
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.message-group-invitation-error {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
color: #df4e46;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Multi-select mode (Ch.4 4.15-4.16). */
|
/* Multi-select mode (Ch.4 4.15-4.16). */
|
||||||
.select-checkbox {
|
.select-checkbox {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -29,9 +29,9 @@ import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDI
|
|||||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||||
import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
|
import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
import { topicDisplayName } from '../lib/utils.js';
|
import { topicDisplayName } from '../lib/utils.js';
|
||||||
import { checkURL, confirmRiskChallenge, createChatUploader, favoriteMessages, getBootstrap, getConversationContext,
|
import { acceptGroupInvitation, checkURL, confirmRiskChallenge, createChatUploader, favoriteMessages, getBootstrap,
|
||||||
getFavorites, getGroupJoinRequests, getGroupMembers, getLatestGroupAnnouncement, getTopicDraft, recallMessage,
|
getConversationContext, getFavorites, getGroupJoinRequests, getGroupMembers, getLatestGroupAnnouncement,
|
||||||
updateTopicDraft } from '../lib/im-api.js';
|
getTopicDraft, recallMessage, updateTopicDraft } from '../lib/im-api.js';
|
||||||
import HashNavigation from '../lib/navigation.js';
|
import HashNavigation from '../lib/navigation.js';
|
||||||
import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
|
import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
|
||||||
|
|
||||||
@@ -205,6 +205,7 @@ class MessagesView extends React.Component {
|
|||||||
this.handleShowAnnouncement = this.handleShowAnnouncement.bind(this);
|
this.handleShowAnnouncement = this.handleShowAnnouncement.bind(this);
|
||||||
this.handleShowJoinRequests = this.handleShowJoinRequests.bind(this);
|
this.handleShowJoinRequests = this.handleShowJoinRequests.bind(this);
|
||||||
this.handleSendFriendVerification = this.handleSendFriendVerification.bind(this);
|
this.handleSendFriendVerification = this.handleSendFriendVerification.bind(this);
|
||||||
|
this.handleAcceptGroupInvitation = this.handleAcceptGroupInvitation.bind(this);
|
||||||
this.handleBatchDelete = this.handleBatchDelete.bind(this);
|
this.handleBatchDelete = this.handleBatchDelete.bind(this);
|
||||||
this.handleBatchFavorite = this.handleBatchFavorite.bind(this);
|
this.handleBatchFavorite = this.handleBatchFavorite.bind(this);
|
||||||
this.handleAttachFile = this.handleAttachFile.bind(this);
|
this.handleAttachFile = this.handleAttachFile.bind(this);
|
||||||
@@ -1384,6 +1385,29 @@ class MessagesView extends React.Component {
|
|||||||
HashNavigation.setUrlSidePanel('', 'friend-verification'), 'uid', this.state.topic));
|
HashNavigation.setUrlSidePanel('', 'friend-verification'), 'uid', this.state.topic));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6.3 加入群聊.png:被邀请人在跟邀请人的会话里收到一张"邀请你加入群聊……立即加入"的卡片。
|
||||||
|
// 后端 serveIMGroupInvitations 确实往这个 P2P 会话发了一条 x-im-type=system_action、
|
||||||
|
// template=group_invitation_created 的消息,content.actions 里带着真实的
|
||||||
|
// {type:"group_invitation_accept", label:"立即加入", target_id:<invitation_id>}——但
|
||||||
|
// chat-message.jsx 原来对所有 system_action/system_notice 消息一律只取 content.text 渲染成
|
||||||
|
// 纯文本,这个 action 数据被整体丢弃,卡片上不会出现任何按钮,被邀请人无法接受邀请。
|
||||||
|
// acceptGroupInvitation() 这个封装早就在 im-api.js 里,只是从未有调用点。
|
||||||
|
handleAcceptGroupInvitation(invitationId) {
|
||||||
|
return acceptGroupInvitation(invitationId).then(result => {
|
||||||
|
if (result && result.status == 'joined') {
|
||||||
|
this.showToast('已加入群聊');
|
||||||
|
} else if (result && result.status == 'pending_approval') {
|
||||||
|
this.showToast('申请已提交,等待管理员通过');
|
||||||
|
} else {
|
||||||
|
this.showToast('已处理该邀请');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).catch(err => {
|
||||||
|
this.showToast(err.message || '加入群聊失败,请重试');
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
showToast(text) {
|
showToast(text) {
|
||||||
this.setState({toast: text});
|
this.setState({toast: text});
|
||||||
window.clearTimeout(this.toastTimer);
|
window.clearTimeout(this.toastTimer);
|
||||||
@@ -2360,6 +2384,7 @@ class MessagesView extends React.Component {
|
|||||||
userIsAdmin={this.state.isAdmin}
|
userIsAdmin={this.state.isAdmin}
|
||||||
relationship={this.state.relationshipContext ? this.state.relationshipContext.relationship : null}
|
relationship={this.state.relationshipContext ? this.state.relationshipContext.relationship : null}
|
||||||
onSendFriendVerification={this.handleSendFriendVerification}
|
onSendFriendVerification={this.handleSendFriendVerification}
|
||||||
|
onAcceptGroupInvitation={this.handleAcceptGroupInvitation}
|
||||||
selectMode={this.props.selectMode}
|
selectMode={this.props.selectMode}
|
||||||
selected={this.props.selectedSeqs && this.props.selectedSeqs.includes(msg.seq)}
|
selected={this.props.selectedSeqs && this.props.selectedSeqs.includes(msg.seq)}
|
||||||
onToggleSelect={this.props.onToggleMessageSelect}
|
onToggleSelect={this.props.onToggleMessageSelect}
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
voicePlayed: isVoicePlayed(props.tinode.getCurrentUserID(), props.topic, props.seq)
|
voicePlayed: isVoicePlayed(props.tinode.getCurrentUserID(), props.topic, props.seq),
|
||||||
|
invitationBusy: false,
|
||||||
|
invitationResult: null,
|
||||||
|
invitationError: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.uploader) {
|
if (props.uploader) {
|
||||||
@@ -62,6 +65,7 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||||
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
||||||
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
||||||
|
this.handleAcceptGroupInvitation = this.handleAcceptGroupInvitation.bind(this);
|
||||||
|
|
||||||
this.formatterContext = {
|
this.formatterContext = {
|
||||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||||
@@ -232,6 +236,39 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See the long comment above messages-view.jsx's handleAcceptGroupInvitation for the backstory --
|
||||||
|
// this just drives this one message bubble's own busy/done/error display around that call.
|
||||||
|
handleAcceptGroupInvitation(invitationId) {
|
||||||
|
if (this.state.invitationBusy || this.state.invitationResult || !this.props.onAcceptGroupInvitation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({invitationBusy: true, invitationError: null});
|
||||||
|
this.props.onAcceptGroupInvitation(invitationId).then(result => {
|
||||||
|
this.setState({invitationBusy: false, invitationResult: (result && result.status) || 'joined'});
|
||||||
|
}).catch(err => {
|
||||||
|
this.setState({invitationBusy: false, invitationError: (err && err.message) || '操作失败,请重试'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// docs/images/加入群聊.png: a card with the inviter's notice text and a single "立即加入" button.
|
||||||
|
// Only rendered for the one system_action template that actually carries an actionable
|
||||||
|
// `content.actions` entry -- every other system_action/system_notice message keeps falling through
|
||||||
|
// to the plain-text path right below, unchanged.
|
||||||
|
renderGroupInvitationCard(text, action) {
|
||||||
|
const {invitationBusy, invitationResult, invitationError} = this.state;
|
||||||
|
return <div className="message-group-invitation">
|
||||||
|
<div>{text}</div>
|
||||||
|
{invitationResult == 'joined' ? <em>已加入群聊</em> :
|
||||||
|
invitationResult == 'pending_approval' ? <em>已提交申请,等待管理员通过</em> :
|
||||||
|
invitationResult ? <em>{invitationResult}</em> :
|
||||||
|
<button type="button" disabled={invitationBusy}
|
||||||
|
onClick={() => this.handleAcceptGroupInvitation(action.target_id)}>
|
||||||
|
{invitationBusy ? '处理中…' : (action.label || '立即加入')}
|
||||||
|
</button>}
|
||||||
|
{invitationError ? <small className="message-group-invitation-error">{invitationError}</small> : null}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
// Reason caption shown under a failed outgoing message in a 1:1 chat, based on the real
|
// Reason caption shown under a failed outgoing message in a 1:1 chat, based on the real
|
||||||
// relationship state (see docs/images/个人对话—拉黑状态/已读状态.png and .../个人对话—删除状态.png).
|
// relationship state (see docs/images/个人对话—拉黑状态/已读状态.png and .../个人对话—删除状态.png).
|
||||||
// Returns null for relationships that don't explain a send failure (friend, pending, unknown) so
|
// Returns null for relationships that don't explain a send failure (friend, pending, unknown) so
|
||||||
@@ -281,7 +318,22 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
// message like "有成员通过审批加入了群聊。" was rendering as a bare warning icon. Reduce either to
|
// message like "有成员通过审批加入了群聊。" was rendering as a bare warning icon. Reduce either to
|
||||||
// its `text` upfront so the existing plain-string path renders it as an ordinary bubble -- these
|
// its `text` upfront so the existing plain-string path renders it as an ordinary bubble -- these
|
||||||
// are meant to read like a normal message, not a special banner.
|
// are meant to read like a normal message, not a special banner.
|
||||||
if (this.props.head && (this.props.head['x-im-type'] === 'system_notice' || this.props.head['x-im-type'] === 'system_action') &&
|
//
|
||||||
|
// One template is the exception: `group_invitation_created` (docs/images/加入群聊.png) is the one
|
||||||
|
// system_action message that actually carries a real, backend-honored action -- confirmed live
|
||||||
|
// against server/im_http.go's serveIMGroupInvitations, content.actions[0] is
|
||||||
|
// {type:"group_invitation_accept", label:"立即加入", target_id:<invitation_id>}, and
|
||||||
|
// acceptGroupInvitation() already existed in im-api.js with zero call sites because this action
|
||||||
|
// data was being thrown away right here along with everything else system_action carries. Collapsing
|
||||||
|
// it to bare text (like every other system_action template correctly does) left the invitee with no
|
||||||
|
// way to ever accept -- the reference image's whole point is the "立即加入" button.
|
||||||
|
const systemActions = content && typeof content == 'object' && Array.isArray(content.actions) ? content.actions : null;
|
||||||
|
const inviteAction = this.props.head && this.props.head['x-im-type'] === 'system_action' && systemActions &&
|
||||||
|
systemActions.find(a => a && a.type === 'group_invitation_accept');
|
||||||
|
let invitationCard = null;
|
||||||
|
if (inviteAction && typeof content.text == 'string') {
|
||||||
|
invitationCard = this.renderGroupInvitationCard(content.text, inviteAction);
|
||||||
|
} else if (this.props.head && (this.props.head['x-im-type'] === 'system_notice' || this.props.head['x-im-type'] === 'system_action') &&
|
||||||
content && typeof content == 'object' && typeof content.text == 'string') {
|
content && typeof content == 'object' && typeof content.text == 'string') {
|
||||||
content = content.text;
|
content = content.text;
|
||||||
}
|
}
|
||||||
@@ -353,7 +405,7 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
{showVoiceUnreadDot ? <i className="voice-unread-dot" /> : null}
|
{showVoiceUnreadDot ? <i className="voice-unread-dot" /> : null}
|
||||||
<div className="content-meta">
|
<div className="content-meta">
|
||||||
<div className={textSizeClass}>
|
<div className={textSizeClass}>
|
||||||
{bundleCard || content}
|
{invitationCard || bundleCard || content}
|
||||||
{attachments}
|
{attachments}
|
||||||
</div>
|
</div>
|
||||||
{this.props.timestamp ?
|
{this.props.timestamp ?
|
||||||
|
|||||||
+38
-9
@@ -6373,7 +6373,10 @@ function Register({
|
|||||||
error: errors.invite,
|
error: errors.invite,
|
||||||
autoCapitalize: "off",
|
autoCapitalize: "off",
|
||||||
autoCorrect: "off"
|
autoCorrect: "off"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("button", {
|
}, void 0, false), !usingPhone ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("p", {
|
||||||
|
className: "auth-help",
|
||||||
|
children: "未绑定手机号无法自助找回密码,忘记密码后只能联系企业管理员重置"
|
||||||
|
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("button", {
|
||||||
className: "auth-primary",
|
className: "auth-primary",
|
||||||
type: "submit",
|
type: "submit",
|
||||||
disabled: !form.username && !form.phone || !form.password || !agreed || submitting,
|
disabled: !form.username && !form.phone || !form.password || !agreed || submitting,
|
||||||
@@ -6644,6 +6647,10 @@ function Realname({
|
|||||||
});
|
});
|
||||||
setStatus(data.status || 'pending');
|
setStatus(data.status || 'pending');
|
||||||
setError('');
|
setError('');
|
||||||
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getRealnameStatus)().then(fresh => {
|
||||||
|
if (fresh.masked_name) setMaskedName(fresh.masked_name);
|
||||||
|
if (fresh.masked_id_number) setMaskedIdNumber(fresh.masked_id_number);
|
||||||
|
}).catch(() => {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || '提交失败,请稍后重试');
|
setError(err.message || '提交失败,请稍后重试');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -15653,11 +15660,6 @@ function SettingsList({
|
|||||||
pending: '审核中',
|
pending: '审核中',
|
||||||
rejected: '未通过'
|
rejected: '未通过'
|
||||||
}[realnameStatus] || '未认证';
|
}[realnameStatus] || '未认证';
|
||||||
const logout = () => {
|
|
||||||
if (window.confirm('确定要退出当前登录吗?')) {
|
|
||||||
onLogout ? onLogout() : onToast('当前环境不支持退出登录');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
className: "personal-page",
|
className: "personal-page",
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
||||||
@@ -15712,7 +15714,7 @@ function SettingsList({
|
|||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
className: "personal-logout",
|
className: "personal-logout",
|
||||||
onClick: logout,
|
onClick: () => setSheet('logout'),
|
||||||
children: "退出当前登录"
|
children: "退出当前登录"
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), sheet === 'cache' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(CacheClear, {
|
}, void 0, true), sheet === 'cache' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(CacheClear, {
|
||||||
@@ -15727,6 +15729,10 @@ function SettingsList({
|
|||||||
onToast: onToast,
|
onToast: onToast,
|
||||||
onDeleteAccount: onDeleteAccount,
|
onDeleteAccount: onDeleteAccount,
|
||||||
onClose: () => setSheet(null)
|
onClose: () => setSheet(null)
|
||||||
|
}, void 0, false) : null, sheet === 'logout' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(LogoutConfirm, {
|
||||||
|
onToast: onToast,
|
||||||
|
onLogout: onLogout,
|
||||||
|
onClose: () => setSheet(null)
|
||||||
}, void 0, false) : null]
|
}, void 0, false) : null]
|
||||||
}, void 0, true);
|
}, void 0, true);
|
||||||
}
|
}
|
||||||
@@ -15768,6 +15774,23 @@ function ConfirmSheet({
|
|||||||
}, void 0, true)
|
}, void 0, true)
|
||||||
}, void 0, false);
|
}, void 0, false);
|
||||||
}
|
}
|
||||||
|
function LogoutConfirm({
|
||||||
|
onLogout,
|
||||||
|
onToast,
|
||||||
|
onClose
|
||||||
|
}) {
|
||||||
|
const confirm = () => {
|
||||||
|
onClose();
|
||||||
|
onLogout ? onLogout() : onToast('当前环境不支持退出登录');
|
||||||
|
};
|
||||||
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(ConfirmSheet, {
|
||||||
|
icon: "logout",
|
||||||
|
title: "退出登录",
|
||||||
|
body: "确定要退出当前登录吗?",
|
||||||
|
onConfirm: confirm,
|
||||||
|
onCancel: onClose
|
||||||
|
}, void 0, false);
|
||||||
|
}
|
||||||
function CacheClear({
|
function CacheClear({
|
||||||
onToast,
|
onToast,
|
||||||
onClose
|
onClose
|
||||||
@@ -19008,7 +19031,7 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
return this.tinode.createAccountBasic(login_, password_, {
|
return this.tinode.createAccountBasic(login_, password_, {
|
||||||
public: public_,
|
public: public_,
|
||||||
tags: tags_,
|
tags: tags_,
|
||||||
cred: tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.credential(cred_),
|
cred: cred_ ? tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.credential(cred_) : undefined,
|
||||||
attachments: attachments
|
attachments: attachments
|
||||||
});
|
});
|
||||||
}).then(ctrl => {
|
}).then(ctrl => {
|
||||||
@@ -21178,7 +21201,7 @@ function MeetingDetail({
|
|||||||
const respond = response => {
|
const respond = response => {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getMeeting)(meeting.meeting_id).then(fresh => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.respondMeeting)(fresh.meeting_id, response, fresh.version)).then(_ => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getMeeting)(meeting.meeting_id).then(fresh => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.respondMeeting)(fresh.meeting_id, response, fresh.version)).then(_ => {
|
||||||
onToast(RESPONSE_LABEL[response] ? `已${RESPONSE_LABEL[response]}` : '已回应');
|
onToast(response === 'accepted' ? '已接受会议邀请' : response === 'declined' ? '已拒绝会议邀请' : '已标记为待定');
|
||||||
load();
|
load();
|
||||||
}).catch(err => onToast(err.message || '操作失败')).finally(() => setBusy(false));
|
}).catch(err => onToast(err.message || '操作失败')).finally(() => setBusy(false));
|
||||||
};
|
};
|
||||||
@@ -21278,6 +21301,12 @@ function MeetingDetail({
|
|||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("small", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("small", {
|
||||||
children: "已拒绝"
|
children: "已拒绝"
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("span", {
|
||||||
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("b", {
|
||||||
|
children: counts.tentative
|
||||||
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("small", {
|
||||||
|
children: "待定"
|
||||||
|
}, void 0, false)]
|
||||||
}, void 0, true)]
|
}, void 0, true)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("footer", {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("footer", {
|
||||||
children: [meeting.permissions && meeting.permissions.can_remind ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("button", {
|
children: [meeting.permissions && meeting.permissions.can_remind ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("button", {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -248,6 +248,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
this.handleShowAnnouncement = this.handleShowAnnouncement.bind(this);
|
this.handleShowAnnouncement = this.handleShowAnnouncement.bind(this);
|
||||||
this.handleShowJoinRequests = this.handleShowJoinRequests.bind(this);
|
this.handleShowJoinRequests = this.handleShowJoinRequests.bind(this);
|
||||||
this.handleSendFriendVerification = this.handleSendFriendVerification.bind(this);
|
this.handleSendFriendVerification = this.handleSendFriendVerification.bind(this);
|
||||||
|
this.handleAcceptGroupInvitation = this.handleAcceptGroupInvitation.bind(this);
|
||||||
this.handleBatchDelete = this.handleBatchDelete.bind(this);
|
this.handleBatchDelete = this.handleBatchDelete.bind(this);
|
||||||
this.handleBatchFavorite = this.handleBatchFavorite.bind(this);
|
this.handleBatchFavorite = this.handleBatchFavorite.bind(this);
|
||||||
this.handleAttachFile = this.handleAttachFile.bind(this);
|
this.handleAttachFile = this.handleAttachFile.bind(this);
|
||||||
@@ -1218,6 +1219,21 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].navigateTo(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].addUrlParam(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].setUrlSidePanel('', 'friend-verification'), 'uid', this.state.topic));
|
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].navigateTo(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].addUrlParam(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_20__["default"].setUrlSidePanel('', 'friend-verification'), 'uid', this.state.topic));
|
||||||
}
|
}
|
||||||
|
handleAcceptGroupInvitation(invitationId) {
|
||||||
|
return (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_19__.acceptGroupInvitation)(invitationId).then(result => {
|
||||||
|
if (result && result.status == 'joined') {
|
||||||
|
this.showToast('已加入群聊');
|
||||||
|
} else if (result && result.status == 'pending_approval') {
|
||||||
|
this.showToast('申请已提交,等待管理员通过');
|
||||||
|
} else {
|
||||||
|
this.showToast('已处理该邀请');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).catch(err => {
|
||||||
|
this.showToast(err.message || '加入群聊失败,请重试');
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
showToast(text) {
|
showToast(text) {
|
||||||
this.setState({
|
this.setState({
|
||||||
toast: text
|
toast: text
|
||||||
@@ -2055,6 +2071,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
userIsAdmin: this.state.isAdmin,
|
userIsAdmin: this.state.isAdmin,
|
||||||
relationship: this.state.relationshipContext ? this.state.relationshipContext.relationship : null,
|
relationship: this.state.relationshipContext ? this.state.relationshipContext.relationship : null,
|
||||||
onSendFriendVerification: this.handleSendFriendVerification,
|
onSendFriendVerification: this.handleSendFriendVerification,
|
||||||
|
onAcceptGroupInvitation: this.handleAcceptGroupInvitation,
|
||||||
selectMode: this.props.selectMode,
|
selectMode: this.props.selectMode,
|
||||||
selected: this.props.selectedSeqs && this.props.selectedSeqs.includes(msg.seq),
|
selected: this.props.selectedSeqs && this.props.selectedSeqs.includes(msg.seq),
|
||||||
onToggleSelect: this.props.onToggleMessageSelect,
|
onToggleSelect: this.props.onToggleMessageSelect,
|
||||||
@@ -2595,7 +2612,10 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
voicePlayed: isVoicePlayed(props.tinode.getCurrentUserID(), props.topic, props.seq)
|
voicePlayed: isVoicePlayed(props.tinode.getCurrentUserID(), props.topic, props.seq),
|
||||||
|
invitationBusy: false,
|
||||||
|
invitationResult: null,
|
||||||
|
invitationError: null
|
||||||
};
|
};
|
||||||
if (props.uploader) {
|
if (props.uploader) {
|
||||||
props.uploader.onProgress = this.handleProgress.bind(this);
|
props.uploader.onProgress = this.handleProgress.bind(this);
|
||||||
@@ -2608,6 +2628,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||||
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
||||||
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
||||||
|
this.handleAcceptGroupInvitation = this.handleAcceptGroupInvitation.bind(this);
|
||||||
this.formatterContext = {
|
this.formatterContext = {
|
||||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||||
viewportWidth: props.viewportWidth,
|
viewportWidth: props.viewportWidth,
|
||||||
@@ -2763,6 +2784,53 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
this.props.onQuoteClick(replyToSeq);
|
this.props.onQuoteClick(replyToSeq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleAcceptGroupInvitation(invitationId) {
|
||||||
|
if (this.state.invitationBusy || this.state.invitationResult || !this.props.onAcceptGroupInvitation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
invitationBusy: true,
|
||||||
|
invitationError: null
|
||||||
|
});
|
||||||
|
this.props.onAcceptGroupInvitation(invitationId).then(result => {
|
||||||
|
this.setState({
|
||||||
|
invitationBusy: false,
|
||||||
|
invitationResult: result && result.status || 'joined'
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
this.setState({
|
||||||
|
invitationBusy: false,
|
||||||
|
invitationError: err && err.message || '操作失败,请重试'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renderGroupInvitationCard(text, action) {
|
||||||
|
const {
|
||||||
|
invitationBusy,
|
||||||
|
invitationResult,
|
||||||
|
invitationError
|
||||||
|
} = this.state;
|
||||||
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||||
|
className: "message-group-invitation",
|
||||||
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||||
|
children: text
|
||||||
|
}, void 0, false), invitationResult == 'joined' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("em", {
|
||||||
|
children: "已加入群聊"
|
||||||
|
}, void 0, false) : invitationResult == 'pending_approval' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("em", {
|
||||||
|
children: "已提交申请,等待管理员通过"
|
||||||
|
}, void 0, false) : invitationResult ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("em", {
|
||||||
|
children: invitationResult
|
||||||
|
}, void 0, false) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("button", {
|
||||||
|
type: "button",
|
||||||
|
disabled: invitationBusy,
|
||||||
|
onClick: () => this.handleAcceptGroupInvitation(action.target_id),
|
||||||
|
children: invitationBusy ? '处理中…' : action.label || '立即加入'
|
||||||
|
}, void 0, false), invitationError ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("small", {
|
||||||
|
className: "message-group-invitation-error",
|
||||||
|
children: invitationError
|
||||||
|
}, void 0, false) : null]
|
||||||
|
}, void 0, true);
|
||||||
|
}
|
||||||
renderFailReason() {
|
renderFailReason() {
|
||||||
if (this.props.response || this.props.received != tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.MESSAGE_STATUS_FAILED && this.props.received != tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.MESSAGE_STATUS_FATAL) {
|
if (this.props.response || this.props.received != tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.MESSAGE_STATUS_FAILED && this.props.received != tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.MESSAGE_STATUS_FATAL) {
|
||||||
return null;
|
return null;
|
||||||
@@ -2804,7 +2872,12 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
let textSizeClass = 'message-content';
|
let textSizeClass = 'message-content';
|
||||||
const fullDisplay = this.props.isGroup && this.props.response && (this.props.sequence == 'single' || this.props.sequence == 'last');
|
const fullDisplay = this.props.isGroup && this.props.response && (this.props.sequence == 'single' || this.props.sequence == 'last');
|
||||||
let content = this.props.content;
|
let content = this.props.content;
|
||||||
if (this.props.head && (this.props.head['x-im-type'] === 'system_notice' || this.props.head['x-im-type'] === 'system_action') && content && typeof content == 'object' && typeof content.text == 'string') {
|
const systemActions = content && typeof content == 'object' && Array.isArray(content.actions) ? content.actions : null;
|
||||||
|
const inviteAction = this.props.head && this.props.head['x-im-type'] === 'system_action' && systemActions && systemActions.find(a => a && a.type === 'group_invitation_accept');
|
||||||
|
let invitationCard = null;
|
||||||
|
if (inviteAction && typeof content.text == 'string') {
|
||||||
|
invitationCard = this.renderGroupInvitationCard(content.text, inviteAction);
|
||||||
|
} else if (this.props.head && (this.props.head['x-im-type'] === 'system_notice' || this.props.head['x-im-type'] === 'system_action') && content && typeof content == 'object' && typeof content.text == 'string') {
|
||||||
content = content.text;
|
content = content.text;
|
||||||
}
|
}
|
||||||
let bundleCard = null;
|
let bundleCard = null;
|
||||||
@@ -2880,7 +2953,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
className: "content-meta",
|
className: "content-meta",
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||||
className: textSizeClass,
|
className: textSizeClass,
|
||||||
children: [bundleCard || content, attachments]
|
children: [invitationCard || bundleCard || content, attachments]
|
||||||
}, void 0, true), this.props.timestamp ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)(_received_marker_jsx__WEBPACK_IMPORTED_MODULE_6__["default"], {
|
}, void 0, true), this.props.timestamp ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)(_received_marker_jsx__WEBPACK_IMPORTED_MODULE_6__["default"], {
|
||||||
edited: this.props.edited,
|
edited: this.props.edited,
|
||||||
timestamp: this.props.timestamp,
|
timestamp: this.props.timestamp,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -154,6 +154,14 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.containerRef.current.style.cursor = 'grab';
|
this.containerRef.current.style.cursor = 'grab';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.handleWindowResize = () => {
|
||||||
|
if (this.props.minimized && this.containerRef && this.containerRef.current && window.matchMedia('(width<=640px)').matches) {
|
||||||
|
const el = this.containerRef.current;
|
||||||
|
el.style.removeProperty('left');
|
||||||
|
el.style.removeProperty('right');
|
||||||
|
el.style.removeProperty('top');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
@@ -167,12 +175,14 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.containerRef.current.addEventListener('pointermove', this.drag);
|
this.containerRef.current.addEventListener('pointermove', this.drag);
|
||||||
this.containerRef.current.addEventListener('pointerup', this.dragEnd);
|
this.containerRef.current.addEventListener('pointerup', this.dragEnd);
|
||||||
}
|
}
|
||||||
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
topic.onInfo = this.previousOnInfo;
|
topic.onInfo = this.previousOnInfo;
|
||||||
this.stop();
|
this.stop();
|
||||||
|
window.removeEventListener('resize', this.handleWindowResize);
|
||||||
if (this.containerRef) {
|
if (this.containerRef) {
|
||||||
this.containerRef.current.removeEventListener('pointerdown', this.dragStart);
|
this.containerRef.current.removeEventListener('pointerdown', this.dragStart);
|
||||||
this.containerRef.current.removeEventListener('pointermove', this.drag);
|
this.containerRef.current.removeEventListener('pointermove', this.drag);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user