jevstrudel.git / website / src / jev / pitches.mjs
1// Pitches (song ideas for Jev songs, worker/src/content.ts) and the songs
2// that answer them. The maker of a song says which pitch it answers: a
3// site song in its SPEC.md (`pitch: <id>`, read at build time by
4// songs.mjs), a listener's song by its author (`setSongPitch`, in
5// listeners.mjs). A pitch's author cannot mark it answered: only the song's
6// maker can vouch for that.
7
8const PITCH_ID = /^[A-Za-z0-9_-]{22}$/;
9
10// A spec's `pitch:`, a pitch's id, or null; a malformed one fails the build
11// (songs.mjs) rather than silently never matching.
12export function pitchOf(value, where = 'a song') {
13  if (value === undefined || value === null) return null;
14  if (typeof value === 'string' && PITCH_ID.test(value)) return value;
15  throw new Error(`${where}: pitch must be a pitch's id (22 characters), not ${JSON.stringify(value)}`);
16}
17
18// Every song answering `pitch`: the site's (from this build) and the
19// listeners' the Worker listed with it, site songs first.
20export function answersOf(pitch, siteSongs) {
21  return [
22    ...siteSongs.filter((s) => s.pitch === pitch.id).map((s) => ({ site: s.id, title: s.title })),
23    ...(pitch.answers ?? []).map((a) => ({ listener: a.id, title: a.title })),
24  ];
25}
26
27// How the pitches list may be sorted, and what each is called.
28export const PITCH_SORTS = { votes: 'most voted', new: 'newest' };