Merge remote-tracking branch 'origin/main' into bundler-1/work

This commit is contained in:
Doxa Maps 2026-09-28 02:02:37 +00:00
commit e69e6c79f4
16 changed files with 1251 additions and 43 deletions

View file

@ -162,13 +162,18 @@ The only other thing here is `DX_PB_PORT`, which you set only if the log says po
## One more thing to set
The maps need a Mapbox token. It is the one credential you have to ask the team for:
Copy `.env.example` to `.env.local`. **Every line in it is optional** — empty means the default, which is
the all-local setup described above. You can start with none of them filled in.
The one you will probably want is a Mapbox token, which is the single credential you have to ask the team
for:
```
VITE_MAPBOX_TOKEN=<ask the team>
```
That, plus `DX_SYNC_REMOTE=origin` if you want it, is the whole of `.env.local`.
Leave it empty and the Mapbox-backed maps will not draw; you can also work with the keyless MapLibre
provider instead.
---

View file

@ -88,6 +88,13 @@ ok(M.parseQuery('?view=nonsense').view === 'board', 'an unknown view falls back
// the tree is the default panel: a bare URL names no panel, only the map is written, both are read back
ok(M.parseQuery('').panel === '' && M.parseQuery('?panel=map').panel === 'map' && M.parseQuery('?panel=tree').panel === 'tree'
&& M.buildQuery({ panel: 'map' }) === 'panel=map' && M.buildQuery({ panel: 'tree' }) === '', 'the tree is the default panel; ?panel=map restores the map')
// an OLD map link (map state, no ?panel=) still opens the map; a tree link with map state says panel=tree
ok(['?group=abai-sungai', '?groups=abai-sungai,hadrami-arabs', '?country=IND', '?province=IN-BR', '?view=province'].every((s) => M.parseQuery(s).panel === 'map')
&& M.parseQuery('?access=none&band=yes').panel === '' && M.parseQuery('?codes=abf').panel === '' && M.parseQuery('?country=IND&panel=tree').panel === 'tree',
'old links with map state and no panel open the map; a bare or map-free link opens the tree')
const treeBack = M.buildQuery({ panel: 'tree', keep: '?country=IND', filters: false })
ok(M.parseQuery('?' + treeBack).panel === 'tree' && M.parseQuery('?' + M.buildQuery({ panel: 'tree', group: 'abai-sungai' })).panel === 'tree',
'a tree view with map state round-trips to the tree')
// embed-contract parity with Gospel Resources: ?groups= (peoples by slug) and ?codes= / ?code= (languages)
const pg = M.parseQuery('?groups=abai-sungai,hadrami-arabs&codes=abf,ayh')
ok(pg.groups.length === 2 && Array.isArray(pg.filters.language) && pg.filters.language.join('|') === 'abf|ayh', 'parses ?groups= (slugs) and ?codes= (languages), comma-separated as Gospel Resources writes them')

View file

@ -88,6 +88,7 @@ try {
const shut = await render('en', {})
ok(!/data-tree-legend|class="gim-legend"/.test(shut.html) && /class="gtr-min"/.test(shut.html) && /class="gim-legend-min"/.test(shut.html),
'en: with no knob, the tree and map legends start collapsed')
ok(/data-tree-credit[^>]*>[\s\S]*?Glottolog[\s\S]*?CC BY 4\.0/.test(shut.html), 'en: the Glottolog CC BY credit is on screen with the legend collapsed')
ok(en.errors.length === 0, `en: every panel rendered with no error (${en.errors.length}${en.errors[0] ? ': ' + en.errors[0].slice(0, 160) : ''})`)
for (const [name, re] of [['GiFacetRail', /data-facet="access"/], ['GiGrid', /data-grid-total/], ['GiCharts', /data-region="charts"/],
['GiTree', /data-tree-legend[\s\S]*data-tree-poster/], ['GiResources', /data-resources-total/], ['GiMap', /data-region="map"/]]) ok(re.test(en.html), `en: ${name} rendered its region`)

View file

@ -328,6 +328,10 @@ useShadowStyles(
'.gtr-row .gtr-dot{margin-top:3px}' +
'.gtr-levels{margin:6px 0 2px;font-size:11.5px}' +
'.gtr-note{font-size:10.5px;color:#6d766e;margin-top:4px}' +
// ALWAYS ON, like a map's attribution control: the CC BY credit never hides behind the collapsed legend
'.gtr-credit{position:absolute;z-index:3;inset-block-end:0;inset-inline-end:0;padding:2px 6px;font:11px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;' +
'background:rgba(255,255,255,.82);color:#3a4048;border-start-start-radius:3px}' +
'.gtr-credit a{color:#2a5d8f;text-decoration:none}' +
'.gtr-poster{display:flex;align-items:center;gap:6px;margin-top:6px;padding-top:6px;border-top:1px solid #e6e8e3;font-weight:600}' +
'.gtr-poster select,.gtr-poster button{font:inherit;font-weight:400;font-size:12px;border:1px solid #d1d4cd;background:#fff;color:#3b463d;border-radius:6px;min-height:26px;padding:2px 8px;cursor:pointer}' +
'.gtr-poster button:focus-visible,.gtr-poster select:focus-visible{outline:2px solid #92b195;outline-offset:1px}' +
@ -385,5 +389,7 @@ useShadowStyles(
<div class="gtr-note">{{ T('gospelInternet.tree.sources', 'Tree: Glottolog (CC BY 4.0). Resources: eBible, Faith Comes By Hearing, Scripture Earth, PNG.Bible, JESUS Film, Global Recordings and others, keyed by ISO 639-3.') }} {{ citation }}</div>
</div>
<button v-else type="button" class="gtr-min" @click="legendOpen = true">{{ T('gospelInternet.legend.show', 'Legend') }}</button>
<!-- names and a licence, the same in every language (like the map's own attribution): not catalogue text -->
<div class="gtr-credit" data-tree-credit><a href="https://glottolog.org" target="_blank" rel="noopener noreferrer">Glottolog</a> · <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">CC BY 4.0</a></div>
</div>
</template>

View file

@ -86,6 +86,12 @@ export const VIEWS = ['board', 'zero', 'province', 'platforms']
* The published names are kept so links already shared keep restoring. */
export const SHARE_FACETS = Object.fromEntries(Object.entries(URL_KEYS))
// MAP STATE: what a link written while the map was the default carries and only the map shows (an open or
// selected people group's pin, a country or province, the province view). Such a link with no ?panel= opens
// the MAP, so old map links keep working; a bare link opens the tree (dx-shepherd ENDTOKEN-GOINT-ATTR).
const MAP_STATE = ['group', 'groups', 'country', 'province']
export const hasMapState = (q) => MAP_STATE.some((k) => q.get(k)) || q.get('view') === 'province'
/** Parse `?view=&access=…&group=` into { view, filters, group }. Unknown views fall back to board. */
export function parseQuery(search) {
const q = new URLSearchParams(search || '')
@ -105,7 +111,7 @@ export function parseQuery(search) {
if (codes.length && !filters.language) filters.language = codes.length === 1 ? codes[0] : codes
const groups = list('groups').filter((s) => /^[a-z0-9-]+$/.test(s))
// '' = not in the URL: the board opens on its default (the language tree, the Driver 2026-09-28)
const panel = ['tree', 'map'].includes(q.get('panel')) ? q.get('panel') : ''
const panel = ['tree', 'map'].includes(q.get('panel')) ? q.get('panel') : hasMapState(q) ? 'map' : ''
const tree = /^[a-z0-9-]{3,40}$/.test(q.get('tree') || '') ? q.get('tree') : ''
// A tree node narrows by LANGUAGE (its ISO codes); the URL carries the node, not the code list.
if (tree) delete filters.language
@ -119,7 +125,6 @@ export function buildQuery({ view, filterSet, group, keep, panel, tree, filters
// delete nor write them, so the two writers never fight over one address bar
for (const name of [...(filters ? Object.keys(URL_KEYS) : []), 'view', 'group', 'panel', 'tree']) q.delete(name)
if (view && view !== 'board') q.set('view', view)
if (panel === 'map') q.set('panel', 'map') // the tree is the default, so only the map is written
if (tree) q.set('tree', tree)
if (filters) for (const [name, key] of Object.entries(URL_KEYS)) {
if (tree && key === 'language') continue
@ -128,6 +133,8 @@ export function buildQuery({ view, filterSet, group, keep, panel, tree, filters
q.set(name, Array.isArray(v) ? v.join('|') : String(v))
}
if (group) q.set('group', group)
// the tree is the default, so it is written only where map state would otherwise open the map
if (panel === 'map' || (panel === 'tree' && hasMapState(q))) q.set('panel', panel)
return q.toString()
}

File diff suppressed because it is too large Load diff

View file

@ -604,7 +604,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb",
"integrity": "sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn",
"elements": [
"gospel-internet-board"
],
@ -623,7 +623,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -604,7 +604,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb",
"integrity": "sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn",
"elements": [
"gospel-internet-board"
],
@ -623,7 +623,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-y/ti76gIc9CO7C0Q74z8teaxCKsB5xbb5sk+aDVBgZLecERoSaFhc+brdokaOTIb\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-qdNXX4aWhgMvTym3O4xUzPisrdct3miCaawTtbiRDztj/iJyMddyGv7yA7mPtAzn\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{