master @ 54 LINES
[ HISTORY ] [ UP ]
┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ // Vertical timeline: date column, spine junction, entry title, wrapped │
│ // description beside the rail. Returns rows so the component can link │
│ // titles. │
│ import { G, checkGrid, len, pad, wrapHard } from "../../lib/ascii"; │
│ │
│ export interface TimelineEntry { │
│ date: string; │
│ title: string; │
│ desc?: string; │
│ href?: string; │
│ } │
│ │
│ export interface TimelineRow { │
│ prefix: string; │
│ text: string; │
│ href?: string; │
│ /** Description / spacer rows render in the soft ink. */ │
│ soft?: boolean; │
│ } │
│ │
│ export interface Props { │
│ entries: TimelineEntry[]; │
│ } │
│ │
│ export function layoutTimeline(entries: TimelineEntry[], width: number): TimelineR │
│ ow[] { │
│ if (!entries.length) return []; │
│ │
│ const dateW = Math.max(...entries.map((e) => len(e.date))); │
│ const textW = width - dateW - 5; │
│ const rail = `${" ".repeat(dateW + 2)}${G.V} `; │
│ const out: TimelineRow[] = []; │
│ │
│ entries.forEach((e, i) => { │
│ // ┬ opens the spine, ┼ continues it; a lone entry needs no spine. │
│ const j = entries.length === 1 ? G.H : i === 0 ? G.TJ : G.X; │
│ const title = len(e.title) > textW ? [...e.title].slice(0, textW - 3).join("") │
│ + "..." : e.title; │
│ const prefix = `${pad(e.date, dateW)} ${G.H}${j}${G.H} `; │
│ checkGrid(prefix + title, width, "timeline entry"); │
│ out.push({ prefix, text: title, href: e.href }); │
│ │
│ if (e.desc) { │
│ for (const ln of wrapHard(e.desc, textW).split("\n")) { │
│ checkGrid(rail + ln, width, "timeline desc"); │
│ out.push({ prefix: rail, text: ln, soft: true }); │
│ } │
│ } │
│ if (i < entries.length - 1) { │
│ out.push({ prefix: `${" ".repeat(dateW + 2)}${G.V}`, text: "", soft: true }) │
│ ; │
│ } │
│ }); │
│ │
│ return out; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ // Vertical timeline: date column, spine jun │
│ ction, entry title, wrapped │
│ // description beside the rail. Returns rows │
│ so the component can link │
│ // titles. │
│ import { G, checkGrid, len, pad, wrapHard } │
│ from "../../lib/ascii"; │
│ │
│ export interface TimelineEntry { │
│ date: string; │
│ title: string; │
│ desc?: string; │
│ href?: string; │
│ } │
│ │
│ export interface TimelineRow { │
│ prefix: string; │
│ text: string; │
│ href?: string; │
│ /** Description / spacer rows render in th │
│ e soft ink. */ │
│ soft?: boolean; │
│ } │
│ │
│ export interface Props { │
│ entries: TimelineEntry[]; │
│ } │
│ │
│ export function layoutTimeline(entries: Time │
│ lineEntry[], width: number): TimelineRow[] { │
│ if (!entries.length) return []; │
│ │
│ const dateW = Math.max(...entries.map((e) │
│ => len(e.date))); │
│ const textW = width - dateW - 5; │
│ const rail = `${" ".repeat(dateW + 2)}${G. │
│ V} `; │
│ const out: TimelineRow[] = []; │
│ │
│ entries.forEach((e, i) => { │
│ // ┬ opens the spine, ┼ continues it; a │
│ lone entry needs no spine. │
│ const j = entries.length === 1 ? G.H : i │
│ === 0 ? G.TJ : G.X; │
│ const title = len(e.title) > textW ? [.. │
│ .e.title].slice(0, textW - 3).join("") + ".. │
│ ." : e.title; │
│ const prefix = `${pad(e.date, dateW)} ${ │
│ G.H}${j}${G.H} `; │
│ checkGrid(prefix + title, width, "timeli │
│ ne entry"); │
│ out.push({ prefix, text: title, href: e. │
│ href }); │
│ │
│ if (e.desc) { │
│ for (const ln of wrapHard(e.desc, text │
│ W).split("\n")) { │
│ checkGrid(rail + ln, width, "timelin │
│ e desc"); │
│ out.push({ prefix: rail, text: ln, s │
│ oft: true }); │
│ } │
│ } │
│ if (i < entries.length - 1) { │
│ out.push({ prefix: `${" ".repeat(dateW │
│ + 2)}${G.V}`, text: "", soft: true }); │
│ } │
│ }); │
│ │
│ return out; │
│ } │
└──────────────────────────────────────────────┘
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET