// 左导航条目数据 + 激活判定(纯逻辑,供桌面静态侧栏 / 移动抽屉共用,UX §5.1/§3.1)。 // 组件只负责渲染;条目与高亮规则在此集中,避免侧栏与抽屉漂移。 export type ActiveNav = | "write" | "outline" | "foreshadow" | "review" | "style" | "codex" | "rules" | "skills" | "toolbox" | "chains"; export interface NavItem { href: string; label: string; // 项目级入口的激活键(与 activeNav 比对高亮);全局项无 key。 key?: ActiveNav; } export const GLOBAL_NAV_ITEMS: readonly NavItem[] = [ { href: "/", label: "作品库" }, { href: "/templates", label: "模板库" }, { href: "/settings/providers", label: "设置" }, ]; // 项目内页展开的项目级入口(UX §3.1)。 export function projectNavItems(projectId: string): NavItem[] { return [ { href: `/projects/${projectId}/write`, label: "写作", key: "write" }, { href: `/projects/${projectId}/outline`, label: "大纲", key: "outline" }, { href: `/projects/${projectId}/foreshadow`, label: "伏笔", key: "foreshadow", }, { href: `/projects/${projectId}/review`, label: "审稿", key: "review" }, { href: `/projects/${projectId}/chains`, label: "多章链", key: "chains" }, { href: `/projects/${projectId}/style`, label: "文风", key: "style" }, { href: `/projects/${projectId}/codex`, label: "设定库", key: "codex" }, { href: `/projects/${projectId}/toolbox`, label: "工具箱", key: "toolbox" }, { href: `/projects/${projectId}/rules`, label: "规则", key: "rules" }, { href: `/projects/${projectId}/skills`, label: "技能库", key: "skills" }, ]; } // 左栏一个分组:可选小标题 + 若干条目(P2-1)。全局组 label=null(顶部直显、无小标题); // 项目组复用现有"本作"式小标题样式。分组只重排 + 加标题,链接/路由/激活键均不变(行为保持)。 export interface NavSection { label: string | null; items: readonly NavItem[]; } // 项目级入口的语义分组(P2-1):把 10 个平铺项归为 写作/资料库/审阅/更多,便于扫读。 // 工具箱归入「更多」——原分组清单只列了 规则·技能库,收进"更多"以不丢任何入口 // (见 memory/decisions.md 2026-07-10 条)。 const PROJECT_GROUPS: readonly { label: string; keys: readonly ActiveNav[] }[] = [ { label: "写作", keys: ["write"] }, { label: "资料库", keys: ["outline", "codex", "foreshadow", "style"] }, { label: "审阅", keys: ["review", "chains"] }, { label: "更多", keys: ["toolbox", "rules", "skills"] }, ]; // 左导航分组(桌面侧栏 / 移动抽屉共用):全局组恒在顶部,项目内页再展开四个项目组。 export function navSections(projectId?: string): NavSection[] { const global: NavSection = { label: null, items: GLOBAL_NAV_ITEMS }; if (projectId === undefined) return [global]; const byKey = new Map(projectNavItems(projectId).map((item) => [item.key, item])); const projectSections = PROJECT_GROUPS.map((group) => ({ label: group.label, items: group.keys.map((key) => { const item = byKey.get(key); if (item === undefined) { throw new Error(`navSections: 缺少项目入口 ${key}`); } return item; }), })); return [global, ...projectSections]; } export interface NavActiveContext { pathname: string; activeNav?: ActiveNav; } // 全局项按精确路径匹配;项目项按 activeNav 键匹配(或路径相等兜底)。 export function isNavItemActive(item: NavItem, ctx: NavActiveContext): boolean { if (item.key !== undefined) { return item.key === ctx.activeNav || ctx.pathname === item.href; } return ctx.pathname === item.href; }