/** * peopleGroupList.js — the people-group LIST, a universal prop like engine, locale and token. * * Every map shows people groups, and until now always DOXA's (the live API's 2,107). The same maps * must work for any project's list: Project 7000 (unreached), 4700 (frontier), 700 (frontier of * 500,000 or more), 2000 (DOXA). A list is chosen the way the engine is, first answer wins: * 1. profile-config `list` — this embed, decided by whoever mounted the element * 2. ?list= in the page URL — any generated leaf can be switched from the URL * 3. window.MAP_LIST — a host global, for a host that sets it programmatically * 4. 'doxa' — the default: exactly today's behaviour, nothing fetched, nothing filtered * An id or an alias ('700', 'project-700') is accepted; anything unknown falls back to the default * and says so once in the console. * * WHAT A MAP DOES WITH IT (Tier A). Rows keep coming from the DOXA API, because that is where the * prayer fields live. applyPeopleGroupList() keeps the rows whose slug belongs to the list, from a * membership file the build writes (data/people-groups/lists/.json, served under the asset * pool). So under Project 700 a map shows the DOXA groups that are Project 700 groups. The list's * groups that DOXA does not carry are Tier B, per map (SPEC-PEOPLE-GROUP-LIST-PROP.md). * * FAILS CLOSED. If a non-default list's membership cannot be loaded, the map gets NO rows and the * console names the list and the URL. A map that silently showed all 2,107 while an embed asked for * Project 700 would be showing the wrong list with no sign of it. * * Pure except for the fetch; safe outside a Vue setup(). */ import registry from './lists.json' import { resolveAssetsBase } from '../utils/embedBase.js' export const LIST_DEFAULT = registry.default const byKey = new Map() for (const l of registry.lists) { byKey.set(l.id, l) for (const a of l.aliases || []) byKey.set(String(a).toLowerCase(), l) } /** Every list the registry defines: { id, label, definition?, source, filter }. */ export function peopleGroupLists() { return registry.lists } /** The registry entry for an id or alias, or null. */ export function listInfo(idOrAlias) { return byKey.get(String(idOrAlias ?? '').toLowerCase().trim()) || null } /** Unwrap a ref/computed to its config object; parse a JSON string; pass plain objects through. */ function unwrapConfig(profileConfig) { let cfg = profileConfig if (cfg && typeof cfg === 'object' && 'value' in cfg && !('list' in cfg) && !('profile' in cfg)) cfg = cfg.value if (typeof cfg === 'string') { try { cfg = JSON.parse(cfg) } catch { cfg = null } } return cfg && typeof cfg === 'object' ? cfg : null } const warned = new Set() /** The list this map should show, as a registry id. Always a known id. */ export function resolvePeopleGroupList(profileConfig) { const cfg = unwrapConfig(profileConfig) const fromConfig = cfg?.list const fromUrl = typeof location !== 'undefined' ? new URLSearchParams(location.search).get('list') : null const fromWindow = typeof window !== 'undefined' ? window.MAP_LIST : undefined const raw = fromConfig || fromUrl || fromWindow || LIST_DEFAULT const hit = listInfo(raw) if (hit) return hit.id if (!warned.has(raw)) { warned.add(raw) console.warn(`[list] unknown people-group list "${raw}", showing "${LIST_DEFAULT}". Known: ${registry.lists.map((l) => l.id).join(', ')}`) } return LIST_DEFAULT } const membership = new Map() /** The set of DOXA slugs in a list (fetched once per URL). Rejects if it cannot be loaded. */ export function loadListMembership(id, profileConfig) { const url = `${resolveAssetsBase(unwrapConfig(profileConfig))}/people-groups/lists/${id}.json` if (!membership.has(url)) { membership.set(url, fetch(url) .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json() }) .then((d) => new Set(Array.isArray(d?.slugs) ? d.slugs : [])) .catch((e) => { membership.delete(url); throw new Error(`list "${id}" membership ${url}: ${e.message}`) })) } return membership.get(url) } /** * Keep the rows that belong to the resolved list. The default list returns `rows` itself, untouched * and without a fetch. `key` reads a row's slug (default: row.slug). */ export async function applyPeopleGroupList(rows, profileConfig, { key = (r) => r?.slug } = {}) { const id = resolvePeopleGroupList(profileConfig) if (id === LIST_DEFAULT || !Array.isArray(rows)) return rows try { const slugs = await loadListMembership(id, profileConfig) return rows.filter((r) => slugs.has(key(r))) } catch (e) { console.error(`[list] ${e.message}. Showing no people groups rather than the wrong list.`) return [] } }