项目文件夹

文件
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

147 行
5.1 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import { useRedBookStrings } from '../hooks/useRedBookStrings';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { UNSAFE_NavigationContext, useLocation, useNavigate } from 'react-router-dom';
import { useAppNavigationHandler } from '../../../os/hooks/useAppNavigationHandler';
import { realNow } from '../../../os/TimeService';
const EXIT_INTERVAL = 2000; // 2秒内双击退出
const ROOT_PATHS = ['/', '/market', '/message', '/me'];
const ROOT_OVERLAY_PARAMS = ['menu', 'modal'];
type RedBookBackIntent =
| { type: 'history-back' }
| { type: 'replace'; to: string }
| { type: 'finish' }
| { type: 'toast'; goHome: boolean };
export function getRedBookBackIntent(options: {
pathname: string;
search: string;
currentIndex: number;
lastBackTime: number;
now: number;
}): RedBookBackIntent {
const { pathname, search, currentIndex, lastBackTime, now } = options;
const isRootPath = ROOT_PATHS.includes(pathname);
const searchParams = new URLSearchParams(search);
const hasRootOverlay = ROOT_OVERLAY_PARAMS.some(key => searchParams.has(key));
if (isRootPath && hasRootOverlay) {
if (currentIndex > 0) return { type: 'history-back' };
return { type: 'replace', to: buildRootPathWithoutOverlay(pathname, searchParams) };
}
if (isRootPath) {
if (now - lastBackTime < EXIT_INTERVAL) return { type: 'finish' };
return { type: 'toast', goHome: pathname === '/message' || pathname === '/me' };
}
if (currentIndex > 0) return { type: 'history-back' };
return { type: 'replace', to: '/?tab=discover' };
}
function buildRootPathWithoutOverlay(pathname: string, searchParams: URLSearchParams): string {
ROOT_OVERLAY_PARAMS.forEach(key => searchParams.delete(key));
const search = searchParams.toString();
return search ? `${pathname}?${search}` : pathname;
}
// 标准 App 桥接:系统返回/路由观测/(可选)外部导航
export const RedBookNavigationHandler: React.FC = () => {
const s = useRedBookStrings();
const location = useLocation();
const navigate = useNavigate();
const { navigator } = React.useContext(UNSAFE_NavigationContext);
const historyIndexRef = useRef(0);
const lastBackTimeRef = useRef(0);
const [toast, setToast] = useState<{ show: boolean; msg: string }>({ show: false, msg: '' });
const showToast = (msg: string) => {
setToast({ show: true, msg });
setTimeout(() => setToast({ show: false, msg: '' }), 1500);
};
const normalizeInitialRoute = useCallback((path: string): string => {
// 允许传入 "/me"、"/me?x=y" 这类路径;为了可解析性加一个 base
try {
const url = new URL(path, 'http://redbook.local');
const pathname = url.pathname;
const params = url.searchParams;
// Home:首页 Tab 已迁到 URL。外部直达必须提供合法地址(如 "/?tab=discover")。
// User:兼容外部直达时缺省 query
if (pathname.startsWith('/user/') && pathname !== '/user/allUser') {
// 未来如果 UserPage tab 状态迁移到 query,这里可以补默认 tab
}
if (pathname === '/user/allUser' && !params.has('type')) {
// FollowList 默认:关注(type=1
params.set('type', '1');
}
const searchStr = params.toString();
return searchStr ? `${pathname}?${searchStr}` : pathname;
} catch {
return path;
}
}, []);
useEffect(() => {
const memoryNavigator = navigator as any;
if (typeof memoryNavigator?.index === 'number') {
historyIndexRef.current = memoryNavigator.index;
}
}, [location, navigator]);
const handleBackPress = useCallback((): boolean => {
const memoryNavigator = navigator as any;
const currentIndex =
typeof memoryNavigator?.index === 'number' ? memoryNavigator.index : historyIndexRef.current;
const now = realNow();
const intent = getRedBookBackIntent({
pathname: location.pathname,
search: location.search,
currentIndex,
lastBackTime: lastBackTimeRef.current,
now,
});
switch (intent.type) {
case 'history-back':
navigate(-1);
return true;
case 'replace':
navigate(intent.to, { replace: true });
return true;
case 'finish':
lastBackTimeRef.current = 0;
window.__OS__?.finishActivity?.();
return true;
case 'toast':
lastBackTimeRef.current = now;
showToast(s.press_again_to_go_home);
if (intent.goHome) {
navigate('/?tab=discover', { replace: true });
}
return true;
}
}, [location.pathname, location.search, navigate, navigator, s.press_again_to_go_home]);
useAppNavigationHandler('redbook', {
onBack: handleBackPress,
onNavigate: (path, navigateToPath) => {
navigateToPath(normalizeInitialRoute(path));
},
});
// 渲染居中 Toast
return toast.show ? (
<div className="fixed inset-0 flex items-center justify-center z-[9999] pointer-events-none">
<div className="bg-black/80 text-white px-6 py-3 rounded-lg text-sm font-medium shadow-lg animate-in fade-in zoom-in-95 duration-200">
{toast.msg}
</div>
</div>
) : null;
};