Files
writer-work-flow/apps/web/lib/outline/outline.ts

73 lines
2.7 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.

// 大纲编辑器纯逻辑按卷分组、伏笔窗口徽标文案、接近回收窗口提示UX §6.7)。
// OutlineChapterView/ForeshadowWindowView 已由 OpenAPI 强类型,无需手动收窄。
import type { ForeshadowWindowView, OutlineChapterView } from "@/lib/api/types";
export interface VolumeGroup {
volume: number;
chapters: OutlineChapterView[];
}
// 按 volume 分组(保持章号升序);卷号升序。
export function groupByVolume(
chapters: readonly OutlineChapterView[] | undefined,
): VolumeGroup[] {
const byVolume = new Map<number, OutlineChapterView[]>();
for (const ch of chapters ?? []) {
const list = byVolume.get(ch.volume) ?? [];
byVolume.set(ch.volume, [...list, ch]);
}
return [...byVolume.entries()]
.sort(([a], [b]) => a - b)
.map(([volume, list]) => ({
volume,
chapters: [...list].sort((a, b) => a.no - b.no),
}));
}
// 视图卷过滤值:"all" = 全部卷;具体数字 = 只看该卷(对齐后端 GET ?volume
export type ViewVolume = number | "all";
// 已存大纲里出现过的卷号(升序、去重)——驱动「全部 / 卷 N」选择器选项。
export function distinctVolumes(
chapters: readonly OutlineChapterView[] | undefined,
): number[] {
const seen = new Set<number>();
for (const ch of chapters ?? []) seen.add(ch.volume);
return [...seen].sort((a, b) => a - b);
}
// 按视图卷过滤:选「全部」返回全部,选具体卷只留该卷(客户端过滤,避免重复请求)。
export function filterByViewVolume(
chapters: readonly OutlineChapterView[] | undefined,
view: ViewVolume,
): OutlineChapterView[] {
const all = [...(chapters ?? [])];
if (view === "all") return all;
return all.filter((ch) => ch.volume === view);
}
// 伏笔窗口徽标文案(纯文本,旗标图标由渲染层的 lucide Flag 承载,不掺字形):
// `F-012 (40-60)` / `F-012 (≤60)` / `F-012 (≥40)` / `F-012`。
export function windowBadgeLabel(window: ForeshadowWindowView): string {
const from = window.expected_close_from;
const to = window.expected_close_to;
if (typeof from === "number" && typeof to === "number") {
return `${window.code} (${from}-${to})`;
}
if (typeof to === "number") return `${window.code} (≤${to})`;
if (typeof from === "number") return `${window.code} (≥${from})`;
return window.code;
}
// 接近回收:本章号落在窗口 [from,to] 内提示「本章可回收」UX §6.7 ⚑ 可回收)。
export function isCloseWindow(
chapter: OutlineChapterView,
window: ForeshadowWindowView,
): boolean {
const to = window.expected_close_to;
if (typeof to !== "number") return false;
const from = window.expected_close_from ?? to;
return chapter.no >= from && chapter.no <= to;
}