补齐侧栏/右栏图标与徽章、鉴权种子、StaticFeedList,并修正嵌套 a 与标题徽章对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import type { NavigateFunction } from 'react-router-dom';
|
||
import type { PostItem } from '../api/types';
|
||
import type { FeedSort } from '../components/FeedSortBar';
|
||
import {
|
||
feedCacheKey,
|
||
getHomeStoreState,
|
||
type FeedCacheEntry,
|
||
} from '../store/homeStore';
|
||
import { clearSessionSnapshots } from './sessionPageCache';
|
||
import { softRefreshCurrentPage } from './softRefresh';
|
||
import { transitionTo } from './spaTransition';
|
||
|
||
/** 导航到帖子列表时附带的状态,用于同 URL 重复点击时强制刷新 */
|
||
export type FeedNavState = { refreshFeed?: boolean };
|
||
|
||
/** 对外暴露的缓存形状(兼容旧调用方;不含 lastFetchTime) */
|
||
export type FeedCache = {
|
||
posts: PostItem[];
|
||
postTotal: number;
|
||
page: number;
|
||
scrollTop: number;
|
||
};
|
||
|
||
function toPublic(entry: FeedCacheEntry): FeedCache {
|
||
return {
|
||
posts: entry.posts,
|
||
postTotal: entry.postTotal,
|
||
page: entry.page,
|
||
scrollTop: entry.scrollTop,
|
||
};
|
||
}
|
||
|
||
/** 读取帖子列表缓存(从详情页返回时恢复浏览位置) */
|
||
export function getFeedCache(
|
||
boardId: number,
|
||
keyword: string,
|
||
sort: FeedSort,
|
||
tag = '',
|
||
author = '',
|
||
titleOnly = false,
|
||
): FeedCache | null {
|
||
const key = feedCacheKey({ boardId, keyword, sort, tag, author, titleOnly });
|
||
const entry = getHomeStoreState().getFeed(key);
|
||
return entry ? toPublic(entry) : null;
|
||
}
|
||
|
||
/** 保存帖子列表缓存(写入 Zustand,并记录拉取时间) */
|
||
export function setFeedCache(
|
||
boardId: number,
|
||
keyword: string,
|
||
sort: FeedSort,
|
||
data: FeedCache,
|
||
tag = '',
|
||
author = '',
|
||
titleOnly = false,
|
||
) {
|
||
const key = feedCacheKey({ boardId, keyword, sort, tag, author, titleOnly });
|
||
getHomeStoreState().setFeed(key, data);
|
||
}
|
||
|
||
/** 清除所有帖子列表缓存(手动刷新 / 帖子变更时连详情快照一起作废) */
|
||
export function clearAllFeedCache() {
|
||
getHomeStoreState().clearAll();
|
||
clearSessionSnapshots('post:');
|
||
}
|
||
|
||
/** 主动刷新帖子列表时派发,用于同页内立即回到顶部 */
|
||
export const FEED_RESET_EVENT = 'feed-reset';
|
||
|
||
/** 手机下拉刷新(Feed 页):强制重拉列表并重置滚动,不整页 reload */
|
||
export const FEED_PULL_REFRESH_EVENT = 'feed-pull-refresh';
|
||
|
||
/** 手机下拉:强制刷新当前前台页(绕过会话快照,非整页 reload) */
|
||
export const PAGE_FORCE_REFRESH_EVENT = 'page-force-refresh';
|
||
|
||
/** 当前浏览器地址是否已是目标 Feed URL(忽略 hash) */
|
||
function isSameFeedUrl(url: string): boolean {
|
||
try {
|
||
const target = new URL(url, window.location.origin);
|
||
return (
|
||
window.location.pathname === target.pathname
|
||
&& window.location.search === target.search
|
||
);
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导航到帖子列表。
|
||
* 默认:等待预热后再换页;同 URL 或 `refresh: true` 时软刷新(带顶栏进度条)。
|
||
*/
|
||
export function navigateFeed(nav: NavigateFunction, url: string, opts?: { refresh?: boolean }) {
|
||
const same = isSameFeedUrl(url);
|
||
const refresh = opts?.refresh ?? same;
|
||
if (refresh) {
|
||
if (same) {
|
||
void softRefreshCurrentPage(url, { progress: true });
|
||
return;
|
||
}
|
||
void transitionTo(nav, url, {
|
||
force: true,
|
||
state: { refreshFeed: true } satisfies FeedNavState,
|
||
});
|
||
return;
|
||
}
|
||
void transitionTo(nav, url);
|
||
}
|