Merge origin/development into studio/deep-research
This commit is contained in:
commit
cff06fb8e6
10 changed files with 537 additions and 72 deletions
280
internal/check-pane-config-reforward.mjs
Normal file
280
internal/check-pane-config-reforward.mjs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-pane-config-reforward — a composite's panes keep following the outer config for the whole
|
||||
* session, and a locale change never rewrites a filter into a display name.
|
||||
*
|
||||
* ★ THE BUG THIS GUARDS, measured by rega on development 92ca8b6e6 across FOUR composites
|
||||
* (regions-mfe, upg-100-mfe, country-posters-mfe, research-map-plus-grid). The outer element's
|
||||
* profile-config reached locale 'ar' by +1.5 s — attribute AND property, same node — while every
|
||||
* NESTED host carried NO locale key at boot and still none at +15 s. Text per composite at +15 s:
|
||||
* the chrome switched (3 Arabic / 0 Latin) and the panes did not (0/205, 0/190, 0/618, 0/43).
|
||||
* The cause is one line per composite: the pane's profile-config is written ONCE, in onMounted, so
|
||||
* the pane holds a JSON STRING and every later change stops at the composite. The leaf's own chain
|
||||
* is fully reactive and was never at fault.
|
||||
*
|
||||
* ★ IT RUNS THE SHIPPED HELPER WITH REAL VUE. usePaneConfig is imported, not re-typed, and driven
|
||||
* by real refs through a real watchEffect — the dependency-tracking hole this gate's case 3 covers
|
||||
* (a plain array leaves a LATER entry's getter untracked) is invisible to any test that fakes
|
||||
* reactivity, and it was found by studio only because their test used real Vue.
|
||||
*
|
||||
* ★ AND IT ASSERTS THE DOM, NOT THE RETURN VALUE. Every case reads the attribute back off the
|
||||
* element, because "the helper decided to write" and "the pane was told" are different claims and
|
||||
* only the second one is the fix.
|
||||
*
|
||||
* Exit 0 pass · 1 fail.
|
||||
*/
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { usePaneConfig } from '../library/composables/usePaneConfig.js'
|
||||
|
||||
let bad = 0
|
||||
const ok = (cond, what) => { if (!cond) bad++; console.log(` ${cond ? 'ok ' : 'FAIL'} ${what}`) }
|
||||
|
||||
/**
|
||||
* The smallest element that can hold attributes and answer contains(). Not a DOM shim in general —
|
||||
* only the surface the helper actually touches, so a change in what it touches breaks loudly here
|
||||
* rather than passing against a stub that pretends to be a browser.
|
||||
*/
|
||||
function makeEl(name = 'pane') {
|
||||
const attrs = new Map()
|
||||
return {
|
||||
name,
|
||||
isConnected: true,
|
||||
writes: 0,
|
||||
getAttribute: (k) => (attrs.has(k) ? attrs.get(k) : null),
|
||||
setAttribute(k, v) { attrs.set(k, String(v)); this.writes++ },
|
||||
}
|
||||
}
|
||||
const makeHost = (children) => ({ contains: (el) => children.includes(el) })
|
||||
|
||||
// ★ NULL-SAFE DELIBERATELY. When the re-forward is broken the attribute is absent, and a gate that
|
||||
// CRASHES on that reports "something went wrong somewhere" instead of naming the pane that stopped
|
||||
// following. Verified against the plain-array mutation: it turns a TypeError into a named FAIL.
|
||||
const cfgOf = (el) => {
|
||||
const raw = el.getAttribute('profile-config')
|
||||
if (!raw) return {}
|
||||
try { return JSON.parse(raw) } catch { return {} }
|
||||
}
|
||||
|
||||
// ── 1. THE MEASURED DEFECT. A locale change after mount must reach the pane.
|
||||
{
|
||||
const outer = ref({ locale: 'en', apiBaseUrl: '/api' })
|
||||
const el = makeEl()
|
||||
const host = makeHost([el])
|
||||
const paneConfig = usePaneConfig(() => host)
|
||||
// Mount-time write, exactly as the composite does it before registering.
|
||||
el.setAttribute('profile-config', JSON.stringify({ ...outer.value, profile: 'regions-list' }))
|
||||
paneConfig.register(el, () => ({ ...outer.value, profile: 'regions-list' }))
|
||||
await nextTick()
|
||||
const atMount = el.writes
|
||||
ok(cfgOf(el).locale === 'en', 'at mount the pane has the outer locale')
|
||||
|
||||
outer.value = { locale: 'ar', apiBaseUrl: '/api' }
|
||||
await nextTick()
|
||||
ok(cfgOf(el).locale === 'ar', 'after a live locale switch the pane has been told (this is the bug: it used to stay en)')
|
||||
ok(el.writes === atMount + 1, `exactly one re-write, not a loop (${el.writes - atMount})`)
|
||||
ok(cfgOf(el).profile === 'regions-list', "the pane's own keys survive the re-forward")
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 1b. NO LOCALE KEY AT BOOT IS THE REAL BEFORE-STATE, not a stale one. rega measured the nested
|
||||
// hosts carrying no locale key at all, so the first re-forward is absent -> 'ar'.
|
||||
{
|
||||
const outer = ref({ apiBaseUrl: '/api' })
|
||||
const el = makeEl()
|
||||
const paneConfig = usePaneConfig(() => makeHost([el]))
|
||||
el.setAttribute('profile-config', JSON.stringify({ ...outer.value }))
|
||||
paneConfig.register(el, () => ({ ...outer.value }))
|
||||
await nextTick()
|
||||
ok(cfgOf(el).locale === undefined, 'the measured boot state: no locale key on the nested host')
|
||||
outer.value = { apiBaseUrl: '/api', locale: 'ar' }
|
||||
await nextTick()
|
||||
ok(cfgOf(el).locale === 'ar', 'an ABSENT locale key is still a change, so the write fires')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 2. NO WRITE WHEN NOTHING CHANGED. Setting profile-config re-parses it in the child and can
|
||||
// remount what it drives, so an unconditional write on every reactive tick is a rerender loop.
|
||||
{
|
||||
const outer = ref({ locale: 'en' })
|
||||
const unrelated = ref(0)
|
||||
const el = makeEl()
|
||||
const paneConfig = usePaneConfig(() => makeHost([el]))
|
||||
el.setAttribute('profile-config', JSON.stringify(outer.value))
|
||||
paneConfig.register(el, () => ({ ...outer.value, tick: unrelated.value && 0 }))
|
||||
await nextTick()
|
||||
const before = el.writes
|
||||
unrelated.value = 7 // reactive churn that does not change the config
|
||||
await nextTick()
|
||||
ok(el.writes === before, `an unchanged config writes nothing (${el.writes - before} writes)`)
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 3. EVERY PANE FOLLOWS, NOT ONLY THE FIRST. ★ THE REGRESSION STUDIO FOUND. With a plain array
|
||||
// the watchEffect tracks only the getters reachable on its FIRST run, so pane 2's source is never a
|
||||
// dependency: changing it produced ZERO writes. That is this very bug, reappearing inside its own
|
||||
// fix, for any adopter whose panes read different sources.
|
||||
// It is also plugin-studio's load-bearing property, recorded at plugin-studio.vue:897: "EVERY
|
||||
// mounted pane, not only the one on screen: a kept-alive pane showed the old language when the
|
||||
// reader came back to it."
|
||||
{
|
||||
const a = ref('en')
|
||||
const b = ref('en')
|
||||
const elA = makeEl('a')
|
||||
const elB = makeEl('b')
|
||||
const paneConfig = usePaneConfig(() => makeHost([elA, elB]))
|
||||
paneConfig.register(elA, () => ({ locale: a.value }))
|
||||
paneConfig.register(elB, () => ({ locale: b.value }))
|
||||
await nextTick()
|
||||
b.value = 'ar' // ONLY the second pane's source
|
||||
await nextTick()
|
||||
ok(cfgOf(elB).locale === 'ar', 'the SECOND registered pane follows its own source (a plain array left this untracked)')
|
||||
ok(cfgOf(elA).locale === 'en', 'and the first pane is not disturbed by it')
|
||||
a.value = 'ro'
|
||||
await nextTick()
|
||||
ok(cfgOf(elA).locale === 'ro', 'the first pane still follows after the second has fired')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 4. A DETACHED PANE IS DROPPED, not written to. A pane column rebuild replaces the elements.
|
||||
{
|
||||
const outer = ref({ locale: 'en' })
|
||||
const kept = makeEl('kept')
|
||||
const gone = makeEl('gone')
|
||||
let children = [kept, gone]
|
||||
const paneConfig = usePaneConfig(() => makeHost(children))
|
||||
paneConfig.register(kept, () => ({ ...outer.value }))
|
||||
paneConfig.register(gone, () => ({ ...outer.value }))
|
||||
await nextTick()
|
||||
ok(paneConfig.count() === 2, 'both panes registered')
|
||||
children = [kept] // the column was rebuilt; `gone` is no longer under it
|
||||
gone.isConnected = false
|
||||
const goneWrites = gone.writes
|
||||
outer.value = { locale: 'ar' }
|
||||
await nextTick()
|
||||
ok(gone.writes === goneWrites, 'a detached pane is not written to')
|
||||
ok(paneConfig.count() === 1, `and it is PRUNED rather than re-tested forever (count ${paneConfig.count()})`)
|
||||
ok(cfgOf(kept).locale === 'ar', 'the surviving pane still follows')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 5. ONE PANE'S THROWING GETTER DOES NOT TAKE THE OTHERS DOWN. Inside a watchEffect an exception
|
||||
// stops every LATER entry in the same pass — which would silently freeze panes that are fine.
|
||||
{
|
||||
const outer = ref({ locale: 'en' })
|
||||
const boom = makeEl('boom')
|
||||
const fine = makeEl('fine')
|
||||
const paneConfig = usePaneConfig(() => makeHost([boom, fine]))
|
||||
const realError = console.error
|
||||
let errors = 0
|
||||
console.error = () => { errors++ }
|
||||
paneConfig.register(boom, () => { throw new Error('pane getter exploded') })
|
||||
paneConfig.register(fine, () => ({ ...outer.value }))
|
||||
await nextTick()
|
||||
outer.value = { locale: 'ar' }
|
||||
await nextTick()
|
||||
console.error = realError
|
||||
ok(cfgOf(fine).locale === 'ar', 'a pane registered AFTER a throwing one still follows the outer config')
|
||||
ok(errors >= 1, `and the throw is reported rather than swallowed (${errors})`)
|
||||
ok(errors <= 2, `but not once per reactive tick forever (${errors})`)
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 6. registerAttrs: THE SECOND PANE SHAPE. people-groups-grid takes three separate attributes
|
||||
// rather than one JSON blob (research-map-plus-grid.vue:430-435). Same list, same effect, same
|
||||
// write-on-change — the point of generalising rather than letting the second adopter fork.
|
||||
{
|
||||
const linked = reactive({ region: '', religion: '', language: 'arb' })
|
||||
const grid = makeEl('grid')
|
||||
const paneConfig = usePaneConfig(() => makeHost([grid]))
|
||||
paneConfig.registerAttrs(grid, () => ({
|
||||
'wagf-region': linked.region,
|
||||
'religion-family': linked.religion,
|
||||
'primary-language': linked.language,
|
||||
}))
|
||||
await nextTick()
|
||||
ok(grid.getAttribute('primary-language') === 'arb', 'an attribute-configured pane is written at registration')
|
||||
const before = grid.writes
|
||||
linked.region = 'africa'
|
||||
await nextTick()
|
||||
ok(grid.getAttribute('wagf-region') === 'africa', 'a changed attribute is written')
|
||||
ok(grid.writes === before + 1, `ONLY the changed attribute is written, not all three (${grid.writes - before})`)
|
||||
ok(grid.getAttribute('religion-family') === '', 'an empty value is written as empty, never the string "undefined"')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 6b. A KEY THE GETTER LATER OMITS IS CLEARED, not left applied. A stale filter silently
|
||||
// narrowing the grid is the same class of lie as the frozen config this helper exists to fix.
|
||||
{
|
||||
const linked = reactive({ region: 'africa', present: true })
|
||||
const grid = makeEl('grid')
|
||||
const paneConfig = usePaneConfig(() => makeHost([grid]))
|
||||
paneConfig.registerAttrs(grid, () => (linked.present
|
||||
? { 'wagf-region': linked.region, 'religion-family': 'islam' }
|
||||
: { 'religion-family': 'islam' }))
|
||||
await nextTick()
|
||||
ok(grid.getAttribute('wagf-region') === 'africa', 'the filter is applied')
|
||||
linked.present = false
|
||||
await nextTick()
|
||||
ok(grid.getAttribute('wagf-region') === '', 'an omitted key CLEARS its attribute rather than leaving a stale filter')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 7. ★ LOCALE-INVARIANCE OF A FILTER VALUE (sr's property, SR-ADOPT-ANSWERS). A re-forward
|
||||
// triggered by a LOCALE change must produce the SAME primary-language string it produced before.
|
||||
// The hazard is real and general even though it does not bite today: research-map-plus-grid's
|
||||
// gridLanguage() falls back to `linked.language.value || linked.language.label`, and `label` is a
|
||||
// DISPLAY NAME. The grid's valueMatches() compares whole normalised values, so a name fetched in
|
||||
// one language matches no row in another — the failure that took the grid 2,107 -> 0 and read as
|
||||
// "no results" (rega, 2026-09-26). This asserts the CLASS, not today's instance.
|
||||
{
|
||||
const locale = ref('en')
|
||||
// A language whose display label follows the locale, as the API returns it, but whose code does not.
|
||||
const NAMES = { en: 'Arabic, Sudanese', ar: 'العربية السودانية' }
|
||||
const linkedLanguage = () => ({ codes: ['apd'], value: 'apd', label: NAMES[locale.value] })
|
||||
const gridLanguage = () => {
|
||||
const l = linkedLanguage()
|
||||
const codes = Array.isArray(l.codes) ? l.codes : []
|
||||
if (codes.length > 1) return codes.join('|')
|
||||
return l.value || l.label
|
||||
}
|
||||
const grid = makeEl('grid')
|
||||
const paneConfig = usePaneConfig(() => makeHost([grid]))
|
||||
paneConfig.registerAttrs(grid, () => ({ 'primary-language': gridLanguage(), 'wagf-region': `r-${locale.value === 'x' ? 1 : 0}` }))
|
||||
await nextTick()
|
||||
const beforeSwitch = grid.getAttribute('primary-language')
|
||||
locale.value = 'ar'
|
||||
await nextTick()
|
||||
ok(grid.getAttribute('primary-language') === beforeSwitch,
|
||||
`a locale change leaves primary-language unchanged ('${beforeSwitch}' -> '${grid.getAttribute('primary-language')}')`)
|
||||
ok(!/[-ۿ]/.test(grid.getAttribute('primary-language')),
|
||||
'the filter is a CODE, never a localised display name')
|
||||
paneConfig.dispose()
|
||||
}
|
||||
|
||||
// ── 8. DISPOSE STOPS THE EFFECT. A composite unmounted and left watching is a leak that keeps
|
||||
// writing to detached elements.
|
||||
{
|
||||
const outer = ref({ locale: 'en' })
|
||||
const el = makeEl()
|
||||
const paneConfig = usePaneConfig(() => makeHost([el]))
|
||||
paneConfig.register(el, () => ({ ...outer.value }))
|
||||
await nextTick()
|
||||
paneConfig.dispose()
|
||||
const after = el.writes
|
||||
outer.value = { locale: 'ar' }
|
||||
await nextTick()
|
||||
ok(el.writes === after, 'after dispose() the outer config is no longer followed')
|
||||
ok(paneConfig.count() === 0, 'and the entry list is emptied')
|
||||
}
|
||||
|
||||
// ── 9. IT DOES NOT THROW OUTSIDE setup(). Every case above runs outside a component instance, which
|
||||
// is the un-guarded onBeforeUnmount hazard studio raised — if that guard regressed, this file would
|
||||
// have failed at the first register() rather than here. Stated so the reason is on the record.
|
||||
ok(true, 'the whole file ran outside setup(): the getCurrentInstance() guard holds')
|
||||
|
||||
if (bad) {
|
||||
console.error(`\nPANE-CONFIG-REFORWARD: FAIL — ${bad} case(s). A composite's panes no longer follow the`)
|
||||
console.error('outer profile-config, so a live locale switch leaves every nested pane in the old language.')
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('\nPANE-CONFIG-REFORWARD: PASS — panes follow the outer config for the session, and a locale change never rewrites a filter value.')
|
||||
169
library/composables/usePaneConfig.js
Normal file
169
library/composables/usePaneConfig.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* usePaneConfig — keep a composite's panes' profile-config in step with the outer element's.
|
||||
*
|
||||
* ★ THE BUG THIS EXISTS FOR. A composite builds each pane by creating the child custom element and
|
||||
* writing its profile-config ONCE, inside onMounted:
|
||||
*
|
||||
* el.setAttribute('profile-config', JSON.stringify(pcfg)) // and never again
|
||||
*
|
||||
* That is correct at mount and wrong for the rest of the session. The pane is handed a JSON STRING,
|
||||
* not a reactive object, so a later change to the OUTER element's profile-config updates the
|
||||
* composite's own `cfg` and reaches nothing below it. MEASURED on Regions: a live locale switch
|
||||
* en -> ar left the panes in English for the whole session, because regions-dashboard is kept alive
|
||||
* by v-show rather than remounted — the one code path that would have rewritten the attribute never
|
||||
* runs again. The leaf's own chain is fully reactive and was never at fault: it is reactive to its
|
||||
* own prop, and its prop is frozen.
|
||||
*
|
||||
* ★ WHY A HELPER RATHER THAN THREE FIXES. regions-mfe, upg-100-mfe and country-posters-mfe all build
|
||||
* their panes the same way — assemble a config object, stringify it, set the attribute once. They
|
||||
* differ only in WHICH keys they inherit. So the composite keeps its own INHERITED_PAGE_KEYS and its
|
||||
* own assembly, and hands the result here; this owns the part that was missing everywhere.
|
||||
*
|
||||
* ★ IT WRITES ONLY WHEN THE STRING CHANGES. Setting profile-config re-parses it in the child and can
|
||||
* remount what it drives, so an unconditional write on every reactive tick would be a rerender loop.
|
||||
* The previous value is remembered per element and compared before touching the DOM.
|
||||
*
|
||||
* const panes = usePaneConfig(() => stage.value, TAG)
|
||||
* panes.register(el, () => paneConfigFor(pane)) // at mount, after appendChild
|
||||
*
|
||||
* Re-forwarding happens inside a watchEffect, so any reactive source the getter reads — the outer
|
||||
* config, a computed spec, the pane's own config — triggers it.
|
||||
*
|
||||
* ★ NOT EVERY PANE IS CONFIGURED BY profile-config. people-groups-grid takes its filters as THREE
|
||||
* separate attributes (wagf-region, religion-family, primary-language) rather than one JSON blob, so
|
||||
* research-map-plus-grid has one pane of each kind. registerAttrs covers that second shape:
|
||||
*
|
||||
* panes.registerAttrs(grid, () => ({ 'wagf-region': region.value, 'religion-family': fam.value }))
|
||||
*
|
||||
* It shares this list, this watchEffect, this prune and this write-only-on-change — deliberately, so
|
||||
* the second adopter rides ONE mechanism instead of forking a second. A helper with a single caller
|
||||
* has not been shown to generalise at all; this is the shape that showed it.
|
||||
*/
|
||||
import { watchEffect, onBeforeUnmount, shallowReactive, getCurrentInstance } from 'vue'
|
||||
|
||||
/**
|
||||
* @param {() => (Element|null|undefined)} hostGetter the element the panes were appended under; used
|
||||
* only to drop registrations whose element has left the DOM (a pane column can be rebuilt).
|
||||
* ★ IT MUST BE IN THE SAME TREE AS THE PANES. Element.contains() does NOT cross a shadow
|
||||
* boundary, so passing the custom element while its panes live in its shadow root would make
|
||||
* every pane look detached and skip every write, silently.
|
||||
* @returns {{ register: (el: Element, configGetter: () => object) => void,
|
||||
* registerAttrs: (el: Element, attrsGetter: () => Record<string,unknown>) => void,
|
||||
* dispose: () => void, count: () => number }}
|
||||
*/
|
||||
export function usePaneConfig(hostGetter) {
|
||||
// ★ shallowReactive, NOT a plain array. The watchEffect below runs once, at the FIRST register();
|
||||
// with a plain array a LATER entry's getter is never tracked, so its pane stays frozen until
|
||||
// something the first entry reads happens to change. MEASURED with real Vue by studio: two panes
|
||||
// reading different refs, changing only pane 2's source produced ZERO writes. That is the very bug
|
||||
// this helper exists to fix, reappearing for any adopter whose panes read different sources.
|
||||
// Making the list reactive re-runs the effect on register() and re-tracks every getter.
|
||||
/**
|
||||
* One entry per registered pane. `attrs` is the ATTRIBUTE NAMES this entry owns: the single-key
|
||||
* ['profile-config'] for a JSON-configured pane, or the caller's list for an attribute-configured
|
||||
* one. `last` holds the previously written value PER ATTRIBUTE, so a getter that changes one of
|
||||
* three attributes writes one of three.
|
||||
* @type {Array<{el: Element, get: () => object, attrs: string[]|null, last: Record<string,string|null>, warned: boolean}>}
|
||||
*/
|
||||
const entries = shallowReactive([])
|
||||
let stop = null
|
||||
|
||||
const alive = (el) => {
|
||||
if (!el || !el.isConnected) return false
|
||||
const host = typeof hostGetter === 'function' ? hostGetter() : hostGetter
|
||||
// A pane column rebuild replaces the elements; an entry whose element is no longer under the
|
||||
// current host is stale and must not be written to.
|
||||
return !host || host.contains(el)
|
||||
}
|
||||
|
||||
/** Add an entry and make sure the one shared effect is running. */
|
||||
function add(entry) {
|
||||
entries.push(entry)
|
||||
if (stop) return
|
||||
stop = watchEffect(() => {
|
||||
// PRUNE rather than skip: an adopter that rebuilds its pane column would otherwise keep
|
||||
// detached elements in the list and re-run their getters on every pass, forever.
|
||||
for (let i = entries.length - 1; i >= 0; i--) if (!alive(entries[i].el)) entries.splice(i, 1)
|
||||
for (const e of entries) forward(e)
|
||||
})
|
||||
}
|
||||
|
||||
/** Re-evaluate one entry's getter and write only what changed. */
|
||||
function forward(entry) {
|
||||
let value
|
||||
// A getter that throws must not take the other panes down with it: this runs inside a
|
||||
// watchEffect, where an exception would stop every later entry in the same pass. It is
|
||||
// reported ONCE per entry -- silence here would recreate the symptom this helper fixes.
|
||||
try { value = entry.get() } catch (e) {
|
||||
if (!entry.warned) { entry.warned = true; console.error('[usePaneConfig] pane config getter threw; this pane will not follow the outer config:', e) }
|
||||
return
|
||||
}
|
||||
if (!entry.attrs) {
|
||||
// JSON pane: the whole config is one attribute.
|
||||
let next
|
||||
try { next = JSON.stringify(value) } catch (e) {
|
||||
if (!entry.warned) { entry.warned = true; console.error('[usePaneConfig] pane config did not stringify; this pane will not follow the outer config:', e) }
|
||||
return
|
||||
}
|
||||
if (next === entry.last['profile-config']) return
|
||||
entry.last['profile-config'] = next
|
||||
entry.el.setAttribute('profile-config', next)
|
||||
return
|
||||
}
|
||||
// Attribute pane: each named attribute is compared and written independently, so a getter whose
|
||||
// region changed does not rewrite (and so re-trigger) the two that did not.
|
||||
const bag = value && typeof value === 'object' ? value : {}
|
||||
for (const name of entry.attrs) {
|
||||
// An absent/null/undefined value is written as '' rather than the string "undefined" -- the
|
||||
// grid reads these as plain filter strings and "undefined" would be a filter that matches
|
||||
// nothing, which is the empty-grid failure this repo has already paid for once.
|
||||
const raw = bag[name]
|
||||
const next = raw === undefined || raw === null ? '' : String(raw)
|
||||
if (next === entry.last[name]) continue
|
||||
entry.last[name] = next
|
||||
entry.el.setAttribute(name, next)
|
||||
}
|
||||
}
|
||||
|
||||
function register(el, configGetter) {
|
||||
if (!el || typeof configGetter !== 'function') return
|
||||
// The attribute the caller already set at mount is the baseline: recording it here means the
|
||||
// first watchEffect pass does not rewrite an identical value.
|
||||
add({ el, get: configGetter, attrs: null, last: { 'profile-config': el.getAttribute('profile-config') }, warned: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* The attribute-configured sibling. `attrsGetter` returns an object keyed by ATTRIBUTE NAME
|
||||
* (kebab-case, as written in the DOM), e.g. { 'wagf-region': 'africa' }.
|
||||
* ★ THE NAME LIST IS FIXED AT REGISTRATION, from the getter's first result. A pane's attribute
|
||||
* surface is static -- it is the element's contract -- and pinning it means a later getter that
|
||||
* omits a key CLEARS that attribute rather than silently leaving a stale filter applied, which is
|
||||
* the same class of lie as the frozen config this helper exists to fix.
|
||||
*/
|
||||
function registerAttrs(el, attrsGetter) {
|
||||
if (!el || typeof attrsGetter !== 'function') return
|
||||
let first
|
||||
try { first = attrsGetter() } catch (e) {
|
||||
console.error('[usePaneConfig] attrs getter threw at registration; this pane is not registered:', e)
|
||||
return
|
||||
}
|
||||
const attrs = Object.keys(first && typeof first === 'object' ? first : {})
|
||||
if (!attrs.length) {
|
||||
console.error('[usePaneConfig] attrs getter returned no attribute names; this pane is not registered.')
|
||||
return
|
||||
}
|
||||
const last = {}
|
||||
for (const name of attrs) last[name] = el.getAttribute(name)
|
||||
add({ el, get: attrsGetter, attrs, last, warned: false })
|
||||
}
|
||||
|
||||
// ★ GUARDED, the house pattern (useMapData.js:144). onBeforeUnmount only binds inside setup(); a
|
||||
// caller outside one gets a Vue warning and no auto-cleanup. `dispose` is returned so such a
|
||||
// caller has something to call -- silently leaking a watchEffect would be the worse half.
|
||||
const dispose = () => { if (stop) { stop(); stop = null } entries.length = 0 }
|
||||
if (getCurrentInstance()) onBeforeUnmount(dispose)
|
||||
|
||||
return { register, registerAttrs, dispose, count: () => entries.length }
|
||||
}
|
||||
|
||||
export default usePaneConfig
|
||||
|
|
@ -237,6 +237,7 @@
|
|||
"check:poster-action-print": "node internal/check-poster-action-print.mjs",
|
||||
"check:popup-href-escaped": "node internal/check-popup-href-escaped.mjs",
|
||||
"check:popup-pan-controls": "node internal/check-popup-pan-clears-controls.mjs",
|
||||
"check:pane-config-reforward": "node internal/check-pane-config-reforward.mjs",
|
||||
"check:control-inset": "node internal/check-control-inset-measured.mjs",
|
||||
"check:viewport-meta": "node internal/check-viewport-meta.mjs",
|
||||
"check:viewport-meta:self-test": "VIEWPORT_META_SELFTEST=1 node internal/check-viewport-meta.mjs",
|
||||
|
|
@ -340,7 +341,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
* wall's 'show-on-map', and it keeps each pane under its own ProfileLoader/Pinia.
|
||||
*/
|
||||
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { usePaneConfig } from '@map/composables/usePaneConfig.js'
|
||||
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js'
|
||||
|
|
@ -113,16 +114,27 @@ let offScroll = null
|
|||
// below), and this was the one genuinely missing.
|
||||
const INHERITED_PAGE_KEYS = ['assetsBase', 'apiBaseUrl', 'prayBaseUrl', 'tokenUrl', 'dataSource', 'locale', 'lang', 'languageCode', 'isDark', 'qa', 'pinHint', 'urlSync', 'provider', 'list']
|
||||
|
||||
function paneElement(pane) {
|
||||
const el = document.createElement(TAG)
|
||||
// ★ SPLIT OUT SO IT CAN BE RE-EVALUATED. This reads cfg.value and spec.value, so calling it again
|
||||
// after the outer profile-config changes yields the NEW pane config. It used to be inline in
|
||||
// paneElement, which ran once per pane in onMounted and never again -- that is the whole defect:
|
||||
// the pane is handed a JSON STRING, so a later locale change reached this element and stopped.
|
||||
// stage is the element the panes are appended UNDER, in the same tree as them -- contains() does
|
||||
// not cross a shadow boundary, so the host must not be the custom element itself.
|
||||
const paneConfig = usePaneConfig(() => stage.value)
|
||||
|
||||
function paneConfigFor(pane) {
|
||||
const host = cfg.value || {}
|
||||
const inherited = {}
|
||||
for (const k of INHERITED_PAGE_KEYS) {
|
||||
if (host[k] !== undefined && host[k] !== null && host[k] !== '') inherited[k] = host[k]
|
||||
}
|
||||
if (inherited.locale === undefined && inherited.lang !== undefined) inherited.locale = inherited.lang
|
||||
const pcfg = { ...inherited, ...spec.value.config, ...pane.config }
|
||||
el.setAttribute('profile-config', JSON.stringify(pcfg))
|
||||
return { ...inherited, ...spec.value.config, ...pane.config }
|
||||
}
|
||||
|
||||
function paneElement(pane) {
|
||||
const el = document.createElement(TAG)
|
||||
el.setAttribute('profile-config', JSON.stringify(paneConfigFor(pane)))
|
||||
return el
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +172,10 @@ onMounted(() => {
|
|||
if (!column) return
|
||||
for (const pane of column.panes) {
|
||||
if (!pane.config) continue
|
||||
column.mountEl(pane.id)?.appendChild(paneElement(pane))
|
||||
const el = paneElement(pane)
|
||||
column.mountEl(pane.id)?.appendChild(el)
|
||||
// Keep this pane's profile-config in step with the outer element's for the rest of the session.
|
||||
paneConfig.register(el, () => paneConfigFor(pane))
|
||||
}
|
||||
// Tabs are a readout as well as a control — follow the user's scroll.
|
||||
offScroll = column.onScroll(() => {
|
||||
|
|
|
|||
|
|
@ -1126,7 +1126,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
|
||||
"integrity": "sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -1149,8 +1149,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1166,7 +1166,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1182,8 +1182,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1126,7 +1126,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
|
||||
"integrity": "sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -1149,8 +1149,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1166,7 +1166,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1182,8 +1182,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue