Some checks failed
Deploy production / deploy (push) Failing after 9s
Schema math (parts, chapters, sections, activity), seeded with all 100 chapters (Ch 0-94, appendix A-E), 647 sections, PDF pages, exercise counts and approximate prereqs. Triggers stamp started_at/completed_at and log status/exercise/note/section changes; math.apply_progress(jsonb) applies the JSON format bots send. Private by default via MATH_PRIVATE in #lib/math (owner-only prefix + lock in every tracker list). Overall + per-part progress, current chapter, prereq hints, quick status buttons, exercises stepper, notes, optional section ticks, activity log, dependency chart. Deploy preflight checks the math schema (4 tables, 100 chapters).
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
// Math tracker (/trackers/math): Evan Chen's "An Infinitely Large Napkin". Shared by the
|
||
// server (owner gate, loads) and the page.
|
||
|
||
/**
|
||
* The privacy switch. true: owner-only (404 for everyone else, lock icon in every tracker
|
||
* list). false: a public, read-only page for everyone else (notes stay owner-only, edits
|
||
* are always owner-only). Flip this one constant; owner.ts and projects.ts read it.
|
||
*/
|
||
export const MATH_PRIVATE = true;
|
||
|
||
export const MATH_STATUSES = ['not_started', 'reading', 'exercises', 'done', 'skipped'] as const;
|
||
export type MathStatus = (typeof MATH_STATUSES)[number];
|
||
|
||
export const STATUS_LABEL: Record<MathStatus, string> = {
|
||
not_started: 'not started',
|
||
reading: 'reading',
|
||
exercises: 'exercises',
|
||
done: 'done',
|
||
skipped: 'skipped'
|
||
};
|
||
|
||
/** The free PDF of the book; chapters link to it with #page=N (PDF page numbers). */
|
||
export const NAPKIN_PDF = 'https://venhance.github.io/napkin/Napkin.pdf';
|
||
export const pdfPage = (page: number) => `${NAPKIN_PDF}#page=${page}`;
|
||
|
||
/** "9,10,11,12,18" → "9–12, 18" (numeric keys only; letters are listed as-is). */
|
||
export function compactKeys(keys: readonly string[]): string {
|
||
const nums = keys.filter((k) => /^\d+$/.test(k)).map(Number);
|
||
const other = keys.filter((k) => !/^\d+$/.test(k));
|
||
nums.sort((a, b) => a - b);
|
||
const out: string[] = [];
|
||
for (let i = 0; i < nums.length;) {
|
||
let j = i;
|
||
while (j + 1 < nums.length && nums[j + 1] === nums[j] + 1) j++;
|
||
out.push(j - i >= 2 ? `${nums[i]}–${nums[j]}` : nums.slice(i, j + 1).join(', '));
|
||
i = j + 1;
|
||
}
|
||
return [...out, ...other].join(', ');
|
||
}
|