// 播放上下文:播放页「上一条 / 下一条」在哪个列表里移动。 // 它放在 /aweme/ 的查询参数里,刷新、新标签页打开、后退都不会丢: // (无) 全部作品,按发布时间倒序(和首页一致) // ?ctx=tag&tag=美女 某个标签 // ?ctx=author&author= 某个作者的作品 // ?ctx=search&q=百姓&tag=… 某次搜索的结果 // ?ctx=hashtag&hashtag=丰川祥子 描述里带某个 hashtag 的作品 // 这个文件只放纯函数,客户端组件也可以引用;查数据库的部分在 playback-context.server.ts。 import { normalizeHashtag } from "@/lib/hashtag"; import { isVideoContentTag } from "@/lib/video-tags"; export type PlaybackContext = | { kind: "search"; q: string; tag: string | null } | { kind: "tag"; tag: string } | { kind: "hashtag"; name: string } | { kind: "author"; secUid: string }; /** 播放页侧栏网格里的一项(/api/playlist 的返回) */ export type PlaylistItem = { aweme_id: string; type: "video" | "image"; desc: string; cover_url: string | null; likes: number; }; export type PlaylistResponse = { items: PlaylistItem[]; total: number }; type ParamSource = | URLSearchParams | Record; function readParam(source: ParamSource, key: string): string { const value = source instanceof URLSearchParams ? source.get(key) : source[key]; return (Array.isArray(value) ? value[0] : value)?.trim() ?? ""; } export function parsePlaybackContext( source: ParamSource, ): PlaybackContext | null { const kind = readParam(source, "ctx"); const tag = readParam(source, "tag"); const validTag = tag && isVideoContentTag(tag) ? tag : null; if (kind === "search") { const q = readParam(source, "q"); if (!q && !validTag) return null; return { kind: "search", q, tag: validTag }; } if (kind === "tag" && validTag) return { kind: "tag", tag: validTag }; if (kind === "hashtag") { const name = normalizeHashtag(readParam(source, "hashtag")); if (name) return { kind: "hashtag", name }; } if (kind === "author") { const secUid = readParam(source, "author"); if (secUid) return { kind: "author", secUid }; } return null; } /** 生成带上下文的查询串(含开头的 ?);没有上下文时返回空串。 */ export function playbackContextQuery(ctx: PlaybackContext | null): string { if (!ctx) return ""; const params = new URLSearchParams({ ctx: ctx.kind }); if (ctx.kind === "search") { if (ctx.q) params.set("q", ctx.q); if (ctx.tag) params.set("tag", ctx.tag); } else if (ctx.kind === "tag") { params.set("tag", ctx.tag); } else if (ctx.kind === "hashtag") { params.set("hashtag", ctx.name); } else { params.set("author", ctx.secUid); } return `?${params.toString()}`; } export function awemeHref(awemeId: string, query = ""): string { return `/aweme/${awemeId}${query}`; }