第七轮深度测试:修复7个agent发现的12处前端问题,重点是列表分页截断与呼叫/播放状态机
- 通话状态机(Ch8):canStartCall 补齐通话中重复发起的防护,callShouldStart 补齐放弃/失败路径的清理 - 分页截断(Ch5-6):新增 fetchAllPages 辅助函数,修复通讯录/黑名单/好友申请/群成员/管理员/禁言等 11 处列表只读首页导致真实成员被遗漏的问题;群角色判断改用不受分页影响的 getGroup().my_role - 消息与媒体(Ch3-4/Ch7/Ch9):录音超时污染时长、手动重试丢失 head 导致消息被错误分类、批量删除 未取消上传中附件、语音播放器补齐同时只能播一条、workplace-collaboration-view 冗余重试请求 - 独立密码/青少年模式(Ch10-11):设置密码后按调用来源判断是否应自动开启青少年模式,避免关闭流程 中途"忘记密码"被误当成开启;注销账号确认文案补充群主后果说明 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f42b8536e5
commit
bede45fd7f
@@ -181,6 +181,27 @@ export function translateReason(reason, details) {
|
|||||||
|
|
||||||
const idem = () => ({'Idempotency-Key': requestId()});
|
const idem = () => ({'Idempotency-Key': requestId()});
|
||||||
|
|
||||||
|
// Most list endpoints here cap out at 20-50 items per page (some as low as 30, e.g. group
|
||||||
|
// members against a 128-member cap) and round7 testing found several call sites only ever
|
||||||
|
// fetched the first page -- for things like "am I this group's owner/admin" or the admin
|
||||||
|
// candidate list, a real owner/admin sitting past page 1 went undetected entirely rather than
|
||||||
|
// just showing an incomplete list. Empty/repeated next_cursor means done; this app's list
|
||||||
|
// responses haven't been observed to send a separate has_more that disagrees with that.
|
||||||
|
export async function fetchAllPages(fetchPage, startCursor = '') {
|
||||||
|
let cursor = startCursor;
|
||||||
|
let items = [];
|
||||||
|
for (;;) {
|
||||||
|
const page = await fetchPage(cursor);
|
||||||
|
items = items.concat((page && page.items) || []);
|
||||||
|
const nextCursor = (page && page.next_cursor) || '';
|
||||||
|
if (!nextCursor || nextCursor === cursor) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor = nextCursor;
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
export const imGet = (path, opts) => imRequest(path, opts);
|
export const imGet = (path, opts) => imRequest(path, opts);
|
||||||
export const imPost = (path, body, opts = {}) => imRequest(path, {method: 'POST', body, headers: idem(), ...opts});
|
export const imPost = (path, body, opts = {}) => imRequest(path, {method: 'POST', body, headers: idem(), ...opts});
|
||||||
export const imPatch = (path, body, opts = {}) => imRequest(path, {method: 'PATCH', body, headers: idem(), ...opts});
|
export const imPatch = (path, body, opts = {}) => imRequest(path, {method: 'PATCH', body, headers: idem(), ...opts});
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ const INVITE_RE = /^[A-Za-z]{5}$/;
|
|||||||
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
||||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
const navigate = view => HashNavigation.navigateTo(view);
|
// params is optional and additive only -- every existing call site that passes just `view` keeps
|
||||||
|
// producing the exact same bare hash as the old `HashNavigation.navigateTo(view)` did.
|
||||||
|
const navigate = (view, params) => HashNavigation.navigateTo(HashNavigation.composeUrlHash([view], params || {}));
|
||||||
|
|
||||||
function Icon({children, className = ''}) {
|
function Icon({children, className = ''}) {
|
||||||
return <i className={'material-icons ' + className} aria-hidden="true">{children}</i>;
|
return <i className={'material-icons ' + className} aria-hidden="true">{children}</i>;
|
||||||
@@ -529,7 +531,13 @@ function PasswordReset() {
|
|||||||
// successful verify, stashes the resulting action_token for the next screen to spend.
|
// successful verify, stashes the resulting action_token for the next screen to spend.
|
||||||
const IDENTITY_CHECK_SCENE = 'set_independent_password';
|
const IDENTITY_CHECK_SCENE = 'set_independent_password';
|
||||||
|
|
||||||
function IdentityCheck({authenticated}) {
|
function IdentityCheck({authenticated, hashParams}) {
|
||||||
|
// Carries through whichever youth-mode flow (enable/disable) sent the user here via "忘记独立
|
||||||
|
// 密码?", if any -- IndependentPassword needs it to know whether to auto-continue into actually
|
||||||
|
// enabling youth mode, since the server hands back an enable-flow token unconditionally on every
|
||||||
|
// successful password (re)set regardless of why the caller got here. Absent (undefined) for the
|
||||||
|
// unrelated generic "身份验证" entry in acc-security-view.jsx, which should never touch youth mode.
|
||||||
|
const mode = hashParams && hashParams.ym;
|
||||||
const [phoneMask, setPhoneMask] = React.useState('');
|
const [phoneMask, setPhoneMask] = React.useState('');
|
||||||
const [challengeId, setChallengeId] = React.useState('');
|
const [challengeId, setChallengeId] = React.useState('');
|
||||||
const [code, setCode] = React.useState('');
|
const [code, setCode] = React.useState('');
|
||||||
@@ -581,7 +589,7 @@ function IdentityCheck({authenticated}) {
|
|||||||
try {
|
try {
|
||||||
const data = await verifySecurityChallenge(challengeId, code);
|
const data = await verifySecurityChallenge(challengeId, code);
|
||||||
sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
|
sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
|
||||||
navigate('independent-password');
|
navigate('independent-password', mode ? {ym: mode} : undefined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || '验证码错误');
|
setError(err.message || '验证码错误');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -681,7 +689,12 @@ function RealnameReminder() {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function IndependentPassword({authenticated}) {
|
function IndependentPassword({authenticated, hashParams}) {
|
||||||
|
// Only the genuine "haven't set a password yet, setting one now to enable youth mode for the
|
||||||
|
// first time" flow should auto-continue into enableYouthMode below -- see IdentityCheck's
|
||||||
|
// comment on the same param for why this can't just be inferred from enable_action_token being
|
||||||
|
// present.
|
||||||
|
const mode = hashParams && hashParams.ym;
|
||||||
const [pin, setPin] = React.useState('');
|
const [pin, setPin] = React.useState('');
|
||||||
const [error, setError] = React.useState('');
|
const [error, setError] = React.useState('');
|
||||||
const [toast, setToast] = React.useState('');
|
const [toast, setToast] = React.useState('');
|
||||||
@@ -704,8 +717,13 @@ function IndependentPassword({authenticated}) {
|
|||||||
// 见 server/im_http.go:6178-6189)。原来这里把它整个丢掉、只弹一句"设置成功"就停在
|
// 见 server/im_http.go:6178-6189)。原来这里把它整个丢掉、只弹一句"设置成功"就停在
|
||||||
// PIN 页——用户走完短信验证+设密码,青少年模式其实还是关的,得自己摸回设置页再输一遍。
|
// PIN 页——用户走完短信验证+设密码,青少年模式其实还是关的,得自己摸回设置页再输一遍。
|
||||||
// 现在拿着这个 token 把最后一步做完。
|
// 现在拿着这个 token 把最后一步做完。
|
||||||
|
// enable_action_token comes back unconditionally on every successful (re)set, regardless of
|
||||||
|
// why the caller ended up here (server doesn't know or care) -- only the genuine "first time
|
||||||
|
// enabling" flow should spend it. Otherwise this silently flipped youth mode on for someone
|
||||||
|
// who was mid-way through disabling it, or who came from the unrelated generic "身份验证"
|
||||||
|
// entry with no youth-mode intent at all.
|
||||||
const enableToken = result && result.enable_action_token;
|
const enableToken = result && result.enable_action_token;
|
||||||
if (enableToken) {
|
if (enableToken && mode === 'enable') {
|
||||||
try {
|
try {
|
||||||
const current = await getYouthMode();
|
const current = await getYouthMode();
|
||||||
await enableYouthMode(current.policy_version, enableToken);
|
await enableYouthMode(current.policy_version, enableToken);
|
||||||
@@ -719,6 +737,15 @@ function IndependentPassword({authenticated}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mode === 'disable') {
|
||||||
|
// enableToken (if any) can't be spent here -- ConsumeActionToken requires an exact
|
||||||
|
// action='close_youth_mode' match, and this one is always tagged 'enable_youth_mode'. The
|
||||||
|
// user does know the fresh PIN now though, so send them straight back to actually finish
|
||||||
|
// disabling instead of leaving them stuck on this screen with nothing left to do.
|
||||||
|
setError(''); setToast('独立密码已重置,请使用新密码关闭青少年模式');
|
||||||
|
window.setTimeout(() => navigate('youth-mode-verify', {ym: 'disable'}), 800);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setError(''); setToast('独立密码设置成功');
|
setError(''); setToast('独立密码设置成功');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || '设置失败,请返回重新验证身份');
|
setError(err.message || '设置失败,请返回重新验证身份');
|
||||||
@@ -740,7 +767,7 @@ function IndependentPassword({authenticated}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AuthOnboardingView({
|
export default function AuthOnboardingView({
|
||||||
view, onTenantResolved, authenticated = false,
|
view, hashParams, onTenantResolved, authenticated = false,
|
||||||
login, loginDisabled, persist, errorText, errorLevel,
|
login, loginDisabled, persist, errorText, errorLevel,
|
||||||
onLoginRequest, onPersistenceChange, onImRegisterRequest, onError
|
onLoginRequest, onPersistenceChange, onImRegisterRequest, onError
|
||||||
}) {
|
}) {
|
||||||
@@ -753,11 +780,11 @@ export default function AuthOnboardingView({
|
|||||||
case 'enterprise-feedback': return <EnterpriseFeedback />;
|
case 'enterprise-feedback': return <EnterpriseFeedback />;
|
||||||
case 'register': return <Register errorText={errorText} errorLevel={errorLevel} onImRegisterRequest={onImRegisterRequest} />;
|
case 'register': return <Register errorText={errorText} errorLevel={errorLevel} onImRegisterRequest={onImRegisterRequest} />;
|
||||||
case 'reset': return <PasswordReset />;
|
case 'reset': return <PasswordReset />;
|
||||||
case 'identity-check': return <IdentityCheck authenticated={authenticated} />;
|
case 'identity-check': return <IdentityCheck authenticated={authenticated} hashParams={hashParams} />;
|
||||||
case 'realname': return <Realname authenticated={authenticated} />;
|
case 'realname': return <Realname authenticated={authenticated} />;
|
||||||
case 'realname-verified': return <Realname authenticated={authenticated} />;
|
case 'realname-verified': return <Realname authenticated={authenticated} />;
|
||||||
case 'realname-reminder': return <RealnameReminder />;
|
case 'realname-reminder': return <RealnameReminder />;
|
||||||
case 'independent-password': return <IndependentPassword authenticated={authenticated} />;
|
case 'independent-password': return <IndependentPassword authenticated={authenticated} hashParams={hashParams} />;
|
||||||
default: return <TenantEntry onTenantResolved={onTenantResolved || (() => {})} />;
|
default: return <TenantEntry onTenantResolved={onTenantResolved || (() => {})} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
getFriends, getFriendRequests, createFriendRequest, acceptFriendRequest, rejectFriendRequest,
|
getFriends, getFriendRequests, createFriendRequest, acceptFriendRequest, rejectFriendRequest,
|
||||||
deleteFriend, updateFriendRemark, searchUsers, getUserProfile, createMyQRCode, getBlocks,
|
deleteFriend, updateFriendRemark, searchUsers, getUserProfile, createMyQRCode, getBlocks,
|
||||||
blockUser, unblockUser, getFileUrl, getBootstrap, getConversationContext, getConversationSettings,
|
blockUser, unblockUser, getFileUrl, getBootstrap, getConversationContext, getConversationSettings,
|
||||||
updateConversationSettings, createUserQRCode
|
updateConversationSettings, createUserQRCode, fetchAllPages
|
||||||
} from '../lib/im-api.js';
|
} from '../lib/im-api.js';
|
||||||
import { readCachedData, writeCachedData, useCachedData } from '../lib/use-cached-data.js';
|
import { readCachedData, writeCachedData, useCachedData } from '../lib/use-cached-data.js';
|
||||||
|
|
||||||
@@ -112,12 +112,14 @@ function AddressBook({myUserId, onTopicSelected}) {
|
|||||||
const fileAssistantTopic = bootstrap && bootstrap.entries && bootstrap.entries.file_assistant
|
const fileAssistantTopic = bootstrap && bootstrap.entries && bootstrap.entries.file_assistant
|
||||||
&& bootstrap.entries.file_assistant.topic;
|
&& bootstrap.entries.file_assistant.topic;
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
getFriends().then(data => {
|
// getFriends() defaults to a 50-item page -- a plain single call silently truncated address
|
||||||
const list = data.items || [];
|
// books past that size, with no "load more" affordance to recover the rest.
|
||||||
|
fetchAllPages(cursor => getFriends('', cursor)).then(list => {
|
||||||
if (friendsCacheKey) { writeCachedData(friendsCacheKey, list); }
|
if (friendsCacheKey) { writeCachedData(friendsCacheKey, list); }
|
||||||
setFriends(list);
|
setFriends(list);
|
||||||
}).catch(() => {}).finally(() => setLoading(false));
|
}).catch(() => {}).finally(() => setLoading(false));
|
||||||
getFriendRequests('inbound', 'pending').then(data => setPendingCount((data.items || []).length)).catch(() => {});
|
fetchAllPages(cursor => 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 || ''}`.includes(query));
|
||||||
const groups = visible.reduce((result, item) => {
|
const groups = visible.reduce((result, item) => {
|
||||||
@@ -173,7 +175,8 @@ function FriendRequests({onToast}) {
|
|||||||
const [items, setItems] = React.useState([]);
|
const [items, setItems] = React.useState([]);
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
getFriendRequests('all', 'all').then(data => setItems(data.items || [])).catch(() => {}).finally(() => setLoading(false));
|
fetchAllPages(cursor => getFriendRequests('all', 'all', cursor))
|
||||||
|
.then(setItems).catch(() => {}).finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
const handle = (item, accept) => {
|
const handle = (item, accept) => {
|
||||||
const nextStatus = accept ? 'accepted' : 'rejected';
|
const nextStatus = accept ? 'accepted' : 'rejected';
|
||||||
@@ -510,8 +513,8 @@ function Settings({uid, initialBlocked, pinBlocked, onToast}) {
|
|||||||
// calls userSummaryPayload(user, "") -- confirmed against im_be/server/im_http.go:7844 --
|
// calls userSummaryPayload(user, "") -- confirmed against im_be/server/im_http.go:7844 --
|
||||||
// folding an empty remark into display_name instead of returning a separate field), only the
|
// folding an empty remark into display_name instead of returning a separate field), only the
|
||||||
// friends list endpoint's friendListItemPayload does. Look this friend up there instead.
|
// friends list endpoint's friendListItemPayload does. Look this friend up there instead.
|
||||||
getFriends().then(data => {
|
fetchAllPages(cursor => getFriends('', cursor)).then(list => {
|
||||||
const entry = (data.items || []).find(item => item.user_id === uid);
|
const entry = list.find(item => item.user_id === uid);
|
||||||
if (entry) setRemark(entry.remark || '');
|
if (entry) setRemark(entry.remark || '');
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, [uid]);
|
}, [uid]);
|
||||||
@@ -569,7 +572,9 @@ function Settings({uid, initialBlocked, pinBlocked, onToast}) {
|
|||||||
function BlockList({onToast}) {
|
function BlockList({onToast}) {
|
||||||
const [people, setPeople] = React.useState([]);
|
const [people, setPeople] = React.useState([]);
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
React.useEffect(() => { getBlocks().then(data => setPeople(data.items || [])).catch(() => {}).finally(() => setLoading(false)); }, []);
|
React.useEffect(() => {
|
||||||
|
fetchAllPages(cursor => getBlocks(cursor)).then(setPeople).catch(() => {}).finally(() => setLoading(false));
|
||||||
|
}, []);
|
||||||
const remove = userId => {
|
const remove = userId => {
|
||||||
unblockUser(userId).then(() => { setPeople(items => items.filter(item => item.user_id !== userId)); onToast('已移出黑名单'); })
|
unblockUser(userId).then(() => { setPeople(items => items.filter(item => item.user_id !== userId)); onToast('已移出黑名单'); })
|
||||||
.catch(err => onToast(err.message || '操作失败'));
|
.catch(err => onToast(err.message || '操作失败'));
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
updateCurrentGroupAnnouncement, readGroupAnnouncement, getGroupGovernance, updateGroupGovernance, addGroupAdmins,
|
updateCurrentGroupAnnouncement, readGroupAnnouncement, getGroupGovernance, updateGroupGovernance, addGroupAdmins,
|
||||||
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
|
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
|
||||||
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile,
|
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile,
|
||||||
getGroupMemberProfile
|
getGroupMemberProfile, fetchAllPages
|
||||||
} from '../lib/im-api.js';
|
} from '../lib/im-api.js';
|
||||||
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
|
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
|
||||||
|
|
||||||
@@ -706,8 +706,9 @@ function AdminList({topic, onToast}) {
|
|||||||
// Fetch the unfiltered member list rather than {role:'admin'} + a separate per-user profile lookup
|
// Fetch the unfiltered member list rather than {role:'admin'} + a separate per-user profile lookup
|
||||||
// for the owner -- GET /users/{id}/profile 400s ("malformed request", field "user_id") when {id} is
|
// for the owner -- GET /users/{id}/profile 400s ("malformed request", field "user_id") when {id} is
|
||||||
// the caller's own uid, which broke this whenever the viewer is themselves the group's owner.
|
// the caller's own uid, which broke this whenever the viewer is themselves the group's owner.
|
||||||
getGroupMembers(topic).then(data => {
|
// Members cap out at 30/page against a 128-member group limit -- an admin sitting past page 1
|
||||||
const items = data.items || [];
|
// used to vanish from this list entirely rather than the list just being incomplete.
|
||||||
|
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||||
writeCachedData('group-members:' + topic, items);
|
writeCachedData('group-members:' + topic, items);
|
||||||
const d = deriveOwnerAndAdmins(items);
|
const d = deriveOwnerAndAdmins(items);
|
||||||
setOwner(d.owner);
|
setOwner(d.owner);
|
||||||
@@ -744,8 +745,7 @@ function GroupMembers({topic, onToast}) {
|
|||||||
const [loadError, setLoadError] = React.useState(false);
|
const [loadError, setLoadError] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
getGroupMembers(topic).then(data => {
|
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||||
const items = data.items || [];
|
|
||||||
writeCachedData('group-members:' + topic, items);
|
writeCachedData('group-members:' + topic, items);
|
||||||
setMembers(items);
|
setMembers(items);
|
||||||
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||||
@@ -781,8 +781,8 @@ function MemberPicker({type, topic, onToast}) {
|
|||||||
const [loadError, setLoadError] = React.useState(false);
|
const [loadError, setLoadError] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
getGroupMembers(topic, mute ? {muteEligible: true} : {eligibleRole: 'admin'})
|
fetchAllPages(cursor => getGroupMembers(topic, {cursor, ...(mute ? {muteEligible: true} : {eligibleRole: 'admin'})}))
|
||||||
.then(data => setMembers((data.items || []).filter(m => m.eligible !== false)))
|
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||||
}, [topic, mute]);
|
}, [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 || '').includes(query));
|
||||||
@@ -842,15 +842,14 @@ function Muted({topic, onToast}) {
|
|||||||
const [loadError, setLoadError] = React.useState(false);
|
const [loadError, setLoadError] = React.useState(false);
|
||||||
const load = React.useCallback(() => {
|
const load = React.useCallback(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
getGroupMutes(topic).then(data => {
|
fetchAllPages(cursor => getGroupMutes(topic, cursor)).then(list => {
|
||||||
const list = data.items || [];
|
|
||||||
writeCachedData('group-muted:' + topic, list);
|
writeCachedData('group-muted:' + topic, list);
|
||||||
setItems(list);
|
setItems(list);
|
||||||
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||||
getGroupMembers(topic).then(data => {
|
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||||
const map = {};
|
const map = {};
|
||||||
const avatarMap = {};
|
const avatarMap = {};
|
||||||
(data.items || []).forEach(m => {
|
items.forEach(m => {
|
||||||
map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
|
map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
|
||||||
if (m.avatar_file_id) avatarMap[m.user_id] = {avatar_file_id: m.avatar_file_id};
|
if (m.avatar_file_id) avatarMap[m.user_id] = {avatar_file_id: m.avatar_file_id};
|
||||||
});
|
});
|
||||||
@@ -896,7 +895,8 @@ function OwnerSelect({topic, onToast}) {
|
|||||||
const [loadError, setLoadError] = React.useState(false);
|
const [loadError, setLoadError] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
getGroupMembers(topic, {eligibleRole: 'owner'}).then(data => setMembers((data.items || []).filter(m => m.eligible !== false)))
|
fetchAllPages(cursor => getGroupMembers(topic, {cursor, eligibleRole: 'owner'}))
|
||||||
|
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||||
}, [topic]);
|
}, [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 || '').includes(query));
|
||||||
@@ -1122,11 +1122,13 @@ export default function GroupManagementView({view, hashParams, myUserId, tinode,
|
|||||||
if (!topic || !myUserId) { setMyRole(null); return; }
|
if (!topic || !myUserId) { setMyRole(null); return; }
|
||||||
const key = `group-my-role:${topic}:${myUserId}`;
|
const key = `group-my-role:${topic}:${myUserId}`;
|
||||||
setMyRole(readCachedData(key) || null);
|
setMyRole(readCachedData(key) || null);
|
||||||
getGroupMembers(topic).then(data => {
|
// Used to look this up by paginating the full member list and finding myUserId in it (30/page
|
||||||
const items = data.items || [];
|
// against a 128-member cap) -- a real owner/admin sitting past page 1 read back as `null`,
|
||||||
writeCachedData('group-members:' + topic, items);
|
// silently hiding every governance affordance gated on this value. getGroup's own `my_role`
|
||||||
const me = items.find(m => m.user_id === myUserId);
|
// (already relied on elsewhere, e.g. the disband-group guard above) isn't paginated at all --
|
||||||
const role = me ? me.role : null;
|
// it's always exactly "me", so this no longer needs the member list at all.
|
||||||
|
getGroup(topic).then(group => {
|
||||||
|
const role = group ? group.my_role : null;
|
||||||
writeCachedData(key, role);
|
writeCachedData(key, role);
|
||||||
setMyRole(role);
|
setMyRole(role);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|||||||
@@ -1346,7 +1346,17 @@ class MessagesView extends React.Component {
|
|||||||
if (!topic || !this.props.selectedSeqs.length) {
|
if (!topic || !this.props.selectedSeqs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
topic.delMessagesList(this.props.selectedSeqs.slice(), true).then(_ => {
|
// A selected message may still be queued/uploading (no server-confirmed seq yet, same case
|
||||||
|
// context-menu.jsx's single-message deleteMessages guards with cancelSend before falling
|
||||||
|
// back to delMessagesList). delMessagesList's normalizeRanges silently drops local-only
|
||||||
|
// seqs instead of cancelling them, so left unfiltered a still-uploading attachment would
|
||||||
|
// survive "delete" and get sent normally once its upload finishes.
|
||||||
|
const remaining = this.props.selectedSeqs.filter(seq => !topic.cancelSend(seq));
|
||||||
|
if (!remaining.length) {
|
||||||
|
this.props.onExitSelectMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topic.delMessagesList(remaining, true).then(_ => {
|
||||||
this.props.onExitSelectMode();
|
this.props.onExitSelectMode();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.props.onError(err.message, 'err');
|
this.props.onError(err.message, 'err');
|
||||||
|
|||||||
@@ -441,8 +441,12 @@ function DeleteAccountConfirm({onDeleteAccount, onToast}) {
|
|||||||
Promise.resolve(onDeleteAccount()).catch(err => { onToast(err.message || '注销失败'); setBusy(false); });
|
Promise.resolve(onDeleteAccount()).catch(err => { onToast(err.message || '注销失败'); setBusy(false); });
|
||||||
};
|
};
|
||||||
if (step === 1) {
|
if (step === 1) {
|
||||||
|
// im_be's replyDelUser -> hub.stopTopicsForUser hard-deletes every group topic this account
|
||||||
|
// owns (not just leaves them) -- confirmed against hub.go's topic.owner==uid branch. Callers
|
||||||
|
// in those groups get no advance warning and no forced-ownership-transfer prompt, so this has
|
||||||
|
// to be disclosed here since it's the only place the user sees before confirming.
|
||||||
return <ConfirmSheet icon="warning" title="注销账号"
|
return <ConfirmSheet icon="warning" title="注销账号"
|
||||||
body="注销后账号数据将被永久删除且无法恢复,包括聊天记录、好友关系和收藏内容。确定要继续吗?"
|
body="注销后账号数据将被永久删除且无法恢复,包括聊天记录、好友关系和收藏内容。如果你是某些群的群主,这些群也将被永久解散,其他成员将无法继续使用。确定要继续吗?"
|
||||||
confirmLabel="继续" onConfirm={() => setStep(2)} onCancel={() => navigate('android-settings')} />;
|
confirmLabel="继续" onConfirm={() => setStep(2)} onCancel={() => navigate('android-settings')} />;
|
||||||
}
|
}
|
||||||
return <ConfirmSheet icon="no_accounts" title="输入密码确认注销" body="请输入登录密码以确认注销账号(该操作立即生效,不可撤销)。"
|
return <ConfirmSheet icon="no_accounts" title="输入密码确认注销" body="请输入登录密码以确认注销账号(该操作立即生效,不可撤销)。"
|
||||||
@@ -598,7 +602,7 @@ function YouthMode({onToast}) {
|
|||||||
if (!settings) return;
|
if (!settings) return;
|
||||||
if (!settings.password_set) {
|
if (!settings.password_set) {
|
||||||
onToast('请先设置独立密码');
|
onToast('请先设置独立密码');
|
||||||
window.setTimeout(() => navigate('identity-check'), 400);
|
window.setTimeout(() => navigate('identity-check', {ym: 'enable'}), 400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate('youth-mode-verify', {ym: 'enable'});
|
navigate('youth-mode-verify', {ym: 'enable'});
|
||||||
@@ -697,8 +701,11 @@ function YouthModeVerify({hashParams, onToast}) {
|
|||||||
</button>
|
</button>
|
||||||
{/* 一旦设过密码,这个页面就是唯一入口,之前完全没有出路——真忘了密码只能不断输错直到被
|
{/* 一旦设过密码,这个页面就是唯一入口,之前完全没有出路——真忘了密码只能不断输错直到被
|
||||||
限流,限流也只是让你等,不会给别的办法。identity-check 走的短信验证码本来就支持在
|
限流,限流也只是让你等,不会给别的办法。identity-check 走的短信验证码本来就支持在
|
||||||
password_set:true 时重新设置密码(实测确认过),只是没有从这里链接过去。 */}
|
password_set:true 时重新设置密码(实测确认过),只是没有从这里链接过去。
|
||||||
<button type="button" className="auth-text-button" onClick={() => navigate('identity-check')}>忘记独立密码?</button>
|
必须带上当前 mode:IndependentPassword 设完新密码后,服务端每次都会无条件带回
|
||||||
|
enable_action_token(与调用者意图无关),原来因为不知道是从"关闭"这个流程点进来的,
|
||||||
|
会把这次重置误当成"继续开启",用户想关闭青少年模式却被弹"已开启"、状态也没有真的关掉。 */}
|
||||||
|
<button type="button" className="auth-text-button" onClick={() => navigate('identity-check', {ym: mode})}>忘记独立密码?</button>
|
||||||
</main>
|
</main>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ class SidepanelView extends React.PureComponent {
|
|||||||
return <div id="sidepanel" className="auth-onboarding-panel">
|
return <div id="sidepanel" className="auth-onboarding-panel">
|
||||||
<AuthOnboardingView
|
<AuthOnboardingView
|
||||||
view={view}
|
view={view}
|
||||||
|
hashParams={this.props.hashParams}
|
||||||
authenticated={!!this.props.myUserId}
|
authenticated={!!this.props.myUserId}
|
||||||
login={this.props.login}
|
login={this.props.login}
|
||||||
loginDisabled={this.props.loginDisabled}
|
loginDisabled={this.props.loginDisabled}
|
||||||
|
|||||||
@@ -2053,6 +2053,15 @@ class TinodeWeb extends React.Component {
|
|||||||
// (send-message.jsx) can gate on exactly the same real-world availability check as the existing
|
// (send-message.jsx) can gate on exactly the same real-world availability check as the existing
|
||||||
// "..." menu items, instead of re-deriving (and risking drifting from) the same three conditions.
|
// "..." menu items, instead of re-deriving (and risking drifting from) the same three conditions.
|
||||||
canStartCall(topicName) {
|
canStartCall(topicName) {
|
||||||
|
// Never checked whether a call was already in progress -- the "..." menu entry and
|
||||||
|
// send-message.jsx's attachment-panel shortcut stayed clickable for the whole (very normal,
|
||||||
|
// product-supported) duration of a minimized call. A second click re-arms
|
||||||
|
// handleStartVideoCall/handleStartAudioCall's 60s ring timeout and forces callState back to
|
||||||
|
// CALL_STATE_OUTGOING_INITATED with no visible effect (the already-mounted CallPanel doesn't
|
||||||
|
// react to that prop), silently hanging up the real, healthy call a full minute later.
|
||||||
|
if (this.state.callState != CALL_STATE_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const topic = this.tinode.getTopic(topicName);
|
const topic = this.tinode.getTopic(topicName);
|
||||||
if (!topic || topic._deleted || !Tinode.isP2PTopicName(topicName)) {
|
if (!topic || topic._deleted || !Tinode.isP2PTopicName(topicName)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2524,7 +2533,15 @@ class TinodeWeb extends React.Component {
|
|||||||
// arrives (or armCallRingTimeout's hang-up firing before it) leaves this call's callSeq
|
// arrives (or armCallRingTimeout's hang-up firing before it) leaves this call's callSeq
|
||||||
// pointing at whatever the *previous* call's seq was, and the next outgoing call could read
|
// pointing at whatever the *previous* call's seq was, and the next outgoing call could read
|
||||||
// that stale value for the brief window before its own ack lands.
|
// that stale value for the brief window before its own ack lands.
|
||||||
callSeq: undefined
|
callSeq: undefined,
|
||||||
|
// handleCallAccept's "topic not subscribed yet" branch (below) sets this and waits for
|
||||||
|
// handleNewTopicCreated to both flip callState to CALL_STATE_IN_PROGRESS and clear this flag
|
||||||
|
// once that subscription succeeds -- but a call closed/abandoned (rejected, or the subscribe
|
||||||
|
// itself failing) before that ever happens left this stuck at true with no owner left to clear
|
||||||
|
// it. It would then get read by the *next*, entirely unrelated topic subscription that happens
|
||||||
|
// to succeed, incorrectly forcing callState to CALL_STATE_IN_PROGRESS for a call that was
|
||||||
|
// already given up on.
|
||||||
|
callShouldStart: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2567,7 +2584,10 @@ class TinodeWeb extends React.Component {
|
|||||||
callTopic: null,
|
callTopic: null,
|
||||||
callState: CALL_STATE_NONE,
|
callState: CALL_STATE_NONE,
|
||||||
callSeq: null,
|
callSeq: null,
|
||||||
callAudioOnly: undefined
|
callAudioOnly: undefined,
|
||||||
|
// Same stuck-flag risk handleCallClose() guards against -- this call is being given up
|
||||||
|
// on here just as much as a local close/reject would, so it needs the same cleanup.
|
||||||
|
callShouldStart: false
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ const nameCache = new Map();
|
|||||||
function resolveUserName(id, onDone) {
|
function resolveUserName(id, onDone) {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
if (nameCache.has(id)) { onDone(nameCache.get(id)); return; }
|
if (nameCache.has(id)) { onDone(nameCache.get(id)); return; }
|
||||||
const withPrefix = id.startsWith('usr') ? id : 'usr' + id;
|
const withPrefix = id.startsWith('usr') ? null : 'usr' + id;
|
||||||
getUserProfile(id).catch(() => getUserProfile(withPrefix)).then(p => {
|
getUserProfile(id).catch(err => withPrefix ? getUserProfile(withPrefix) : Promise.reject(err)).then(p => {
|
||||||
const name = (p && (p.display_name || p.nickname || p.public_account)) || id;
|
const name = (p && (p.display_name || p.nickname || p.public_account)) || id;
|
||||||
nameCache.set(id, name);
|
nameCache.set(id, name);
|
||||||
onDone(name);
|
onDone(name);
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ const THUMB_COLOR = '#1781d7'; // average between dark and light accent colors.
|
|||||||
// Minimum number of amplitude bars to draw.
|
// Minimum number of amplitude bars to draw.
|
||||||
const MIN_PREVIEW_LENGTH = 16;
|
const MIN_PREVIEW_LENGTH = 16;
|
||||||
|
|
||||||
|
// 7.5 "列表中同时只能有一条语音播放":favorites/history 列表和聊天气泡里的每条语音各自是一个独立
|
||||||
|
// AudioPlayer 实例,彼此不知道对方的存在。Module-scoped(不是某个列表父组件的 state)所以跨 tab、
|
||||||
|
// 跨收藏/聊天记录也生效,不只是同一个列表内部。
|
||||||
|
let activeAudioPlayer = null;
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
icon_title_play: {
|
icon_title_play: {
|
||||||
id: 'icon_title_play',
|
id: 'icon_title_play',
|
||||||
@@ -69,6 +74,9 @@ class AudioPlayer extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
if (activeAudioPlayer === this) {
|
||||||
|
activeAudioPlayer = null;
|
||||||
|
}
|
||||||
if (this.audioPlayer) {
|
if (this.audioPlayer) {
|
||||||
this.audioPlayer.onloadedmetadata = null;
|
this.audioPlayer.onloadedmetadata = null;
|
||||||
this.audioPlayer.ontimeupdate = null;
|
this.audioPlayer.ontimeupdate = null;
|
||||||
@@ -78,6 +86,14 @@ class AudioPlayer extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called on the previously-playing instance by whichever other instance is about to start.
|
||||||
|
pauseForOtherPlayer() {
|
||||||
|
if (this.audioPlayer) {
|
||||||
|
this.audioPlayer.pause();
|
||||||
|
}
|
||||||
|
this.setState({playing: false});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.src != prevProps.src) {
|
if (this.props.src != prevProps.src) {
|
||||||
this.initAudio();
|
this.initAudio();
|
||||||
@@ -100,7 +116,10 @@ class AudioPlayer extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
this.audioPlayer.onended = _ => {
|
this.audioPlayer.onended = _ => {
|
||||||
this.audioPlayer.currentTime = 0;
|
this.audioPlayer.currentTime = 0;
|
||||||
this.setState({playing: false, currentTime: secondsToTime(0, this.state.longMin)})
|
this.setState({playing: false, currentTime: secondsToTime(0, this.state.longMin)});
|
||||||
|
if (activeAudioPlayer === this) {
|
||||||
|
activeAudioPlayer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class AudioRecorder extends React.PureComponent {
|
|||||||
if (duration > MAX_DURATION) {
|
if (duration > MAX_DURATION) {
|
||||||
this.startedOn = null;
|
this.startedOn = null;
|
||||||
this.mediaRecorder.pause();
|
this.mediaRecorder.pause();
|
||||||
this.durationMillis += Date.now() - this.startedOn;
|
this.durationMillis = duration;
|
||||||
this.setState({enabled: false, recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
this.setState({enabled: false, recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
seq: this.props.seq,
|
seq: this.props.seq,
|
||||||
replace: this.props.edited ? parseInt(this.props.edited.split(':')[1]) : 0,
|
replace: this.props.edited ? parseInt(this.props.edited.split(':')[1]) : 0,
|
||||||
content: this.props.content,
|
content: this.props.content,
|
||||||
|
head: this.props.head,
|
||||||
userFrom: this.props.userFrom,
|
userFrom: this.props.userFrom,
|
||||||
userName: this.props.userName,
|
userName: this.props.userName,
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
|
|||||||
@@ -608,6 +608,15 @@ class ContextMenu extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = topic.createMessage(params.content, false);
|
const msg = topic.createMessage(params.content, false);
|
||||||
|
// createMessage only ever fills in head.mime for non-plain-text content -- the original
|
||||||
|
// message's x-im-type/reply/mentions/mention_all live in params.head (set by
|
||||||
|
// chat-message.jsx's handleContextClick from the props it was rendered with) and would
|
||||||
|
// otherwise be silently dropped, unlike the auto-retry path (messages-view.jsx's retrySend)
|
||||||
|
// which already forwards the original head. Same merge tinode-web.jsx's
|
||||||
|
// sendMessageToTopic does for a fresh send.
|
||||||
|
if (params.head) {
|
||||||
|
msg.head = Object.assign(msg.head || {}, params.head);
|
||||||
|
}
|
||||||
return topic.publishDraft(msg).catch(err => {
|
return topic.publishDraft(msg).catch(err => {
|
||||||
if (errorHandler) {
|
if (errorHandler) {
|
||||||
errorHandler(err.message, 'err');
|
errorHandler(err.message, 'err');
|
||||||
|
|||||||
+104
-41
@@ -3382,6 +3382,7 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony export */ disableYouthMode: function() { return /* binding */ disableYouthMode; },
|
/* harmony export */ disableYouthMode: function() { return /* binding */ disableYouthMode; },
|
||||||
/* harmony export */ enableYouthMode: function() { return /* binding */ enableYouthMode; },
|
/* harmony export */ enableYouthMode: function() { return /* binding */ enableYouthMode; },
|
||||||
/* harmony export */ favoriteMessages: function() { return /* binding */ favoriteMessages; },
|
/* harmony export */ favoriteMessages: function() { return /* binding */ favoriteMessages; },
|
||||||
|
/* harmony export */ fetchAllPages: function() { return /* binding */ fetchAllPages; },
|
||||||
/* harmony export */ fetchFileState: function() { return /* binding */ fetchFileState; },
|
/* harmony export */ fetchFileState: function() { return /* binding */ fetchFileState; },
|
||||||
/* harmony export */ forwardFavorites: function() { return /* binding */ forwardFavorites; },
|
/* harmony export */ forwardFavorites: function() { return /* binding */ forwardFavorites; },
|
||||||
/* harmony export */ forwardMessages: function() { return /* binding */ forwardMessages; },
|
/* harmony export */ forwardMessages: function() { return /* binding */ forwardMessages; },
|
||||||
@@ -3640,6 +3641,20 @@ function translateReason(reason, details) {
|
|||||||
const idem = () => ({
|
const idem = () => ({
|
||||||
'Idempotency-Key': requestId()
|
'Idempotency-Key': requestId()
|
||||||
});
|
});
|
||||||
|
async function fetchAllPages(fetchPage, startCursor = '') {
|
||||||
|
let cursor = startCursor;
|
||||||
|
let items = [];
|
||||||
|
for (;;) {
|
||||||
|
const page = await fetchPage(cursor);
|
||||||
|
items = items.concat(page && page.items || []);
|
||||||
|
const nextCursor = page && page.next_cursor || '';
|
||||||
|
if (!nextCursor || nextCursor === cursor) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor = nextCursor;
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
const imGet = (path, opts) => imRequest(path, opts);
|
const imGet = (path, opts) => imRequest(path, opts);
|
||||||
const imPost = (path, body, opts = {}) => imRequest(path, {
|
const imPost = (path, body, opts = {}) => imRequest(path, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -5583,7 +5598,7 @@ const PHONE_RE = /^\d{11}$/;
|
|||||||
const INVITE_RE = /^[A-Za-z]{5}$/;
|
const INVITE_RE = /^[A-Za-z]{5}$/;
|
||||||
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
||||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
const navigate = view => _lib_navigation_js__WEBPACK_IMPORTED_MODULE_1__["default"].navigateTo(view);
|
const navigate = (view, params) => _lib_navigation_js__WEBPACK_IMPORTED_MODULE_1__["default"].navigateTo(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_1__["default"].composeUrlHash([view], params || {}));
|
||||||
function Icon({
|
function Icon({
|
||||||
children,
|
children,
|
||||||
className = ''
|
className = ''
|
||||||
@@ -6474,8 +6489,10 @@ function PasswordReset() {
|
|||||||
}
|
}
|
||||||
const IDENTITY_CHECK_SCENE = 'set_independent_password';
|
const IDENTITY_CHECK_SCENE = 'set_independent_password';
|
||||||
function IdentityCheck({
|
function IdentityCheck({
|
||||||
authenticated
|
authenticated,
|
||||||
|
hashParams
|
||||||
}) {
|
}) {
|
||||||
|
const mode = hashParams && hashParams.ym;
|
||||||
const [phoneMask, setPhoneMask] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [phoneMask, setPhoneMask] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [challengeId, setChallengeId] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [challengeId, setChallengeId] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [code, setCode] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [code, setCode] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
@@ -6520,7 +6537,9 @@ function IdentityCheck({
|
|||||||
try {
|
try {
|
||||||
const data = await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.verifySecurityChallenge)(challengeId, code);
|
const data = await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.verifySecurityChallenge)(challengeId, code);
|
||||||
sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
|
sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
|
||||||
navigate('independent-password');
|
navigate('independent-password', mode ? {
|
||||||
|
ym: mode
|
||||||
|
} : undefined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || '验证码错误');
|
setError(err.message || '验证码错误');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -6730,8 +6749,10 @@ function RealnameReminder() {
|
|||||||
}, void 0, true);
|
}, void 0, true);
|
||||||
}
|
}
|
||||||
function IndependentPassword({
|
function IndependentPassword({
|
||||||
authenticated
|
authenticated,
|
||||||
|
hashParams
|
||||||
}) {
|
}) {
|
||||||
|
const mode = hashParams && hashParams.ym;
|
||||||
const [pin, setPin] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [pin, setPin] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [error, setError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [error, setError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [toast, setToast] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [toast, setToast] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
@@ -6755,7 +6776,7 @@ function IndependentPassword({
|
|||||||
sessionStorage.removeItem('im-action-token:' + IDENTITY_CHECK_SCENE);
|
sessionStorage.removeItem('im-action-token:' + IDENTITY_CHECK_SCENE);
|
||||||
setPin('');
|
setPin('');
|
||||||
const enableToken = result && result.enable_action_token;
|
const enableToken = result && result.enable_action_token;
|
||||||
if (enableToken) {
|
if (enableToken && mode === 'enable') {
|
||||||
try {
|
try {
|
||||||
const current = await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.getYouthMode)();
|
const current = await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.getYouthMode)();
|
||||||
await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.enableYouthMode)(current.policy_version, enableToken);
|
await (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_2__.enableYouthMode)(current.policy_version, enableToken);
|
||||||
@@ -6769,6 +6790,14 @@ function IndependentPassword({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mode === 'disable') {
|
||||||
|
setError('');
|
||||||
|
setToast('独立密码已重置,请使用新密码关闭青少年模式');
|
||||||
|
window.setTimeout(() => navigate('youth-mode-verify', {
|
||||||
|
ym: 'disable'
|
||||||
|
}), 800);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setError('');
|
setError('');
|
||||||
setToast('独立密码设置成功');
|
setToast('独立密码设置成功');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -6818,6 +6847,7 @@ function IndependentPassword({
|
|||||||
}
|
}
|
||||||
function AuthOnboardingView({
|
function AuthOnboardingView({
|
||||||
view,
|
view,
|
||||||
|
hashParams,
|
||||||
onTenantResolved,
|
onTenantResolved,
|
||||||
authenticated = false,
|
authenticated = false,
|
||||||
login,
|
login,
|
||||||
@@ -6862,7 +6892,8 @@ function AuthOnboardingView({
|
|||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(PasswordReset, {}, void 0, false);
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(PasswordReset, {}, void 0, false);
|
||||||
case 'identity-check':
|
case 'identity-check':
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(IdentityCheck, {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(IdentityCheck, {
|
||||||
authenticated: authenticated
|
authenticated: authenticated,
|
||||||
|
hashParams: hashParams
|
||||||
}, void 0, false);
|
}, void 0, false);
|
||||||
case 'realname':
|
case 'realname':
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(Realname, {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(Realname, {
|
||||||
@@ -6876,7 +6907,8 @@ function AuthOnboardingView({
|
|||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(RealnameReminder, {}, void 0, false);
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(RealnameReminder, {}, void 0, false);
|
||||||
case 'independent-password':
|
case 'independent-password':
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(IndependentPassword, {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(IndependentPassword, {
|
||||||
authenticated: authenticated
|
authenticated: authenticated,
|
||||||
|
hashParams: hashParams
|
||||||
}, void 0, false);
|
}, void 0, false);
|
||||||
default:
|
default:
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(TenantEntry, {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)(TenantEntry, {
|
||||||
@@ -7624,14 +7656,13 @@ function AddressBook({
|
|||||||
const [bootstrap] = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.useCachedData)(myUserId ? 'bootstrap:' + myUserId : null, _lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getBootstrap, [myUserId]);
|
const [bootstrap] = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.useCachedData)(myUserId ? 'bootstrap:' + myUserId : null, _lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getBootstrap, [myUserId]);
|
||||||
const fileAssistantTopic = bootstrap && bootstrap.entries && bootstrap.entries.file_assistant && bootstrap.entries.file_assistant.topic;
|
const fileAssistantTopic = bootstrap && bootstrap.entries && bootstrap.entries.file_assistant && bootstrap.entries.file_assistant.topic;
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriends)().then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriends)('', cursor)).then(list => {
|
||||||
const list = data.items || [];
|
|
||||||
if (friendsCacheKey) {
|
if (friendsCacheKey) {
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)(friendsCacheKey, list);
|
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)(friendsCacheKey, list);
|
||||||
}
|
}
|
||||||
setFriends(list);
|
setFriends(list);
|
||||||
}).catch(() => {}).finally(() => setLoading(false));
|
}).catch(() => {}).finally(() => setLoading(false));
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriendRequests)('inbound', 'pending').then(data => setPendingCount((data.items || []).length)).catch(() => {});
|
(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 || ''}`.includes(query));
|
||||||
const groups = visible.reduce((result, item) => {
|
const groups = visible.reduce((result, item) => {
|
||||||
@@ -7759,7 +7790,7 @@ function FriendRequests({
|
|||||||
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
||||||
const [loading, setLoading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(true);
|
const [loading, setLoading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(true);
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriendRequests)('all', 'all').then(data => setItems(data.items || [])).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)('all', 'all', cursor)).then(setItems).catch(() => {}).finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
const handle = (item, accept) => {
|
const handle = (item, accept) => {
|
||||||
const nextStatus = accept ? 'accepted' : 'rejected';
|
const nextStatus = accept ? 'accepted' : 'rejected';
|
||||||
@@ -8362,8 +8393,8 @@ function Settings({
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriends)().then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getFriends)('', cursor)).then(list => {
|
||||||
const entry = (data.items || []).find(item => item.user_id === uid);
|
const entry = list.find(item => item.user_id === uid);
|
||||||
if (entry) setRemark(entry.remark || '');
|
if (entry) setRemark(entry.remark || '');
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, [uid]);
|
}, [uid]);
|
||||||
@@ -8522,7 +8553,7 @@ function BlockList({
|
|||||||
const [people, setPeople] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
const [people, setPeople] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
||||||
const [loading, setLoading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(true);
|
const [loading, setLoading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(true);
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getBlocks)().then(data => setPeople(data.items || [])).catch(() => {}).finally(() => setLoading(false));
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getBlocks)(cursor)).then(setPeople).catch(() => {}).finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
const remove = userId => {
|
const remove = userId => {
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.unblockUser)(userId).then(() => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.unblockUser)(userId).then(() => {
|
||||||
@@ -12006,8 +12037,9 @@ function AdminList({
|
|||||||
setOwner(d.owner);
|
setOwner(d.owner);
|
||||||
setAdmins(d.admins);
|
setAdmins(d.admins);
|
||||||
}
|
}
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic).then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
||||||
const items = data.items || [];
|
cursor
|
||||||
|
})).then(items => {
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-members:' + topic, items);
|
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-members:' + topic, items);
|
||||||
const d = deriveOwnerAndAdmins(items);
|
const d = deriveOwnerAndAdmins(items);
|
||||||
setOwner(d.owner);
|
setOwner(d.owner);
|
||||||
@@ -12094,8 +12126,9 @@ function GroupMembers({
|
|||||||
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic).then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
||||||
const items = data.items || [];
|
cursor
|
||||||
|
})).then(items => {
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-members:' + topic, items);
|
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-members:' + topic, items);
|
||||||
setMembers(items);
|
setMembers(items);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@@ -12158,11 +12191,14 @@ function MemberPicker({
|
|||||||
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, mute ? {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
||||||
muteEligible: true
|
cursor,
|
||||||
} : {
|
...(mute ? {
|
||||||
eligibleRole: 'admin'
|
muteEligible: true
|
||||||
}).then(data => setMembers((data.items || []).filter(m => m.eligible !== false))).catch(() => {
|
} : {
|
||||||
|
eligibleRole: 'admin'
|
||||||
|
})
|
||||||
|
})).then(items => setMembers(items.filter(m => m.eligible !== false))).catch(() => {
|
||||||
onToast('加载失败');
|
onToast('加载失败');
|
||||||
setLoadError(true);
|
setLoadError(true);
|
||||||
});
|
});
|
||||||
@@ -12242,18 +12278,19 @@ function Muted({
|
|||||||
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
const load = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(() => {
|
const load = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMutes)(topic).then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMutes)(topic, cursor)).then(list => {
|
||||||
const list = data.items || [];
|
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-muted:' + topic, list);
|
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-muted:' + topic, list);
|
||||||
setItems(list);
|
setItems(list);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
onToast('加载失败');
|
onToast('加载失败');
|
||||||
setLoadError(true);
|
setLoadError(true);
|
||||||
});
|
});
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic).then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
||||||
|
cursor
|
||||||
|
})).then(items => {
|
||||||
const map = {};
|
const map = {};
|
||||||
const avatarMap = {};
|
const avatarMap = {};
|
||||||
(data.items || []).forEach(m => {
|
items.forEach(m => {
|
||||||
map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
|
map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
|
||||||
if (m.avatar_file_id) avatarMap[m.user_id] = {
|
if (m.avatar_file_id) avatarMap[m.user_id] = {
|
||||||
avatar_file_id: m.avatar_file_id
|
avatar_file_id: m.avatar_file_id
|
||||||
@@ -12324,9 +12361,10 @@ function OwnerSelect({
|
|||||||
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
if (!topic) return;
|
if (!topic) return;
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.fetchAllPages)(cursor => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic, {
|
||||||
|
cursor,
|
||||||
eligibleRole: 'owner'
|
eligibleRole: 'owner'
|
||||||
}).then(data => setMembers((data.items || []).filter(m => m.eligible !== false))).catch(() => {
|
})).then(items => setMembers(items.filter(m => m.eligible !== false))).catch(() => {
|
||||||
onToast('加载失败');
|
onToast('加载失败');
|
||||||
setLoadError(true);
|
setLoadError(true);
|
||||||
});
|
});
|
||||||
@@ -12793,11 +12831,8 @@ function GroupManagementView({
|
|||||||
}
|
}
|
||||||
const key = `group-my-role:${topic}:${myUserId}`;
|
const key = `group-my-role:${topic}:${myUserId}`;
|
||||||
setMyRole((0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.readCachedData)(key) || null);
|
setMyRole((0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.readCachedData)(key) || null);
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupMembers)(topic).then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroup)(topic).then(group => {
|
||||||
const items = data.items || [];
|
const role = group ? group.my_role : null;
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-members:' + topic, items);
|
|
||||||
const me = items.find(m => m.user_id === myUserId);
|
|
||||||
const role = me ? me.role : null;
|
|
||||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)(key, role);
|
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)(key, role);
|
||||||
setMyRole(role);
|
setMyRole(role);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
@@ -15738,7 +15773,7 @@ function DeleteAccountConfirm({
|
|||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(ConfirmSheet, {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(ConfirmSheet, {
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
title: "注销账号",
|
title: "注销账号",
|
||||||
body: "注销后账号数据将被永久删除且无法恢复,包括聊天记录、好友关系和收藏内容。确定要继续吗?",
|
body: "注销后账号数据将被永久删除且无法恢复,包括聊天记录、好友关系和收藏内容。如果你是某些群的群主,这些群也将被永久解散,其他成员将无法继续使用。确定要继续吗?",
|
||||||
confirmLabel: "继续",
|
confirmLabel: "继续",
|
||||||
onConfirm: () => setStep(2),
|
onConfirm: () => setStep(2),
|
||||||
onCancel: () => navigate('android-settings')
|
onCancel: () => navigate('android-settings')
|
||||||
@@ -16020,7 +16055,9 @@ function YouthMode({
|
|||||||
if (!settings) return;
|
if (!settings) return;
|
||||||
if (!settings.password_set) {
|
if (!settings.password_set) {
|
||||||
onToast('请先设置独立密码');
|
onToast('请先设置独立密码');
|
||||||
window.setTimeout(() => navigate('identity-check'), 400);
|
window.setTimeout(() => navigate('identity-check', {
|
||||||
|
ym: 'enable'
|
||||||
|
}), 400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate('youth-mode-verify', {
|
navigate('youth-mode-verify', {
|
||||||
@@ -16151,7 +16188,9 @@ function YouthModeVerify({
|
|||||||
}, 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: "auth-text-button",
|
className: "auth-text-button",
|
||||||
onClick: () => navigate('identity-check'),
|
onClick: () => navigate('identity-check', {
|
||||||
|
ym: mode
|
||||||
|
}),
|
||||||
children: "忘记独立密码?"
|
children: "忘记独立密码?"
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true)]
|
}, void 0, true)]
|
||||||
@@ -17252,6 +17291,7 @@ class SidepanelView extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
className: "auth-onboarding-panel",
|
className: "auth-onboarding-panel",
|
||||||
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_25__.jsxDEV)(_auth_onboarding_view_jsx__WEBPACK_IMPORTED_MODULE_6__["default"], {
|
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_25__.jsxDEV)(_auth_onboarding_view_jsx__WEBPACK_IMPORTED_MODULE_6__["default"], {
|
||||||
view: view,
|
view: view,
|
||||||
|
hashParams: this.props.hashParams,
|
||||||
authenticated: !!this.props.myUserId,
|
authenticated: !!this.props.myUserId,
|
||||||
login: this.props.login,
|
login: this.props.login,
|
||||||
loginDisabled: this.props.loginDisabled,
|
loginDisabled: this.props.loginDisabled,
|
||||||
@@ -19317,6 +19357,9 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
canStartCall(topicName) {
|
canStartCall(topicName) {
|
||||||
|
if (this.state.callState != _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const topic = this.tinode.getTopic(topicName);
|
const topic = this.tinode.getTopic(topicName);
|
||||||
if (!topic || topic._deleted || !tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.isP2PTopicName(topicName)) {
|
if (!topic || topic._deleted || !tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.isP2PTopicName(topicName)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -19729,7 +19772,8 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
callTopic: undefined,
|
callTopic: undefined,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
||||||
callAudioOnly: undefined,
|
callAudioOnly: undefined,
|
||||||
callSeq: undefined
|
callSeq: undefined,
|
||||||
|
callShouldStart: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleCallAccept(topicName, setCallTopic, callSeq) {
|
handleCallAccept(topicName, setCallTopic, callSeq) {
|
||||||
@@ -19766,7 +19810,8 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
callTopic: null,
|
callTopic: null,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
||||||
callSeq: null,
|
callSeq: null,
|
||||||
callAudioOnly: undefined
|
callAudioOnly: undefined,
|
||||||
|
callShouldStart: false
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -20426,8 +20471,8 @@ function resolveUserName(id, onDone) {
|
|||||||
onDone(nameCache.get(id));
|
onDone(nameCache.get(id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const withPrefix = id.startsWith('usr') ? id : 'usr' + id;
|
const withPrefix = id.startsWith('usr') ? null : 'usr' + id;
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getUserProfile)(id).catch(() => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getUserProfile)(withPrefix)).then(p => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getUserProfile)(id).catch(err => withPrefix ? (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getUserProfile)(withPrefix) : Promise.reject(err)).then(p => {
|
||||||
const name = p && (p.display_name || p.nickname || p.public_account) || id;
|
const name = p && (p.display_name || p.nickname || p.public_account) || id;
|
||||||
nameCache.set(id, name);
|
nameCache.set(id, name);
|
||||||
onDone(name);
|
onDone(name);
|
||||||
@@ -22458,6 +22503,7 @@ const BAR_COLOR = '#888A';
|
|||||||
const BAR_COLOR_DARK = '#666C';
|
const BAR_COLOR_DARK = '#666C';
|
||||||
const THUMB_COLOR = '#1781d7';
|
const THUMB_COLOR = '#1781d7';
|
||||||
const MIN_PREVIEW_LENGTH = 16;
|
const MIN_PREVIEW_LENGTH = 16;
|
||||||
|
let activeAudioPlayer = null;
|
||||||
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||||
icon_title_play: {
|
icon_title_play: {
|
||||||
id: 'icon_title_play',
|
id: 'icon_title_play',
|
||||||
@@ -22498,6 +22544,9 @@ class AudioPlayer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
this.initCanvas();
|
this.initCanvas();
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
if (activeAudioPlayer === this) {
|
||||||
|
activeAudioPlayer = null;
|
||||||
|
}
|
||||||
if (this.audioPlayer) {
|
if (this.audioPlayer) {
|
||||||
this.audioPlayer.onloadedmetadata = null;
|
this.audioPlayer.onloadedmetadata = null;
|
||||||
this.audioPlayer.ontimeupdate = null;
|
this.audioPlayer.ontimeupdate = null;
|
||||||
@@ -22506,6 +22555,14 @@ class AudioPlayer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
this.audioPlayer = null;
|
this.audioPlayer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pauseForOtherPlayer() {
|
||||||
|
if (this.audioPlayer) {
|
||||||
|
this.audioPlayer.pause();
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
playing: false
|
||||||
|
});
|
||||||
|
}
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.src != prevProps.src) {
|
if (this.props.src != prevProps.src) {
|
||||||
this.initAudio();
|
this.initAudio();
|
||||||
@@ -22534,6 +22591,9 @@ class AudioPlayer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
|
|||||||
playing: false,
|
playing: false,
|
||||||
currentTime: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_2__.secondsToTime)(0, this.state.longMin)
|
currentTime: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_2__.secondsToTime)(0, this.state.longMin)
|
||||||
});
|
});
|
||||||
|
if (activeAudioPlayer === this) {
|
||||||
|
activeAudioPlayer = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
initCanvas() {
|
initCanvas() {
|
||||||
@@ -24962,6 +25022,9 @@ class ContextMenu extends (react__WEBPACK_IMPORTED_MODULE_0___default().Componen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = topic.createMessage(params.content, false);
|
const msg = topic.createMessage(params.content, false);
|
||||||
|
if (params.head) {
|
||||||
|
msg.head = Object.assign(msg.head || {}, params.head);
|
||||||
|
}
|
||||||
return topic.publishDraft(msg).catch(err => {
|
return topic.publishDraft(msg).catch(err => {
|
||||||
if (errorHandler) {
|
if (errorHandler) {
|
||||||
errorHandler(err.message, 'err');
|
errorHandler(err.message, 'err');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1224,7 +1224,12 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
if (!topic || !this.props.selectedSeqs.length) {
|
if (!topic || !this.props.selectedSeqs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
topic.delMessagesList(this.props.selectedSeqs.slice(), true).then(_ => {
|
const remaining = this.props.selectedSeqs.filter(seq => !topic.cancelSend(seq));
|
||||||
|
if (!remaining.length) {
|
||||||
|
this.props.onExitSelectMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topic.delMessagesList(remaining, true).then(_ => {
|
||||||
this.props.onExitSelectMode();
|
this.props.onExitSelectMode();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.props.onError(err.message, 'err');
|
this.props.onError(err.message, 'err');
|
||||||
@@ -2719,6 +2724,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
|||||||
seq: this.props.seq,
|
seq: this.props.seq,
|
||||||
replace: this.props.edited ? parseInt(this.props.edited.split(':')[1]) : 0,
|
replace: this.props.edited ? parseInt(this.props.edited.split(':')[1]) : 0,
|
||||||
content: this.props.content,
|
content: this.props.content,
|
||||||
|
head: this.props.head,
|
||||||
userFrom: this.props.userFrom,
|
userFrom: this.props.userFrom,
|
||||||
userName: this.props.userName,
|
userName: this.props.userName,
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -140,7 +140,7 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
if (duration > _config_js__WEBPACK_IMPORTED_MODULE_6__.MAX_DURATION) {
|
if (duration > _config_js__WEBPACK_IMPORTED_MODULE_6__.MAX_DURATION) {
|
||||||
this.startedOn = null;
|
this.startedOn = null;
|
||||||
this.mediaRecorder.pause();
|
this.mediaRecorder.pause();
|
||||||
this.durationMillis += Date.now() - this.startedOn;
|
this.durationMillis = duration;
|
||||||
this.setState({
|
this.setState({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
recording: false,
|
recording: false,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user