// 工作台当前章号常量(纯模块,**非** "use client")。 // 必须放在非客户端模块:Server Component(write/page.tsx)要在服务端用它拼 GET draft 的 URL; // 若从客户端组件(Workbench.tsx,"use client")导入,Next 会把它替换成客户端引用代理, // 服务端使用时变成抛错的函数 → draft URL 变成 `/chapters/function(){…}/draft`(422)。 export const WORKBENCH_CHAPTER_NO = 1; // 从写作路由的 `?chapter=N` 解析章号:取正整数,非法/缺省/<1 → 默认第 1 章。 // 纯函数(可单测),供 write/page.tsx(Server Component)解析 searchParams。 export function parseChapterParam(raw: string | string[] | undefined): number { const value = Array.isArray(raw) ? raw[0] : raw; if (value === undefined) return WORKBENCH_CHAPTER_NO; const n = Number.parseInt(value, 10); return Number.isInteger(n) && n >= 1 ? n : WORKBENCH_CHAPTER_NO; } // 目录条目:章号 + 可选短标题(大纲无 title,用首个节拍当摘要)。 export interface ChapterEntry { no: number; title?: string; } // 合并大纲章节与「当前章」成目录:按章号去重 + 升序;当前章即使超出大纲也必含 // (否则在写大纲外的新章时会从目录里消失,作者迷路)。纯函数(可单测)。 export function buildChapterEntries( chapters: readonly ChapterEntry[], currentChapterNo: number, ): ChapterEntry[] { const byNo = new Map(); for (const c of chapters) { if (Number.isInteger(c.no) && c.no >= 1 && !byNo.has(c.no)) { byNo.set(c.no, { no: c.no, title: c.title }); } } if (!byNo.has(currentChapterNo)) { byNo.set(currentChapterNo, { no: currentChapterNo }); } return [...byNo.values()].sort((a, b) => a.no - b.no); }