- 播放上下文:作品链接带上来源(搜索、标签、作者、hashtag),上一条/下一条 在该列表内移动;当前作品不在列表中时退回全部作品的发布时间顺序 - 顶部提示标签显示当前列表与位置,点击打开侧栏列表,✕ 退出 - 侧栏改为「评论 | TA的作品 | 搜索结果/#hashtag」标签页,作品封面网格可就地切换 - 作者模式:打开「TA的作品」进入,关闭侧栏不退出,点提示 ✕ 后回到原来的列表 - 从搜索结果进入或点 hashtag 时自动打开对应列表 - 描述中的 hashtag 可点击,按完整 hashtag 精确匹配(#丰川祥子 不含 #丰川祥子cos) - 新增 /api/playlist;搜索 SQL 抽到 lib/search-query.ts 与播放页共用,保证顺序一致 - 移动端:底部抽屉、点作品后收起,媒体区支持上下滑切换 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
||
|
||
/** comments:评论;works:TA的作品;list:搜索结果 / hashtag / 标签列表 */
|
||
export type SidePanelTab = "comments" | "works" | "list";
|
||
|
||
// 侧栏开关沿用评论区原来的 localStorage 键;停在哪个标签页、是否处于作者模式只在本次会话内记住,
|
||
// 这样下次从首页点进来不会莫名其妙停在作者模式。
|
||
// 切换作品时播放页会整体重新挂载,靠这些存储把状态带到下一个作品。
|
||
const OPEN_KEY = "aweme_player_comments_open";
|
||
const TAB_KEY = "aweme_player_panel_tab";
|
||
const AUTHOR_MODE_KEY = "aweme_player_author_mode";
|
||
|
||
function readStorage(storage: () => Storage, key: string): string | null {
|
||
try {
|
||
return storage().getItem(key);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function writeStorage(
|
||
storage: () => Storage,
|
||
key: string,
|
||
value: string | null,
|
||
) {
|
||
try {
|
||
if (value === null) storage().removeItem(key);
|
||
else storage().setItem(key, value);
|
||
} catch {}
|
||
}
|
||
|
||
const local = () => window.localStorage;
|
||
const session = () => window.sessionStorage;
|
||
|
||
export function isPortraitViewport(): boolean {
|
||
return (
|
||
typeof window !== "undefined" &&
|
||
window.matchMedia("(orientation: portrait)").matches
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 侧栏状态与作者模式。
|
||
* 作者模式和侧栏开关是分开的:打开「TA的作品」进入作者模式(上下切换在该作者的作品里移动),
|
||
* 关掉侧栏不会退出,只有点顶部提示的 ✕、或切到「搜索结果 / #hashtag」列表才退出。
|
||
*/
|
||
export function useSidePanelState(authorSecUid: string | undefined) {
|
||
const [open, setOpen] = useState(false);
|
||
const [tab, setTab] = useState<SidePanelTab>("comments");
|
||
// 处于作者模式时记下是哪位作者;切到别的作者的作品(例如浏览器后退)时自动不生效
|
||
const [authorModeFor, setAuthorModeFor] = useState<string | null>(null);
|
||
const [restored, setRestored] = useState(false);
|
||
const [mounted, setMounted] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setOpen(readStorage(local, OPEN_KEY) === "true");
|
||
const savedTab = readStorage(session, TAB_KEY);
|
||
setTab(savedTab === "works" || savedTab === "list" ? savedTab : "comments");
|
||
setAuthorModeFor(readStorage(session, AUTHOR_MODE_KEY));
|
||
setRestored(true);
|
||
// 恢复完成后再开启过渡动画,避免进入页面时侧栏滑入
|
||
requestAnimationFrame(() => {
|
||
requestAnimationFrame(() => setMounted(true));
|
||
});
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!restored) return;
|
||
writeStorage(local, OPEN_KEY, String(open));
|
||
writeStorage(session, TAB_KEY, tab);
|
||
writeStorage(session, AUTHOR_MODE_KEY, authorModeFor);
|
||
}, [open, tab, authorModeFor, restored]);
|
||
|
||
// 切到哪个列表标签页,上下切换就改用哪个列表的顺序;「评论」不影响顺序
|
||
const applyTabMode = useCallback(
|
||
(nextTab: SidePanelTab) => {
|
||
if (nextTab === "works" && authorSecUid) setAuthorModeFor(authorSecUid);
|
||
else if (nextTab === "list") setAuthorModeFor(null);
|
||
},
|
||
[authorSecUid],
|
||
);
|
||
|
||
const selectTab = useCallback(
|
||
(nextTab: SidePanelTab) => {
|
||
setTab(nextTab);
|
||
applyTabMode(nextTab);
|
||
},
|
||
[applyTabMode],
|
||
);
|
||
|
||
const show = useCallback(
|
||
(nextTab: SidePanelTab) => {
|
||
selectTab(nextTab);
|
||
setOpen(true);
|
||
},
|
||
[selectTab],
|
||
);
|
||
|
||
const close = useCallback(() => setOpen(false), []);
|
||
|
||
/** 打开到指定标签页;已经打开在这个标签页时则关闭(关闭不影响顺序) */
|
||
const toggle = useCallback(
|
||
(nextTab: SidePanelTab) => {
|
||
if (open && tab === nextTab) close();
|
||
else show(nextTab);
|
||
},
|
||
[close, open, show, tab],
|
||
);
|
||
|
||
const exitAuthorMode = useCallback(() => setAuthorModeFor(null), []);
|
||
|
||
return {
|
||
open,
|
||
tab,
|
||
mounted,
|
||
/** 上下切换在当前作者的作品里移动 */
|
||
authorMode: Boolean(authorSecUid) && authorModeFor === authorSecUid,
|
||
/** 已从存储里恢复完状态(之后才能安全地自动打开侧栏) */
|
||
restored,
|
||
selectTab,
|
||
show,
|
||
close,
|
||
toggle,
|
||
exitAuthorMode,
|
||
};
|
||
}
|