第八轮最小粒度测试:修复CSS选择器/JSX结构不匹配的一整类bug,清理Tinode品牌残留

第八轮改用"读CSS选择器+核对JSX结构"的方式复测全部11章,命中了7轮纯接口测试从未发现的一类
问题——选择器写对了但目标元素的实际DOM结构/className对不上,样式静默失效:

- 语音通话最小化胶囊按钮尺寸被同特异度规则覆盖;移动端悬浮通话窗口left/right/width三值同时
  生效导致过度约束;来电全屏页认证徽章/姓名标题缺少专属样式覆盖
- 我的收藏-语音卡片三列grid对应实际只有1个子元素,播放器被压缩到卡片左侧一角;链接/记录头部
  完全没有基础样式,退化成浏览器默认外观
- 多选模式下"收到的"消息气泡被flex auto-margin一起拖到最右侧;.avatar-box/#send-message-panel
  两处逗号断句错误导致后半句选择器丢失祖先限定
- 按住说话悬浮反馈渲染成内联胶囊,而参考图要求的居中悬浮卡片样式其实已经存在(挂在了另一个非
  生产demo组件上);批量转发确认框沿用了80%视口高度;audio-player.jsx的playback类点在canvas
  上而非包裹层,波形和计时器可能纵向堆叠
- "消息已撤回"占位符误拼了日期分隔条的class而非专属的deleted

品牌清理:浏览器标签页标题、未读数标题更新均硬编码"Tinode";LINK_CONTACT_US等三个链接常量
指向开源Tinode项目自己的域名(联系我们改为跟用户条款/隐私协议一致的toast,不再发起外部mailto)。

