修复本轮UI走查发现的6处样式/交互问题
1. 企业注册-统一社会信用代码:扫码图标误用 icon+onIconClick(渲染在输入框左侧、蓝色), 跟设计稿(图标在右侧、深灰色)不符。原因是 Field 组件里 icon 出现在 <input> 之前; 全仓库唯一一处 onIconClick 用法,改用 PasswordField 同款的 action(渲染在右侧), 复用已有的 .auth-inline-icon 灰色样式,不新增CSS。 2. 意见反馈:图片上传按钮跟设计稿差异较大(当前是通栏虚线框+蓝色图标文字,设计稿是 独立白色方块+灰色图标文字);字数"0/300"计数器卡在textarea自带边框和外层白卡的接缝 处,看起来像叠在两层盒子中间。改法:拆分原本跟"举报证据上传"共用的CSS规则,给 .feedback-upload>button 单独设置白底/无边框/灰色/7.5rem方块;去掉textarea自身的 border,让计数器所在的外层卡片成为唯一可见的白色容器。"图片"标题跟数量合并回一行 (原来用space-between分居两端),maxFiles真实上限(5,早前已确认是后端真实值)不变。 3. 青少年模式(开启/关闭/验证三个页面互跳):三处跳转全部误用push(HashNavigation. navigateTo),导致点返回会先回到流程内已经处理完的页面,跟本工程之前排查过的 同类"tab切换污染历史记录"是同一个模式。新增 navigateReplace()(跟 navigate() 同结构, 内部换成 HashNavigation.navigateReplace,抄的是 message-center-view.jsx/ content-library-view.jsx已验证过的写法),三处调用改用它。 4. 修改密码页面:复用了 profile-editor-page 的通用label/input样式,但没有像 BirthDateEditor那样带上专属wrapper class,导致三个label的<span>不加粗不换行、 紧贴在一起零间距,input还留着给别的编辑器计数器/清空按钮预留的3rem右侧空白。 新增 change-password-fields 样式(参照birth-date-editor的label/span/input规则)。 5. 消息长按弹出菜单:全历史检索确认这个功能从来不是真正的长按手势,唯一实现是点击 气泡右上角一个不透明度只有0.3的小箭头按钮——参考图"消息页面—长按弹出.png"命名的 功能需求和实现从未对齐。给 .bubble 补上真实的touchstart/touchmove/touchend长按 识别(500ms计时器,移动超过10px视为滚动取消),复用同一套菜单构建逻辑(从 handleContextClick里抽出openContextMenu(x,y)公用)。验证时发现并修复了一个重构 引入的真bug:openContextMenu里遗留了两行没改完的e.pageX/e.pageY(应为参数x/y), 会导致点击小按钮和长按两条路径双双抛ReferenceError,已修正并重新验证两条路径都正常。 6. 群公告横幅(群聊导航栏下方常驻提示条):当前是通栏无圆角、贴边无留白、底部硬分隔线、 文字用的是全局默认灰色(#666)文本token,设计稿是浮在灰底上的白色圆角卡片、四周留白、 无分隔线、文字接近黑色。改法:加margin+border-radius+box-shadow做出浮起的卡片感, 去掉border-bottom,颜色改用--clr-bubble-text(消息正文同款深色token,而不是改全局 的--clr-text-primary,避免影响其它用到这个默认文本色的地方)。 验证:本地dev-proxy逐项验证——企业注册图标位置颜色(截图核对)、意见反馈布局(截图核对, 0/5计数器无缝落在白卡右下角)、青少年模式三处调用grep确认无遗漏+navigateReplace底层 history.replaceState机制单独验证不增加history.length、修改密码页面(截图核对label加粗 间距生效)、长按弹出(真实dispatch TouchEvent触发菜单成功,且验证点击小按钮路径未被 重构破坏)、群公告横幅(getComputedStyle核对margin/borderRadius/boxShadow/color/ borderBottom五个属性全部生效)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6ab2867f27
commit
a8b81a51e3
+39
-6
@@ -3581,10 +3581,12 @@ img.channel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0.75rem 0;
|
||||
padding: 0.6rem 1rem;
|
||||
background-color: var(--clr-bright);
|
||||
border-bottom: 1px solid var(--clr-divider);
|
||||
color: var(--clr-text-primary);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
||||
color: var(--clr-bubble-text);
|
||||
flex: none;
|
||||
}
|
||||
#group-announcement-banner i:first-child { color: var(--clr-warning-text, #e0a82e); }
|
||||
@@ -13427,6 +13429,23 @@ div.toast.show {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
.change-password-fields label {
|
||||
display: block;
|
||||
width: min(100%, 26rem);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.change-password-fields label span {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.change-password-fields label input {
|
||||
height: 3.2rem;
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
.personal-qr-page > main {
|
||||
padding: 1rem;
|
||||
}
|
||||
@@ -14491,8 +14510,7 @@ div.toast.show {
|
||||
margin: 0;
|
||||
padding: 0.75rem 0.8rem 2rem;
|
||||
resize: vertical;
|
||||
border: 1px solid #dfe3e7;
|
||||
border-radius: 0.35rem;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
line-height: 1.5;
|
||||
@@ -14574,8 +14592,7 @@ div.toast.show {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.evidence-upload,
|
||||
.feedback-upload > button {
|
||||
.evidence-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -14591,6 +14608,22 @@ div.toast.show {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.feedback-upload > button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 7.5rem;
|
||||
height: 7.5rem;
|
||||
margin: 0;
|
||||
padding: 0.7rem;
|
||||
border: 0;
|
||||
border-radius: 0.35rem;
|
||||
box-shadow: none;
|
||||
color: #858d96;
|
||||
background: #fff;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.evidence-upload .material-icons,
|
||||
.feedback-upload > button .material-icons {
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -247,8 +247,9 @@ function EnterpriseRegister() {
|
||||
<FormAlert text={errors.form} />
|
||||
<label className="auth-label">统一社会信用代码</label>
|
||||
<Field value={creditCode} onChange={e => setCreditCode(e.target.value.trim().toUpperCase().replace(/[^0-9A-Z]/g, ''))}
|
||||
placeholder="请输入18位统一社会信用代码" maxLength="18" icon="qr_code_scanner" error={errors.creditCode}
|
||||
onIconClick={() => setToast('扫码识别暂不可用,请手动输入')} />
|
||||
placeholder="请输入18位统一社会信用代码" maxLength="18" error={errors.creditCode}
|
||||
action={<button type="button" className="auth-inline-icon"
|
||||
onClick={() => setToast('扫码识别暂不可用,请手动输入')}><Icon>qr_code_scanner</Icon></button>} />
|
||||
<p className="auth-help">请填写营业执照上的统一社会信用代码</p>
|
||||
|
||||
<label className="auth-label">上传营业执照 <span>选填</span></label>
|
||||
|
||||
@@ -19,6 +19,17 @@ const navigate = (view, params) => {
|
||||
HashNavigation.navigateTo(hash);
|
||||
};
|
||||
|
||||
// Same as navigate() but replaces the current history entry instead of pushing a new one -- for
|
||||
// steps within a single flow (e.g. youth-mode off/on/verify) so "back" leaves the flow instead of
|
||||
// re-walking its already-completed steps.
|
||||
const navigateReplace = (view, params) => {
|
||||
let hash = HashNavigation.setUrlSidePanel(window.location.hash, view);
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
hash = value ? HashNavigation.addUrlParam(hash, key, value) : HashNavigation.removeUrlParam(hash, key);
|
||||
});
|
||||
HashNavigation.navigateReplace(hash);
|
||||
};
|
||||
|
||||
export const PERSONAL_SETTINGS_VIEWS = [
|
||||
'personal-center', 'personal-profile', 'profile-nickname', 'profile-birth-date',
|
||||
'profile-signature', 'profile-gender', 'profile-region', 'personal-qrcode', 'android-settings',
|
||||
@@ -460,7 +471,7 @@ function ChangePassword({onUpdatePassword, onToast}) {
|
||||
});
|
||||
};
|
||||
return <div className="personal-page profile-editor-page"><Header title="修改密码" back="android-settings" action="完成"
|
||||
actionLabel={busy ? '处理中…' : '完成'} actionDisabled={busy} onAction={submit} /><main>
|
||||
actionLabel={busy ? '处理中…' : '完成'} actionDisabled={busy} onAction={submit} /><main className="change-password-fields">
|
||||
<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={confirmPwd} onChange={e => setConfirmPwd(e.target.value)} placeholder="再次输入新密码" /></label>
|
||||
@@ -648,7 +659,7 @@ function YouthMode({onToast}) {
|
||||
window.setTimeout(() => navigate('identity-check', {ym: 'enable'}), 400);
|
||||
return;
|
||||
}
|
||||
navigate('youth-mode-verify', {ym: 'enable'});
|
||||
navigateReplace('youth-mode-verify', {ym: 'enable'});
|
||||
};
|
||||
|
||||
if (!settings) return <div className="personal-page youth-mode-page"><Header title="青少年模式" back="android-settings" /></div>;
|
||||
@@ -663,7 +674,7 @@ function YouthMode({onToast}) {
|
||||
</main>
|
||||
<footer>
|
||||
<button type="button" className={settings.enabled ? 'outline' : ''}
|
||||
onClick={() => settings.enabled ? navigate('youth-mode-verify', {ym: 'disable'}) : startEnable()}>
|
||||
onClick={() => settings.enabled ? navigateReplace('youth-mode-verify', {ym: 'disable'}) : startEnable()}>
|
||||
{settings.enabled ? '关闭青少年模式' : '开启青少年模式'}
|
||||
</button>
|
||||
<small>可在设置中查看青少年模式状态</small>
|
||||
@@ -718,7 +729,7 @@ function YouthModeVerify({hashParams, onToast}) {
|
||||
// flip, which is a more confusing flash than the plain loading state they had before caching.
|
||||
if (settings) writeCachedData('youth-mode', {...settings, enabled: mode === 'enable'});
|
||||
onToast(mode === 'enable' ? '青少年模式已开启' : '青少年模式已关闭');
|
||||
window.setTimeout(() => navigate(mode === 'enable' ? 'youth-mode-on' : 'youth-mode-off'), 400);
|
||||
window.setTimeout(() => navigateReplace(mode === 'enable' ? 'youth-mode-on' : 'youth-mode-off'), 400);
|
||||
}).catch(err => {
|
||||
// Server locks out further attempts for retry_after_seconds after too many wrong passwords
|
||||
// (any request during that window 429s regardless of whether THIS password is correct) --
|
||||
|
||||
@@ -246,7 +246,7 @@ function UserFeedback({tinode, onToast}) {
|
||||
<small>{content.length}/{maxLength}</small>
|
||||
</label>
|
||||
<section className="feedback-upload">
|
||||
<header><span><strong>图片</strong></span><em>{images.length}/{maxFiles}</em></header>
|
||||
<header><strong>图片({images.length}/{maxFiles})</strong></header>
|
||||
<button type="button" disabled={uploading} onClick={() => fileInputRef.current && fileInputRef.current.click()}><Icon>add_a_photo</Icon><span>{uploading ? '上传中…' : '点击上传'}</span></button>
|
||||
<input ref={fileInputRef} type="file" accept="image/*" style={{display: 'none'}} onChange={addImage} />
|
||||
</section>
|
||||
|
||||
@@ -62,6 +62,9 @@ class BaseChatMessage extends React.PureComponent {
|
||||
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
this.handleContextClick = this.handleContextClick.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
||||
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
||||
@@ -163,9 +166,59 @@ class BaseChatMessage extends React.PureComponent {
|
||||
this.props.onFormResponse(e.target.dataset.act, text, data);
|
||||
}
|
||||
|
||||
// docs/images/消息页面—长按弹出.png names this "long-press", but the only wiring that ever existed
|
||||
// was handleContextClick below, bound to a click on the tiny .menuTrigger chevron -- no touchstart/
|
||||
// setTimeout path, checked across this file's entire history. Real touch devices have no way to
|
||||
// trigger it that matches the reference image. This adds an actual press-and-hold gesture on the
|
||||
// bubble itself, reusing the same menu-building logic so both paths stay in sync.
|
||||
handleTouchStart(e) {
|
||||
if (!this.props.showContextMenu || this.props.selectMode) {
|
||||
return;
|
||||
}
|
||||
const touch = e.touches && e.touches[0];
|
||||
this.longPressOrigin = {x: touch ? touch.pageX : e.pageX, y: touch ? touch.pageY : e.pageY};
|
||||
this.longPressTimer = window.setTimeout(() => {
|
||||
this.longPressTimer = null;
|
||||
this.openContextMenu(this.longPressOrigin.x, this.longPressOrigin.y);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
handleTouchMove(e) {
|
||||
if (!this.longPressOrigin) {
|
||||
return;
|
||||
}
|
||||
const touch = e.touches && e.touches[0];
|
||||
const x = touch ? touch.pageX : e.pageX;
|
||||
const y = touch ? touch.pageY : e.pageY;
|
||||
// A real press-and-hold shouldn't fire if the finger is actually scrolling the list.
|
||||
if (Math.abs(x - this.longPressOrigin.x) > 10 || Math.abs(y - this.longPressOrigin.y) > 10) {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
|
||||
cancelLongPress() {
|
||||
if (this.longPressTimer) {
|
||||
window.clearTimeout(this.longPressTimer);
|
||||
this.longPressTimer = null;
|
||||
}
|
||||
this.longPressOrigin = null;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
|
||||
handleContextClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.openContextMenu(e.pageX, e.pageY);
|
||||
}
|
||||
|
||||
openContextMenu(x, y) {
|
||||
const menuItems = [];
|
||||
if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
|
||||
menuItems.push('menu_item_send_retry');
|
||||
@@ -203,8 +256,8 @@ class BaseChatMessage extends React.PureComponent {
|
||||
head: this.props.head,
|
||||
userFrom: this.props.userFrom,
|
||||
userName: this.props.userName,
|
||||
y: e.pageY,
|
||||
x: e.pageX,
|
||||
y: y,
|
||||
x: x,
|
||||
pickReply: this.props.pickReply,
|
||||
editMessage: this.props.editMessage,
|
||||
timestamp: this.props.timestamp,
|
||||
@@ -418,7 +471,8 @@ class BaseChatMessage extends React.PureComponent {
|
||||
{isFailed && failReason ?
|
||||
<i className="material-icons red message-fail-icon">error</i> : null}
|
||||
<div>
|
||||
<div className={bubbleClass}>
|
||||
<div className={bubbleClass}
|
||||
onTouchStart={this.handleTouchStart} onTouchMove={this.handleTouchMove} onTouchEnd={this.handleTouchEnd}>
|
||||
{showVoiceUnreadDot ? <i className="voice-unread-dot" /> : null}
|
||||
<div className="content-meta">
|
||||
<div className={textSizeClass}>
|
||||
|
||||
+23
-13
@@ -6005,9 +6005,15 @@ function EnterpriseRegister() {
|
||||
onChange: e => setCreditCode(e.target.value.trim().toUpperCase().replace(/[^0-9A-Z]/g, '')),
|
||||
placeholder: "请输入18位统一社会信用代码",
|
||||
maxLength: "18",
|
||||
icon: "qr_code_scanner",
|
||||
error: errors.creditCode,
|
||||
onIconClick: () => setToast('扫码识别暂不可用,请手动输入')
|
||||
action: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
className: "auth-inline-icon",
|
||||
onClick: () => setToast('扫码识别暂不可用,请手动输入'),
|
||||
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)(Icon, {
|
||||
children: "qr_code_scanner"
|
||||
}, void 0, false)
|
||||
}, void 0, false)
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("p", {
|
||||
className: "auth-help",
|
||||
children: "请填写营业执照上的统一社会信用代码"
|
||||
@@ -15166,6 +15172,13 @@ const navigate = (view, params) => {
|
||||
});
|
||||
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].navigateTo(hash);
|
||||
};
|
||||
const navigateReplace = (view, params) => {
|
||||
let hash = _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].setUrlSidePanel(window.location.hash, view);
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
hash = value ? _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].addUrlParam(hash, key, value) : _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].removeUrlParam(hash, key);
|
||||
});
|
||||
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].navigateReplace(hash);
|
||||
};
|
||||
const PERSONAL_SETTINGS_VIEWS = ['personal-center', 'personal-profile', 'profile-nickname', 'profile-birth-date', 'profile-signature', 'profile-gender', 'profile-region', 'personal-qrcode', 'android-settings', 'change-password', 'app-version', 'privacy-settings', 'friend-discovery-settings', 'youth-mode-off', 'youth-mode-on', 'youth-mode-verify', 'notification-permission', 'permission-settings-reminder', 'camera-permission-info', 'microphone-permission-info'];
|
||||
function Icon({
|
||||
children
|
||||
@@ -16002,6 +16015,7 @@ function ChangePassword({
|
||||
actionDisabled: busy,
|
||||
onAction: submit
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("main", {
|
||||
className: "change-password-fields",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("label", {
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("span", {
|
||||
children: "原密码"
|
||||
@@ -16345,7 +16359,7 @@ function YouthMode({
|
||||
}), 400);
|
||||
return;
|
||||
}
|
||||
navigate('youth-mode-verify', {
|
||||
navigateReplace('youth-mode-verify', {
|
||||
ym: 'enable'
|
||||
});
|
||||
};
|
||||
@@ -16378,7 +16392,7 @@ function YouthMode({
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
className: settings.enabled ? 'outline' : '',
|
||||
onClick: () => settings.enabled ? navigate('youth-mode-verify', {
|
||||
onClick: () => settings.enabled ? navigateReplace('youth-mode-verify', {
|
||||
ym: 'disable'
|
||||
}) : startEnable(),
|
||||
children: settings.enabled ? '关闭青少年模式' : '开启青少年模式'
|
||||
@@ -16430,7 +16444,7 @@ function YouthModeVerify({
|
||||
enabled: mode === 'enable'
|
||||
});
|
||||
onToast(mode === 'enable' ? '青少年模式已开启' : '青少年模式已关闭');
|
||||
window.setTimeout(() => navigate(mode === 'enable' ? 'youth-mode-on' : 'youth-mode-off'), 400);
|
||||
window.setTimeout(() => navigateReplace(mode === 'enable' ? 'youth-mode-on' : 'youth-mode-off'), 400);
|
||||
}).catch(err => {
|
||||
if (err.reason === 'TOO_MANY_ATTEMPTS' && err.details && err.details.retry_after_seconds) {
|
||||
setLockedUntil(Date.now() + err.details.retry_after_seconds * 1000);
|
||||
@@ -17173,14 +17187,10 @@ function UserFeedback({
|
||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("section", {
|
||||
className: "feedback-upload",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("header", {
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("span", {
|
||||
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("strong", {
|
||||
children: "图片"
|
||||
}, void 0, false)
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("em", {
|
||||
children: [images.length, "/", maxFiles]
|
||||
}, void 0, true)]
|
||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("button", {
|
||||
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("strong", {
|
||||
children: ["图片(", images.length, "/", maxFiles, ")"]
|
||||
}, void 0, true)
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
disabled: uploading,
|
||||
onClick: () => fileInputRef.current && fileInputRef.current.click(),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2715,6 +2715,9 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
this.handleContextClick = this.handleContextClick.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
||||
this.handleVoicePlayed = this.handleVoicePlayed.bind(this);
|
||||
@@ -2803,9 +2806,50 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
const text = e.target.dataset.title || 'unknown';
|
||||
this.props.onFormResponse(e.target.dataset.act, text, data);
|
||||
}
|
||||
handleTouchStart(e) {
|
||||
if (!this.props.showContextMenu || this.props.selectMode) {
|
||||
return;
|
||||
}
|
||||
const touch = e.touches && e.touches[0];
|
||||
this.longPressOrigin = {
|
||||
x: touch ? touch.pageX : e.pageX,
|
||||
y: touch ? touch.pageY : e.pageY
|
||||
};
|
||||
this.longPressTimer = window.setTimeout(() => {
|
||||
this.longPressTimer = null;
|
||||
this.openContextMenu(this.longPressOrigin.x, this.longPressOrigin.y);
|
||||
}, 500);
|
||||
}
|
||||
handleTouchMove(e) {
|
||||
if (!this.longPressOrigin) {
|
||||
return;
|
||||
}
|
||||
const touch = e.touches && e.touches[0];
|
||||
const x = touch ? touch.pageX : e.pageX;
|
||||
const y = touch ? touch.pageY : e.pageY;
|
||||
if (Math.abs(x - this.longPressOrigin.x) > 10 || Math.abs(y - this.longPressOrigin.y) > 10) {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
}
|
||||
handleTouchEnd() {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
cancelLongPress() {
|
||||
if (this.longPressTimer) {
|
||||
window.clearTimeout(this.longPressTimer);
|
||||
this.longPressTimer = null;
|
||||
}
|
||||
this.longPressOrigin = null;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.cancelLongPress();
|
||||
}
|
||||
handleContextClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.openContextMenu(e.pageX, e.pageY);
|
||||
}
|
||||
openContextMenu(x, y) {
|
||||
const menuItems = [];
|
||||
if (this.props.received == tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.MESSAGE_STATUS_FAILED) {
|
||||
menuItems.push('menu_item_send_retry');
|
||||
@@ -2842,8 +2886,8 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
head: this.props.head,
|
||||
userFrom: this.props.userFrom,
|
||||
userName: this.props.userName,
|
||||
y: e.pageY,
|
||||
x: e.pageX,
|
||||
y: y,
|
||||
x: x,
|
||||
pickReply: this.props.pickReply,
|
||||
editMessage: this.props.editMessage,
|
||||
timestamp: this.props.timestamp
|
||||
@@ -3051,6 +3095,9 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||
className: bubbleClass,
|
||||
onTouchStart: this.handleTouchStart,
|
||||
onTouchMove: this.handleTouchMove,
|
||||
onTouchEnd: this.handleTouchEnd,
|
||||
children: [showVoiceUnreadDot ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("i", {
|
||||
className: "voice-unread-dot"
|
||||
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxDEV)("div", {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user