fix(收藏/聊天记录检索): 切换页内tab不再污染浏览器历史记录
用户反馈"我的收藏"页面点击tabs切换(全部/图片/视频/文件/语音/文本/链接)会记录到浏览器 历史记录里,导致点击返回一直在这个页面内切换tab,而不是真正返回上一页。 根因:content-library-view.jsx的Favorites/HistoryNav每次切tab都调用navigate(), 即HashNavigation.navigateTo() -> location.hash = ...,每次赋值都会被浏览器记为一条 新的历史记录。切N次tab就push N条记录,无论是系统返回键还是应用自己的"返回"按钮 (HashNavigation.goBack()),都要先把这N条tab切换记录走完才能真正离开页面。 修复:新增HashNavigation.navigateReplace(),用history.replaceState替换当前历史记录 而不是新增一条(hash该更新还是更新,刷新/复制链接不受影响),并手动派发hashchange事件 供tinode-web.jsx现有的监听器捕获——replaceState本身不会自动触发该事件。Favorites的 TYPE_LABELS tabs和HistoryNav("查找聊天记录"页的聊天记录/图片/视频/文件/链接tabs) 两处tab切换都改用这个新方法,不再计入navigationCount。 已在浏览器实测两处:连续切3次tab后,无论是原生history.back()还是应用返回按钮,都能 一次性返回到进入这个页面之前的真实上一页,不再卡在tab切换历史里。
This commit is contained in:
@@ -40,6 +40,22 @@ export default class HashNavigation {
|
|||||||
window.location.hash = url;
|
window.location.hash = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For switching between sub-tabs of the *same* page (e.g. content-library-view.jsx's favorites/
|
||||||
|
// history type tabs), not for navigating to a different page. `navigateTo`/`location.hash =`
|
||||||
|
// always pushes a new history entry, so clicking through N tabs pushes N entries -- the system
|
||||||
|
// back button (and this class's own goBack()) then has to step back through every tab the user
|
||||||
|
// ever visited before it reaches whatever page they were actually on before this one. `replaceState`
|
||||||
|
// updates the hash (still correct for a refresh or a copied link) without adding a history entry,
|
||||||
|
// so it doesn't inflate navigationCount either -- a tab switch was never a real in-app navigation
|
||||||
|
// to begin with. replaceState doesn't fire `hashchange` on its own, so dispatch one manually for
|
||||||
|
// the existing `window.addEventListener('hashchange', ...)` listener (tinode-web.jsx's
|
||||||
|
// handleHashRoute) to pick up; that handler re-reads window.location.hash itself rather than the
|
||||||
|
// event, so a synthetic event with no detail is indistinguishable from a real one to it.
|
||||||
|
static navigateReplace(url) {
|
||||||
|
window.history.replaceState(null, '', '#' + url);
|
||||||
|
window.dispatchEvent(new Event('hashchange'));
|
||||||
|
}
|
||||||
|
|
||||||
// Real "back": retraces the hash the user was actually on before, via native browser history,
|
// Real "back": retraces the hash the user was actually on before, via native browser history,
|
||||||
// rather than a page's own guess at where it must have been reached from. Returns whether it
|
// rather than a page's own guess at where it must have been reached from. Returns whether it
|
||||||
// actually did that -- every call site already has its own hardcoded-destination `navigate(...)`
|
// actually did that -- every call site already has its own hardcoded-destination `navigate(...)`
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ const navigate = (view, params) => {
|
|||||||
HashNavigation.navigateTo(hash);
|
HashNavigation.navigateTo(hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Same as navigate() above but for switching this page's own type tabs (Favorites' 全部/图片/.../链接,
|
||||||
|
// History's 聊天记录/图片/视频/文件/链接) -- these change which content shows on the *same* page, not
|
||||||
|
// which page the user is on, so they shouldn't push a browser-history entry the way a real navigate()
|
||||||
|
// does. Previously every tab click called navigate(), so N tab switches pushed N history entries and
|
||||||
|
// the system back button just cycled through past tabs instead of leaving the page.
|
||||||
|
const navigateReplace = (view) => HashNavigation.navigateReplace(HashNavigation.setUrlSidePanel(window.location.hash, view));
|
||||||
|
|
||||||
export const CONTENT_LIBRARY_VIEWS = [
|
export const CONTENT_LIBRARY_VIEWS = [
|
||||||
'favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice',
|
'favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice',
|
||||||
'favorites-links', 'favorites-image', 'favorites-video', 'favorites-select', 'favorite-share', 'file-download',
|
'favorites-links', 'favorites-image', 'favorites-video', 'favorites-select', 'favorite-share', 'file-download',
|
||||||
@@ -356,7 +363,7 @@ function Favorites({view, tinode, onToast}) {
|
|||||||
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? 'favorites-files' : 'favorites-select')} />
|
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? 'favorites-files' : 'favorites-select')} />
|
||||||
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索收藏内容" />
|
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索收藏内容" />
|
||||||
<nav className="library-type-tabs">{TYPE_LABELS.map(([id, label]) => <button type="button" key={id} className={type === id ? 'active' : ''}
|
<nav className="library-type-tabs">{TYPE_LABELS.map(([id, label]) => <button type="button" key={id} className={type === id ? 'active' : ''}
|
||||||
onClick={() => { setType(id); if (TYPE_ROUTE[id] && !selecting) navigate(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
onClick={() => { setType(id); if (TYPE_ROUTE[id] && !selecting) navigateReplace(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
||||||
<main className="library-favorite-list">
|
<main className="library-favorite-list">
|
||||||
{items === null ? <p className="library-empty-hint">加载中…</p> :
|
{items === null ? <p className="library-empty-hint">加载中…</p> :
|
||||||
list.length ? <>{list.map(item => <FavoriteItem key={item.favorite_id} item={{...item, _thumbUrl: fileUrls[item.favorite_id],
|
list.length ? <>{list.map(item => <FavoriteItem key={item.favorite_id} item={{...item, _thumbUrl: fileUrls[item.favorite_id],
|
||||||
@@ -563,7 +570,7 @@ function Highlight({text, query}) {
|
|||||||
|
|
||||||
function HistoryNav({active}) {
|
function HistoryNav({active}) {
|
||||||
const tabs = [['history-search', '聊天记录'], ['history-images', '图片'], ['history-videos', '视频'], ['history-files', '文件'], ['history-links', '链接']];
|
const tabs = [['history-search', '聊天记录'], ['history-images', '图片'], ['history-videos', '视频'], ['history-files', '文件'], ['history-links', '链接']];
|
||||||
return <nav className="library-history-tabs">{tabs.map(item => <button type="button" key={item[0]} className={active === item[0] ? 'active' : ''} onClick={() => navigate(item[0])}>{item[1]}</button>)}</nav>;
|
return <nav className="library-history-tabs">{tabs.map(item => <button type="button" key={item[0]} className={active === item[0] ? 'active' : ''} onClick={() => navigateReplace(item[0])}>{item[1]}</button>)}</nav>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HISTORY_TITLES = {'history-search': '查找聊天记录', 'history-files': '文件', 'history-links': '链接', 'history-images': '图片', 'history-videos': '视频'};
|
const HISTORY_TITLES = {'history-search': '查找聊天记录', 'history-files': '文件', 'history-links': '链接', 'history-images': '图片', 'history-videos': '视频'};
|
||||||
|
|||||||
+7
-2
@@ -4583,6 +4583,10 @@ class HashNavigation {
|
|||||||
navigationCount++;
|
navigationCount++;
|
||||||
window.location.hash = url;
|
window.location.hash = url;
|
||||||
}
|
}
|
||||||
|
static navigateReplace(url) {
|
||||||
|
window.history.replaceState(null, '', '#' + url);
|
||||||
|
window.dispatchEvent(new Event('hashchange'));
|
||||||
|
}
|
||||||
static goBack() {
|
static goBack() {
|
||||||
if (navigationCount > 0) {
|
if (navigationCount > 0) {
|
||||||
navigationCount--;
|
navigationCount--;
|
||||||
@@ -8931,6 +8935,7 @@ const navigate = (view, params) => {
|
|||||||
});
|
});
|
||||||
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].navigateTo(hash);
|
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].navigateTo(hash);
|
||||||
};
|
};
|
||||||
|
const navigateReplace = view => _lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].navigateReplace(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].setUrlSidePanel(window.location.hash, view));
|
||||||
const CONTENT_LIBRARY_VIEWS = ['favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice', 'favorites-links', 'favorites-image', 'favorites-video', 'favorites-select', 'favorite-share', 'file-download', 'file-downloading', 'file-downloaded', 'history-search', 'history-files', 'history-links', 'history-images', 'history-videos'];
|
const CONTENT_LIBRARY_VIEWS = ['favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice', 'favorites-links', 'favorites-image', 'favorites-video', 'favorites-select', 'favorite-share', 'file-download', 'file-downloading', 'file-downloaded', 'history-search', 'history-files', 'history-links', 'history-images', 'history-videos'];
|
||||||
function Icon({
|
function Icon({
|
||||||
children
|
children
|
||||||
@@ -9432,7 +9437,7 @@ function Favorites({
|
|||||||
className: type === id ? 'active' : '',
|
className: type === id ? 'active' : '',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setType(id);
|
setType(id);
|
||||||
if (TYPE_ROUTE[id] && !selecting) navigate(TYPE_ROUTE[id]);
|
if (TYPE_ROUTE[id] && !selecting) navigateReplace(TYPE_ROUTE[id]);
|
||||||
},
|
},
|
||||||
children: label
|
children: label
|
||||||
}, id, false))
|
}, id, false))
|
||||||
@@ -9797,7 +9802,7 @@ function HistoryNav({
|
|||||||
children: tabs.map(item => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
|
children: tabs.map(item => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
className: active === item[0] ? 'active' : '',
|
className: active === item[0] ? 'active' : '',
|
||||||
onClick: () => navigate(item[0]),
|
onClick: () => navigateReplace(item[0]),
|
||||||
children: item[1]
|
children: item[1]
|
||||||
}, item[0], false))
|
}, item[0], false))
|
||||||
}, void 0, false);
|
}, void 0, false);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user