Files
douyin-archive/lib/hashtag.ts
T
feie9454andClaude Opus 5.5 b84fcec77b feat: 播放页按来源列表切换,新增作者/搜索/hashtag 侧栏
- 播放上下文:作品链接带上来源(搜索、标签、作者、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>
2026-11-09 00:50:09 +08:00

50 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 从作品描述里解析抖音 hashtag(#xxx)。
// 前端渲染可点击的 hashtag 与后端按 hashtag 精确筛选作品共用这里的边界规则,
// 保证「点了 #丰川祥子 看到的列表」不会混进 #丰川祥子cos。
// hashtag 在遇到空白、下一个 #、或这些标点时结束
const STOP_CHARS =
"#,。!?、;:,.!?;:()()【】\\[\\]《》<>\"'“”‘’…~~|/\\\\@";
const MAX_HASHTAG_LENGTH = 50;
const HASHTAG_RE = new RegExp(`#([^\\s${STOP_CHARS}]+)`, "gu");
export type DescPart =
| { type: "text"; text: string }
| { type: "hashtag"; text: string; name: string };
/** 把描述切成普通文字与 hashtag 片段。 */
export function parseDescHashtags(desc: string): DescPart[] {
const parts: DescPart[] = [];
let cursor = 0;
for (const m of desc.matchAll(HASHTAG_RE)) {
const start = m.index ?? 0;
if (start > cursor) {
parts.push({ type: "text", text: desc.slice(cursor, start) });
}
parts.push({ type: "hashtag", text: m[0], name: m[1] });
cursor = start + m[0].length;
}
if (cursor < desc.length) {
parts.push({ type: "text", text: desc.slice(cursor) });
}
return parts;
}
/** 规范化 hashtag 名(去掉开头的 #、首尾空白,限制长度);不合法时返回 null。 */
export function normalizeHashtag(raw: string): string | null {
const name = raw.trim().replace(/^#+/, "").slice(0, MAX_HASHTAG_LENGTH);
if (!name) return null;
if (new RegExp(`[\\s${STOP_CHARS}]`, "u").test(name)) return null;
return name;
}
/**
* 匹配 lower() 后描述文本的 Postgres 正则(ARE):#名字 之后必须是结束符或文本末尾。
* 名字里的正则特殊字符会被转义。
*/
export function hashtagToPgPattern(name: string): string {
const escaped = name.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return `#${escaped}([\\s${STOP_CHARS}]|$)`;
}