修复用户实测反馈的4处问题:品牌名泄露、修改密码页注释泄露、安全提醒页未居中、群相关行间距

- 品牌名:BASE_APP_NAME 从 "TinodeWeb" 改为 "MeetingIM"(关于页/青少年模式文案不再暴露 Tinode 字样);
  logo-view.jsx(未选中会话时的空状态)去掉开源模板遗留的 Tinode 主页/GitHub 链接/原始版本号,
  换成品牌图标+"选择一个会话开始聊天"
- personal-settings-view.jsx:删除 ChangePassword 页面里被误当正文渲染的开发注释(提及 Tinode 协议),
  改回真正的代码注释
- base.css:.risk-info-page(安全提醒整页)补 margin:0 auto——原来复用模态弹窗的 .risk-dialog 样式,
  宽屏下整块内容贴左显示,没有居中
- base.css:.group-member-picker/.group-settings-header/.group-member-section-title 的 strong/small
  漏加入既有的 line-height:1.3 覆盖名单,继承通用 button{line-height:2.2rem},导致群成员列表/群聊设置
  头部的姓名与角色/简介文字行间距异常大、贴底部分割线过近

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-05 13:06:18 +08:00
co-authored by Claude Sonnet 5
parent bede45fd7f
commit f2218a822e
10 changed files with 83 additions and 75 deletions
+42 -12
View File
@@ -3955,12 +3955,29 @@ img.channel {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: var(--clr-cyan-50); background-color: var(--clr-cyan-50);
}
#dummy-view a {
text-decoration: none;
display: block;
text-align: center; text-align: center;
} }
#dummy-view-icon {
display: grid;
place-items: center;
width: 6.5rem;
height: 6.5rem;
margin: 0 auto 0.8rem;
border-radius: 1.2rem;
color: #fff;
background: #2878d6;
}
#dummy-view-icon .material-icons {
font-size: 4.2rem;
}
#dummy-view h2 {
margin: 0;
font-size: 1.25rem;
}
#dummy-view p {
margin: 0.25rem 0 0;
color: #777f89;
}
#image-preview { #image-preview {
position: absolute; position: absolute;
@@ -9164,7 +9181,8 @@ div.toast.show {
.group-list strong, .group-list strong,
.group-request-list strong, .group-request-list strong,
.group-admin-page strong, .group-admin-page strong,
.group-muted-list strong { .group-muted-list strong,
.group-member-picker strong {
line-height: 1.3; line-height: 1.3;
} }
@@ -9176,7 +9194,8 @@ div.toast.show {
.group-list small, .group-list small,
.group-request-list small, .group-request-list small,
.group-admin-page small, .group-admin-page small,
.group-muted-list small { .group-muted-list small,
.group-member-picker small {
margin-top: 0.2rem; margin-top: 0.2rem;
color: #9299a2; color: #9299a2;
font-size: 0.75rem; font-size: 0.75rem;
@@ -9882,7 +9901,11 @@ div.toast.show {
自己的 place-items:center 就只剩交叉轴(水平)生效,主轴(垂直)退回 flex-start, 自己的 place-items:center 就只剩交叉轴(水平)生效,主轴(垂直)退回 flex-start,
里面的图标会贴着方块顶部而不是居中。 */ 里面的图标会贴着方块顶部而不是居中。 */
.group-settings-header > span:not(.group-avatar) { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; } .group-settings-header > span:not(.group-avatar) { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.group-settings-header small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #9299a2; } /* Same generic `button { line-height: 2.2rem }` leak as .group-member-picker's rows (see its
comment) -- name and description each sat in a ~29px box instead of hugging their own text,
reading as far more gap between the two lines than the .15rem `gap` above actually specifies. */
.group-settings-header strong { line-height: 1.3; }
.group-settings-header small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #9299a2; line-height: 1.3; }
.group-list > button { .group-list > button {
display: grid; display: grid;
@@ -10223,11 +10246,13 @@ div.toast.show {
.group-member-section-title > strong { .group-member-section-title > strong {
color: #30353c; color: #30353c;
font-size: 1rem; font-size: 1rem;
line-height: 1.3;
} }
.group-member-section-title > small { .group-member-section-title > small {
margin-left: auto; margin-left: auto;
color: #949ba4; color: #949ba4;
line-height: 1.3;
} }
.group-settings section > .group-member-grid { .group-settings section > .group-member-grid {
@@ -10540,11 +10565,6 @@ div.toast.show {
flex-direction: column; flex-direction: column;
} }
.group-member-picker small {
margin-top: 0.2rem;
color: #9299a2;
}
.group-check { .group-check {
display: grid; display: grid;
place-items: center; place-items: center;
@@ -13808,6 +13828,16 @@ div.toast.show {
text-align: center; text-align: center;
} }
/* .risk-dialog was designed as a modal card, centered by .risk-overlay's place-items:center wrapper
(RiskConfirmDialog). FraudAwareness reuses the same class directly as .support-page's <main> (no
overlay wrapper, no explicit height on the page unlike a modal) to pick up its ul/li/aside/button
styling -- but .support-page is a plain flex column with no centering of its own, so the capped
width left the whole page pinned to the left edge on anything wider than ~25rem instead of centered
like every other support-page/group-page/people-page in the app. */
.risk-info-page {
margin: 0 auto;
}
.risk-symbol { .risk-symbol {
display: grid; display: grid;
place-items: center; place-items: center;
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -1,7 +1,8 @@
import { PACKAGE_VERSION } from './version.js'; import { PACKAGE_VERSION } from './version.js';
// Version-independent name of this application, used for Firebase app name. // Version-independent name of this application, used for Firebase app name and shown to users
export const BASE_APP_NAME = 'TinodeWeb'; // (about page, youth-mode copy).
export const BASE_APP_NAME = 'MeetingIM';
// Name of this application, used in the User-Agent. // Name of this application, used in the User-Agent.
export const APP_NAME = BASE_APP_NAME + '/' + (PACKAGE_VERSION || '0.25'); export const APP_NAME = BASE_APP_NAME + '/' + (PACKAGE_VERSION || '0.25');
+6 -13
View File
@@ -1,25 +1,18 @@
/* This is just a static page to display when no conversation is selected. */ /* Static placeholder shown in the message pane when no conversation is selected. */
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Tinode } from 'tinode-sdk'; import { BASE_APP_NAME } from '../config.js';
import { APP_NAME } from '../config.js'; function Icon({children}) { return <i className="material-icons" aria-hidden="true">{children}</i>; }
export default class LogoView extends React.PureComponent { export default class LogoView extends React.PureComponent {
render() { render() {
const version = APP_NAME + ' (' + Tinode.getLibrary() + ')';
return ( return (
<div id="dummy-view"> <div id="dummy-view">
<div> <div>
<a href="https://github.com/tinode/chat/"> <span id="dummy-view-icon"><Icon>forum</Icon></span>
<img id="logo" alt="logo" src="img/logo.svg" /> <h2>{BASE_APP_NAME}</h2>
<h2>Tinode Web</h2> <p>选择一个会话开始聊天</p>
</a>
<p><FormattedMessage id="label_client" defaultMessage="Client:"
description="Label for a client version" /> {version}</p>
<p><FormattedMessage id="label_server" defaultMessage="Server:"
description="Label for a server version" /> {this.props.serverVersion} ({this.props.serverAddress})</p>
</div> </div>
</div> </div>
); );
+1 -3
View File
@@ -2127,9 +2127,7 @@ class MessagesView extends React.Component {
let component; let component;
if (!this.state.topic) { if (!this.state.topic) {
component = <LogoView component = <LogoView />;
serverVersion={this.props.serverVersion}
serverAddress={this.props.serverAddress} />;
} else { } else {
let component2; let component2;
+2 -1
View File
@@ -395,6 +395,8 @@ function ClearHistoryConfirm({tinode, chatList, onToast}) {
// (Tinode.updateAccountBasic), already wired end-to-end via tinode-web.jsx's // (Tinode.updateAccountBasic), already wired end-to-end via tinode-web.jsx's
// handleUpdatePasswordRequest/onUpdatePassword prop (used today by the old acc-security-view.jsx). // handleUpdatePasswordRequest/onUpdatePassword prop (used today by the old acc-security-view.jsx).
// Reusing that verified callback here instead of reimplementing the WS call. // Reusing that verified callback here instead of reimplementing the WS call.
// The server-side account-update call never checks the caller's current password -- "原密码" below
// is a frontend-only speed bump against fat-fingering this field, not a real credential check.
function ChangePassword({onUpdatePassword, onToast}) { function ChangePassword({onUpdatePassword, onToast}) {
const [oldPwd, setOldPwd] = React.useState(''); const [oldPwd, setOldPwd] = React.useState('');
const [newPwd, setNewPwd] = React.useState(''); const [newPwd, setNewPwd] = React.useState('');
@@ -421,7 +423,6 @@ function ChangePassword({onUpdatePassword, onToast}) {
<label><span>原密码</span><input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} placeholder="请输入当前密码" /></label> <label><span>原密码</span><input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} placeholder="请输入当前密码" /></label>
<label><span>新密码</span><input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} placeholder="至少6位" /></label> <label><span>新密码</span><input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} placeholder="至少6位" /></label>
<label><span>确认新密码</span><input type="password" value={confirmPwd} onChange={e => setConfirmPwd(e.target.value)} placeholder="再次输入新密码" /></label> <label><span>确认新密码</span><input type="password" value={confirmPwd} onChange={e => setConfirmPwd(e.target.value)} placeholder="再次输入新密码" /></label>
<p>Tinode 协议本身不会在服务端校验"原密码",这里的原密码输入是前端自己加的一道二次确认,防止误触。</p>
</main></div>; </main></div>;
} }
+2 -4
View File
@@ -2358,7 +2358,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ }); /* harmony export */ });
/* harmony import */ var _version_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./version.js */ "./src/version.js"); /* harmony import */ var _version_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./version.js */ "./src/version.js");
const BASE_APP_NAME = 'TinodeWeb'; const BASE_APP_NAME = 'MeetingIM';
const APP_NAME = BASE_APP_NAME + '/' + (_version_js__WEBPACK_IMPORTED_MODULE_0__.PACKAGE_VERSION || '0.25'); const APP_NAME = BASE_APP_NAME + '/' + (_version_js__WEBPACK_IMPORTED_MODULE_0__.PACKAGE_VERSION || '0.25');
const API_KEY = "AQAAAAABAAAoeOI7tA3HsYvdzDhYhZJy" || 0; const API_KEY = "AQAAAAABAAAoeOI7tA3HsYvdzDhYhZJy" || 0;
const KNOWN_HOSTS = { const KNOWN_HOSTS = {
@@ -15741,9 +15741,7 @@ function ChangePassword({
onChange: e => setConfirmPwd(e.target.value), onChange: e => setConfirmPwd(e.target.value),
placeholder: "再次输入新密码" placeholder: "再次输入新密码"
}, void 0, false)] }, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("p", { }, void 0, true)]
children: "Tinode 协议本身不会在服务端校验\"原密码\",这里的原密码输入是前端自己加的一道二次确认,防止误触。"
}, void 0, false)]
}, void 0, true)] }, void 0, true)]
}, void 0, true); }, void 0, true);
} }
+1 -1
View File
File diff suppressed because one or more lines are too long
+24 -37
View File
@@ -13,45 +13,35 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ }); /* harmony export */ });
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react"); /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); /* 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 _config_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js");
/* 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");
function Icon({
children
}) {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("i", {
className: "material-icons",
"aria-hidden": "true",
children: children
}, void 0, false);
}
class LogoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) { class LogoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
render() { render() {
const version = _config_js__WEBPACK_IMPORTED_MODULE_3__.APP_NAME + ' (' + tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.getLibrary() + ')'; return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("div", {
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", {
id: "dummy-view", id: "dummy-view",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("div", { children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("a", { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("span", {
href: "https://github.com/tinode/chat/", id: "dummy-view-icon",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("img", { children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)(Icon, {
id: "logo", children: "forum"
alt: "logo", }, void 0, false)
src: "img/logo.svg" }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("h2", {
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("h2", { children: _config_js__WEBPACK_IMPORTED_MODULE_1__.BASE_APP_NAME
children: "Tinode Web" }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__.jsxDEV)("p", {
}, void 0, false)] children: "选择一个会话开始聊天"
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("p", { }, void 0, false)]
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_client",
defaultMessage: "Client:",
description: "Label for a client version"
}, void 0, false), " ", version]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("p", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_server",
defaultMessage: "Server:",
description: "Label for a server version"
}, void 0, false), " ", this.props.serverVersion, " (", this.props.serverAddress, ")"]
}, void 0, true)]
}, void 0, true) }, void 0, true)
}, void 0, false); }, void 0, false);
} }
@@ -1841,10 +1831,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
}, void 0, false); }, void 0, false);
let component; let component;
if (!this.state.topic) { if (!this.state.topic) {
component = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_22__.jsxDEV)(_logo_view_jsx__WEBPACK_IMPORTED_MODULE_10__["default"], { component = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_22__.jsxDEV)(_logo_view_jsx__WEBPACK_IMPORTED_MODULE_10__["default"], {}, void 0, false);
serverVersion: this.props.serverVersion,
serverAddress: this.props.serverAddress
}, void 0, false);
} else { } else {
let component2; let component2;
if (this.state.imagePreview) { if (this.state.imagePreview) {
File diff suppressed because one or more lines are too long