以及:空会话丢失.chat-box样式类、青少年模式PIN定位、若干收藏/文件类型展示细节。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-05 17:09:42 +08:00
co-authored by Claude Sonnet 5
parent 5e173c55e8
commit d759f67b11
24 changed files with 375 additions and 250 deletions
+100 -88
View File
@@ -2400,9 +2400,9 @@ const FORWARDED_PREVIEW_LENGTH = 84;
const EDIT_PREVIEW_LENGTH = 48;
const MIN_DURATION = 2_000;
const MAX_DURATION = 600_000;
const LINK_CONTACT_US = 'mailto:support@tinode.co';
const LINK_PRIVACY_POLICY = 'https://tinode.co/privacy.html';
const LINK_TERMS_OF_SERVICE = 'https://tinode.co/terms.html';
const LINK_CONTACT_US = null;
const LINK_PRIVACY_POLICY = null;
const LINK_TERMS_OF_SERVICE = null;
const IMMEDIATE_P2P_SUBSCRIPTION = false;
const CLICKABLE_URL_SCHEMES = ['http', 'https', 'ftp', 'ftps'];
const QRCODE_SIZE = 128;
@@ -3722,7 +3722,8 @@ const UPLOAD_CTRL_TEXT_CN = {
'too large': '文件大小超出限制',
'malformed': '请求内容有误,请稍后重试',
'permission denied': '没有权限执行此操作',
'gone': '内容不存在或已被删除'
'gone': '内容不存在或已被删除',
'not found': '内容不存在或已被删除'
};
function uploadLargeFile(tinode, file, onProgress) {
const authToken = tinode && tinode.getAuthToken && tinode.getAuthToken();
@@ -4886,7 +4887,7 @@ function updateFavicon(count) {
head.removeChild(oldIcon);
}
head.appendChild(newIcon);
document.title = (count > 0 ? '(' + count + ') ' : '') + 'Tinode';
document.title = (count > 0 ? '(' + count + ') ' : '') + _config_js__WEBPACK_IMPORTED_MODULE_1__.BASE_APP_NAME;
}
function theCard(fn, imageUrl, imageMimeType, note) {
let card = null;
@@ -5470,96 +5471,93 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl");
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js");
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js");
function FlatLink({
href,
icon,
children
}) {
return href ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("a", {
href: href,
className: "flat-button",
target: "_blank",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("i", {
className: "material-icons",
children: icon
}, void 0, false), " \xA0", children]
}, void 0, true) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("span", {
className: "flat-button",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("i", {
className: "material-icons",
children: icon
}, void 0, false), " \xA0", children]
}, void 0, true);
}
class AccSupportView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
render() {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "scrollable-panel",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "panel-form-column",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("a", {
href: _config_js__WEBPACK_IMPORTED_MODULE_3__.LINK_CONTACT_US,
className: "flat-button",
target: "_blank",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("i", {
className: "material-icons",
children: "email"
}, void 0, false), " \xA0", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(FlatLink, {
href: _config_js__WEBPACK_IMPORTED_MODULE_2__.LINK_CONTACT_US,
icon: "email",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "link_contact_us",
defaultMessage: "Contact Us",
description: "Ancor text for contacting us by email"
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("a", {
href: _config_js__WEBPACK_IMPORTED_MODULE_3__.LINK_TERMS_OF_SERVICE,
className: "flat-button",
target: "_blank",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("i", {
className: "material-icons",
children: "description"
}, void 0, false), " \xA0", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
}, void 0, false)
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(FlatLink, {
href: _config_js__WEBPACK_IMPORTED_MODULE_2__.LINK_TERMS_OF_SERVICE,
icon: "description",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "link_terms_of_service",
defaultMessage: "Terms of Service",
description: "Ancor text for terms of service link"
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("a", {
href: _config_js__WEBPACK_IMPORTED_MODULE_3__.LINK_PRIVACY_POLICY,
className: "flat-button",
target: "_blank",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("i", {
className: "material-icons",
children: "policy"
}, void 0, false), " \xA0", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
}, void 0, false)
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(FlatLink, {
href: _config_js__WEBPACK_IMPORTED_MODULE_2__.LINK_PRIVACY_POLICY,
icon: "policy",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "link_privacy_policy",
defaultMessage: "Privacy Policy",
description: "Ancor text for privacy policy link"
}, void 0, false)]
}, void 0, true)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
}, void 0, false)
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "hr"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "panel-form-column",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "panel-form-row",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("label", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("label", {
className: "small",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_client",
defaultMessage: "Client:",
description: "Label for a client version"
}, void 0, false)
}, void 0, false), _config_js__WEBPACK_IMPORTED_MODULE_3__.APP_NAME]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
}, void 0, false), _config_js__WEBPACK_IMPORTED_MODULE_2__.APP_NAME]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "panel-form-row",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("label", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("label", {
className: "small",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_sdk",
defaultMessage: "SDK:"
}, void 0, false)
}, void 0, false), tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.getLibrary()]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
className: "panel-form-row",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("label", {
className: "small",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_server",
defaultMessage: "Server:",
description: "Label for a server version"
}, void 0, false)
}, void 0, false), this.props.serverVersion]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "panel-form-row",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("label", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("label", {
className: "small",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_server_address",
defaultMessage: "Server address:"
}, void 0, false)
@@ -5965,13 +5963,14 @@ function EnterpriseRegister() {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("ol", {
className: "auth-steps",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("li", {
className: submitted ? '' : 'active',
className: "active",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("span", {
children: "1"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("b", {
children: "填写企业信息"
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("li", {
className: submitted ? 'active' : '',
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("span", {
children: "2"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("b", {
@@ -6356,7 +6355,9 @@ function Register({
invite: e.target.value.replace(/[^A-Za-z]/g, '').slice(0, 5)
}),
placeholder: "请输入邀请码(非必填)",
error: errors.invite
error: errors.invite,
autoCapitalize: "off",
autoCorrect: "off"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("button", {
className: "auth-primary",
type: "submit",
@@ -7664,7 +7665,7 @@ function AddressBook({
}).catch(() => {}).finally(() => setLoading(false));
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriendRequests)('inbound', 'pending', cursor)).then(list => setPendingCount(list.length)).catch(() => {});
}, []);
const visible = friends.filter(item => `${item.display_name}${item.remark || ''}`.includes(query));
const visible = friends.filter(item => `${item.display_name}${item.remark || ''}`.toLowerCase().includes(query.toLowerCase()));
const groups = visible.reduce((result, item) => {
const key = alphaInitial(item.remark || item.display_name);
return {
@@ -8486,7 +8487,7 @@ function Settings({
className: "people-remark-edit",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("input", {
value: remarkDraft,
maxLength: "20",
maxLength: "64",
onChange: e => setRemarkDraft(e.target.value),
autoFocus: true
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
@@ -9040,10 +9041,18 @@ const FILE_TYPE_META = {
document: ['description', '#e05d44'],
file: ['insert_drive_file', '#8a8f98']
};
const DOCUMENT_EXT = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
function extFromName(name) {
const m = /\.([a-zA-Z0-9]+)$/.exec(name || '');
return m ? m[1].toUpperCase() : '';
}
function fileTypeFromMime(mime, name) {
const prefix = (mime || '').split('/')[0];
if (prefix === 'image' || prefix === 'video' || prefix === 'audio') return prefix;
const ext = extFromName(name).toLowerCase();
if (mime === 'application/pdf' || DOCUMENT_EXT.includes(ext)) return 'document';
return 'file';
}
function FileCard({
name,
size,
@@ -9053,11 +9062,15 @@ function FileCard({
onOpen
}) {
const ext = extFromName(name);
const [, color] = FILE_TYPE_META[fileTypeFromMime(mime, name)];
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
type: "button",
className: 'library-file-card' + (removed ? ' removed' : ''),
onClick: removed ? undefined : onOpen,
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("span", {
style: {
backgroundColor: color
},
children: ext || (mime || '').split('/')[0].toUpperCase() || '?'
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("strong", {
@@ -9206,6 +9219,7 @@ function FavoriteItem({
}, void 0, false) : null, item.type === 'image' || item.type === 'video' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(MediaThumb, {
url: item._thumbUrl,
video: item.type === 'video',
duration: att && att.duration,
onOpen: onOpen,
removed: item.availability === 'removed'
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("footer", {
@@ -11688,9 +11702,10 @@ function GroupProfile({
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true), canEdit ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
onClick: () => navigate('group-nickname', topic),
disabled: !canEdit,
onClick: () => canEdit && navigate('group-nickname', topic),
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "群名称"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("small", {
@@ -11698,15 +11713,10 @@ function GroupProfile({
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "群名称"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("small", {
children: group.name
}, void 0, false)]
}, void 0, true), canEdit ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
onClick: () => navigate('group-description', topic),
disabled: !canEdit,
onClick: () => canEdit && navigate('group-description', topic),
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "群简介"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("small", {
@@ -11714,12 +11724,6 @@ function GroupProfile({
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "群简介"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("small", {
children: group.description || '群主很懒,还没有群简介'
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
className: "group-profile-qr",
@@ -11895,6 +11899,7 @@ function GroupQr({
const GOVERNANCE_FIELDS = [['join_approval', '进群验证', '开启后,他人申请加入群聊需群主或管理员同意'], ['mute_all', '全体禁言', '开启后,仅群主和管理员可以发言'], ['member_upload_files', '群成员文件上传', '关闭后,普通成员将无法上传群文件'], ['member_invite', '群成员邀请', '关闭后,仅群主和管理员可以邀请新成员进群'], ['member_private_chat', '群成员私信权限', '关闭后,不允许发送名片消息,且普通成员在群聊界面、群组信息内点击头像无反应。']];
function Governance({
topic,
myRole,
tinode,
onToast
}) {
@@ -11922,6 +11927,7 @@ function Governance({
}).catch(err => onToast(err.message || '设置失败'));
};
const dissolve = () => {
if (myRole !== 'owner') return;
if (!window.confirm('解散后群聊将无法恢复,确定要解散该群聊吗?')) return;
const t = tinode ? tinode.getTopic(topic) : null;
if (!t) return;
@@ -12014,7 +12020,7 @@ function Governance({
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
className: "group-danger",
disabled: dissolving,
disabled: dissolving || myRole !== 'owner',
onClick: dissolve,
children: "解散群组"
}, void 0, false)]
@@ -12147,7 +12153,7 @@ function GroupMembers({
setLoadError(true);
});
}, [topic]);
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
const roleLabel = role => role === 'owner' ? '群主' : role === 'admin' ? '管理员' : '';
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
@@ -12214,7 +12220,7 @@ function MemberPicker({
setLoadError(true);
});
}, [topic, mute]);
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
const toggle = id => setSelected(v => v.includes(id) ? v.filter(x => x != id) : v.concat(id));
const done = () => {
@@ -12380,7 +12386,7 @@ function OwnerSelect({
setLoadError(true);
});
}, [topic]);
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
const admins = visible.filter(m => m.role === 'admin');
const others = visible.filter(m => m.role !== 'admin');
const reset = () => {
@@ -12896,6 +12902,7 @@ function GroupManagementView({
onToast: setToast
}, void 0, false);else if (view === 'group-governance') content = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Governance, {
topic: topic,
myRole: myRole,
tinode: tinode,
onToast: setToast
}, void 0, false);else if (['group-admins', 'group-admin-success', 'group-admin-partial', 'group-admin-removed'].includes(view)) content = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(AdminList, {
@@ -13643,6 +13650,8 @@ function MessageActions() {
}, void 0, true);
}
const NOTIFICATION_TEXT = {
'group.created': '你创建了一个新群聊',
'group.join_request.created': '有人申请加入你管理的群聊',
'group.member.joined': '有成员加入了群聊',
'group.member.left': '有成员退出了群聊',
'group.member.updated': '群成员资料有更新',
@@ -15835,9 +15844,7 @@ function AppVersion({
onClick: () => onToast('隐私协议')
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(MenuRow, {
title: "联系我们",
onClick: () => {
window.location.href = _config_js__WEBPACK_IMPORTED_MODULE_4__.LINK_CONTACT_US;
}
onClick: () => onToast('联系我们')
}, void 0, false)]
}, void 0, true)]
}, void 0, true)]
@@ -22698,6 +22705,10 @@ class AudioPlayer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
playing: false
});
} else if (this.audioPlayer.readyState >= 2) {
if (activeAudioPlayer && activeAudioPlayer !== this) {
activeAudioPlayer.pauseForOtherPlayer();
}
activeAudioPlayer = this;
this.audioPlayer.play();
this.setState({
playing: true
@@ -22745,6 +22756,7 @@ class AudioPlayer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
}, void 0, false), play]
}, void 0, true) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.Fragment, {
children: [play, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
className: "playback-row",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("canvas", {
className: "playback",
ref: this.canvasRef,
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -3
View File
@@ -1969,7 +1969,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
const messageNodes = [];
let previousFrom = null;
let prevDate = null;
let chatBoxClass = null;
let chatBoxClass = groupTopic ? 'chat-box group' : 'chat-box';
topic.messages((msg, prev, next, i) => {
let nextFrom = next ? next.from || 'chan' : null;
let sequence = 'single';
@@ -1994,7 +1994,6 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
userName = user.public.fn;
userAvatar = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_17__.makeImageUrl)(user.public.photo);
}
chatBoxClass = groupTopic ? 'chat-box group' : 'chat-box';
const ref = this.getOrCreateMessageRef(msg.seq);
let replyToSeq = msg.head ? parseInt(msg.head.reply) : null;
if (!replyToSeq || isNaN(replyToSeq)) {
@@ -3175,7 +3174,7 @@ class MetaMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
className: "gray",
children: this.props.deletedText || '消息已删除'
}, void 0, false);
bubbleClass += ' date';
bubbleClass += ' deleted';
}
if (!content) {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_1__.jsxDEV)(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_1__.Fragment, {
File diff suppressed because one or more lines are too long
+9 -7
View File
@@ -352,15 +352,17 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
const resumeClass = 'material-icons ' + (this.state.enabled ? 'red' : 'gray');
if (this.props.holdMode) {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: 'audio audio-hold' + (this.props.cancelling ? ' cancelling' : ''),
children: [this.state.recording ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("canvas", {
className: 'compose-record-overlay' + (this.props.cancelling ? ' cancel' : ''),
role: "status",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("i", {
className: "material-icons",
children: this.props.cancelling ? 'delete_outline' : 'mic'
}, void 0, false), this.state.recording ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("canvas", {
ref: this.canvasRef
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "duration",
children: this.state.duration
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "audio-hold-hint",
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
children: this.props.cancelling ? '松开手指,取消发送' : '手指上滑,取消发送'
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
children: this.state.duration
}, void 0, false)]
}, void 0, true);
}
File diff suppressed because one or more lines are too long