Regions: their search, and three places I was not a clone
Port dx-life's fuzzy search into UupgsList onto the repo's existing
@map/components/search/fuzzy.js shim: their 7 SEARCH_KEYS, threshold 0
when exact else 0.4, minMatchCharLength 3, diacritic stripping, their
highlight() construction and match routing, and .search-highlight in
#92b195 from their blocks/_cards.scss:258. A typo now finds the row and
the matched run is wrapped, neither of which the old substring filter
did. _probe-search.mjs: 6/6.
Then, reading the screenshot taken to prove that search worked, three
clone-fidelity defects that every numeric check had certified green:
- The count line above the list read "N People Groups · Cameroon". It
was invented; it is not on their page. Theirs
(UupgsList.vue:577-583) is "Total: N" in .font-size-sm inside a
.repel, with the see-all link suppressed by hide-see-all-link.
_probe-total.mjs: 7/7.
- formatNumber was hardcoded en-US. Theirs (RegionDetail.vue:16) is
n.toLocaleString(`${locale}-u-nu-latn`) — locale separators, Latin
digits. We were wrong in de, fr and es.
- The country strapline formatted its count. Theirs
(CountryDetail.vue:40) is the raw {{ country.count }} — that
component never defines formatNumber. Their asymmetry with
RegionDetail; a clone matches it instead of improving on it.
Why nothing caught the invented line: every check compares properties
of elements present on BOTH sides, so an element present only on ours
is invisible to all of them. _probe-total.mjs now carries a surplus
check — a phrase assertion scoped to .ul-root, since the first draft
swept the whole element and failed on their own legitimate "People
Groups" in the stat cards and the country table header.
Regression: region-vs-live 73/73, country-vs-live 28/28, mobile 44/44
and 26/26, i18n, chooser, dashboard, standalone-app, search, switcher,
goalcards, h1-highlight, total — all green.
This commit is contained in:
parent
8059eeddd5
commit
3e54c7f6a3
8 changed files with 168 additions and 51 deletions
|
|
@ -199,7 +199,13 @@ function summarizeRegions(posts) {
|
|||
}
|
||||
|
||||
function formatNumber(n) {
|
||||
return Number(n || 0).toLocaleString('en-US')
|
||||
// THEIRS, RegionDetail.vue:16: `n.toLocaleString(`${locale.value}-u-nu-latn`)` — locale
|
||||
// grouping separators with Latin digits. This was hardcoded 'en-US', which is only
|
||||
// correct for English: de/fr/es group with '.' and ar would otherwise get Arabic-Indic
|
||||
// digits. The -u-nu-latn keeps Latin digits, which is the half theirs pins deliberately.
|
||||
// A bad tag throws RangeError in toLocaleString, so fall back rather than blank the page.
|
||||
const v = Number(n || 0)
|
||||
try { return v.toLocaleString(`${locale.value || 'en'}-u-nu-latn`) } catch (_) { return v.toLocaleString('en-US') }
|
||||
}
|
||||
|
||||
// Which size step a stat value needs so it is never CUT OFF. Their 47.68px fits up to 10
|
||||
|
|
@ -849,7 +855,9 @@ useShadowStyles(
|
|||
</p>
|
||||
<h1 class="rd-title">{{ titleParts.head ? titleParts.head + ' ' : '' }}<span class="rd-title__last">{{ titleParts.last }}</span></h1>
|
||||
<p v-if="subject.kind === 'country'" class="rd-summary">
|
||||
{{ formatNumber(subject.stats.count) }} {{ t('People Groups') }} ·
|
||||
<!-- RAW, not formatNumber: CountryDetail.vue:40 is `{{ country.count }}` and that
|
||||
component never defines formatNumber (RegionDetail does). Their own asymmetry. -->
|
||||
{{ subject.stats.count }} {{ t('People Groups') }} ·
|
||||
<button
|
||||
type="button"
|
||||
class="rd-light-link"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ import FilterDropdown from '@map/components/search/FilterDropdown.vue'
|
|||
// 'Affinity Bloc', the search placeholder is the full
|
||||
// 'Search names, country/continent, religions...', and prayer_coverage reads 'Intercessors'.
|
||||
import { useRegionsI18n } from './useRegionsI18n.js'
|
||||
// THEIR SEARCH, via the repo's Fuse-parity shim. fuzzy.js already exposes exactly the surface
|
||||
// their UupgsList uses — keys, threshold, minMatchCharLength, and matches[].indices — and its
|
||||
// own header says those indices exist to drive the <span class="search-highlight"> wrapping.
|
||||
// So this is a wiring job, not a new matcher, and it is the ONE shared home for it.
|
||||
import Fuse from '@map/components/search/fuzzy.js'
|
||||
|
||||
const props = defineProps({
|
||||
// ISO-3. Their `lockCountryCode`: the country filter is pre-applied and its dropdown hidden.
|
||||
|
|
@ -74,7 +79,9 @@ const T = computed(() => ({
|
|||
loading: t('Loading results...'),
|
||||
load_more: t('Load More'),
|
||||
full_profile: t('Full Profile'),
|
||||
people_groups: t('People Groups'),
|
||||
total: t('Total'),
|
||||
alternate_name: t('Alternate Names'),
|
||||
exact_filter: t('Exact Search'),
|
||||
}))
|
||||
|
||||
const rows = ref([])
|
||||
|
|
@ -133,17 +140,99 @@ const activeChips = computed(() =>
|
|||
Object.entries(active.value).filter(([, v]) => v).map(([name, v]) => ({ name, ...v }))
|
||||
)
|
||||
|
||||
// Their filter+search pipeline: every active filter ANDs, then the term matches a name or an
|
||||
// alternate name. Substring, not fuzzy — the fuzzy ladder is the /research page's own concern.
|
||||
// Their comment, UupgsList.vue:264-265: "Search compares with combining diacritics stripped, so
|
||||
// 'Mexico' matches 'México'. Safe for highlighting: stripping marks from NFC data preserves
|
||||
// string indices." They do it through Fuse's getFn; our shim has no getFn, so the corpus is
|
||||
// pre-stripped instead and the indices are applied to the ORIGINAL strings — which is exactly
|
||||
// the property their comment establishes.
|
||||
function stripDiacritics(v) {
|
||||
return String(v ?? '').normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
}
|
||||
function escapeHtml(v) {
|
||||
return String(v ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''')
|
||||
}
|
||||
|
||||
// Their key list, verbatim (UupgsList.vue:307-315).
|
||||
const SEARCH_KEYS = ['name', 'imb_alternate_name', 'country_code.label', 'rop1.label',
|
||||
'religion.label', 'wagf_region.label', 'wagf_block.label']
|
||||
|
||||
const byPath = (o, path) => path.split('.').reduce((a, k) => (a == null ? undefined : a[k]), o)
|
||||
|
||||
// Their dropdown filters AND first, then the term. (Their filterUUPGs, same order.)
|
||||
const preFiltered = computed(() => rows.value.filter((p) => {
|
||||
for (const [k, v] of Object.entries(active.value)) {
|
||||
if (v && value(p[k]) !== v.value) return false
|
||||
}
|
||||
return true
|
||||
}))
|
||||
|
||||
/**
|
||||
* Wrap each matched index range in <span class="search-highlight">, their exact construction
|
||||
* (UupgsList.vue:336-348): escape the gap, wrap the hit, and append the tail only on the last
|
||||
* range. Indices come from the stripped corpus and are applied to the original string.
|
||||
*/
|
||||
function highlight(original, indices) {
|
||||
let cursor = 0
|
||||
let out = ''
|
||||
indices.forEach((idx, i) => {
|
||||
const [start, end] = idx
|
||||
const isLast = i === indices.length - 1
|
||||
out += escapeHtml(original.slice(cursor, start))
|
||||
+ '<span class="search-highlight">' + escapeHtml(original.slice(start, end + 1)) + '</span>'
|
||||
+ (isLast ? escapeHtml(original.slice(end + 1)) : '')
|
||||
cursor = end + 1
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
const filtered = computed(() => {
|
||||
const term = searchTerm.value.trim().toLowerCase()
|
||||
return rows.value.filter((p) => {
|
||||
for (const [k, v] of Object.entries(active.value)) {
|
||||
if (v && value(p[k]) !== v.value) return false
|
||||
const term = searchTerm.value.trim()
|
||||
if (!term) return preFiltered.value
|
||||
|
||||
// Search a diacritic-stripped MIRROR of each row, keeping a handle on the original.
|
||||
const corpus = preFiltered.value.map((p) => ({
|
||||
__row: p,
|
||||
name: stripDiacritics(p.name),
|
||||
imb_alternate_name: stripDiacritics(p.imb_alternate_name),
|
||||
country_code: { label: stripDiacritics(label(p.country_code)) },
|
||||
rop1: { label: stripDiacritics(label(p.rop1)) },
|
||||
religion: { label: stripDiacritics(label(p.religion)) },
|
||||
wagf_region: { label: stripDiacritics(label(p.wagf_region)) },
|
||||
wagf_block: { label: stripDiacritics(label(p.wagf_block)) },
|
||||
}))
|
||||
// Their Fuse options: minMatchCharLength 3, and threshold 0 when Exact Search is on.
|
||||
const fuse = new Fuse(corpus, {
|
||||
keys: SEARCH_KEYS,
|
||||
threshold: active.value.exact ? 0 : 0.4,
|
||||
minMatchCharLength: 3,
|
||||
})
|
||||
|
||||
return fuse.search(stripDiacritics(term)).map((res) => {
|
||||
const row = res.item.__row
|
||||
// A shallow copy per result, so highlighting never mutates the fetched rows — their version
|
||||
// resets the labels on every keystroke precisely because it mutates in place.
|
||||
const out = { ...row, matches: [], name_label: '', country_label: '', rop1_label: '' }
|
||||
for (const m of (res.matches || [])) {
|
||||
const original = String(byPath(row, m.key) ?? (m.key.includes('.') ? label(byPath(row, m.key.split('.')[0])) : ''))
|
||||
if (!original) continue
|
||||
const html = highlight(original, m.indices)
|
||||
const parent = m.key.split('.')[0]
|
||||
// Their routing, UupgsList.vue:350-375, for the DEFAULT (non-select-card) card:
|
||||
// name -> the title itself
|
||||
// country_code -> the country link
|
||||
// rop1 -> shown beside the country, as their card does
|
||||
// imb_alternate_name-> a "Alternate Names" matches row
|
||||
// religion/wagf_* -> a labelled matches row
|
||||
// wagf_region is deliberately dropped on this card, exactly as theirs is.
|
||||
if (m.key === 'name') out.name_label = html
|
||||
else if (m.key === 'imb_alternate_name') out.matches.push({ key: T.value.alternate_name, label: html })
|
||||
else if (parent === 'country_code') out.country_label = html
|
||||
else if (parent === 'rop1') out.rop1_label = html
|
||||
else if (parent === 'wagf_region') { /* their card shows none */ }
|
||||
else out.matches.push({ key: parent === 'religion' ? T.value.religion : T.value.wagf_block, label: html })
|
||||
}
|
||||
if (!term) return true
|
||||
const alt = p.imb_alternate_name || ''
|
||||
return String(p.name || '').toLowerCase().includes(term) || String(alt).toLowerCase().includes(term)
|
||||
return out
|
||||
})
|
||||
})
|
||||
const displayed = computed(() => filtered.value.slice(0, shown.value))
|
||||
|
|
@ -194,7 +283,9 @@ useShadowStyles(
|
|||
'.filters__clear-all{ background:none; border:0; cursor:pointer; font:inherit; font-size:15.625px;' +
|
||||
' color:var(--dx-text); text-decoration:underline; text-underline-offset:4px;' +
|
||||
' text-decoration-thickness:2px; }' +
|
||||
'.ul-count{ text-align:center; font-size:15.625px; color:var(--dx-muted); }' +
|
||||
// .repel — their space-between row (composition/_repel.scss), at --font-size-md gap.
|
||||
'.ul-total{ display:flex; gap:17.689px; justify-content:space-between; align-items:center; }' +
|
||||
'.ul-total__count{ font-size:15.625px; }' +
|
||||
// .grid[data-width-lg] — auto-fit, 400px min track, --spacing-2xl gap
|
||||
'.grid{ display:grid; grid-gap:41.6158px;' +
|
||||
' grid-template-columns:repeat(auto-fit, minmax(min(400px,100%), 1fr)); }' +
|
||||
|
|
@ -213,6 +304,9 @@ useShadowStyles(
|
|||
'.uupg__country-link{ color:#4e594f; text-decoration:none; }' +
|
||||
'.uupg__country-link:hover{ text-decoration:underline; }' +
|
||||
// theirs is display:none — the location line is fetched and deliberately not shown
|
||||
// blocks/_cards.scss:258 — brand primary ground, white ink.
|
||||
'.search-highlight{ background-color:#92b195; color:#fff; }' +
|
||||
'.uupg__match{ margin:0; font-size:15.625px; color:var(--dx-muted); }' +
|
||||
'.uupg__content{ display:none; grid-area:content; }' +
|
||||
// .button.compact, grid-area footer, bottom-aligned
|
||||
'.uupg__more-button{ grid-area:footer; align-self:flex-end; display:block; width:fit-content;' +
|
||||
|
|
@ -285,9 +379,14 @@ useShadowStyles(
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="!loading && !failed" class="ul-count">
|
||||
{{ filtered.length.toLocaleString('en-US') }} {{ T.people_groups }}<span v-if="countryName"> · {{ countryName }}</span>
|
||||
</p>
|
||||
<!-- THEIR count line, UupgsList.vue:577-583: "{{ t.total }}: {{ total }}" in a .repel at
|
||||
.font-size-sm, with the see-all link on the right — which CountryDetail hides via
|
||||
hide-see-all-link, so only the total shows. What was here before ("N People Groups ·
|
||||
Cameroon") was MINE, not theirs, and the scope rule says anything that exists on their
|
||||
page must match rather than be improved on. -->
|
||||
<div v-if="!loading && !failed" class="ul-total">
|
||||
<div class="ul-total__count">{{ T.total }}: {{ filtered.length }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="failed" class="ul-status ul-failed" role="status">
|
||||
People groups could not be loaded. This is a connection problem, not a filter.
|
||||
|
|
@ -297,10 +396,20 @@ useShadowStyles(
|
|||
<div v-for="p in displayed" :key="p.slug" class="uupg__card">
|
||||
<img v-if="p.image_url" class="uupg__image" :src="p.image_url" :alt="p.name" loading="lazy">
|
||||
<div class="uupg__header">
|
||||
<h3 class="uupg__name">{{ p.name }}</h3>
|
||||
<!-- v-html because the values carry their <span class="search-highlight"> wrapping;
|
||||
every segment is escaped in highlight() before the span is added. -->
|
||||
<h3 v-if="p.name_label" class="uupg__name" v-html="p.name_label" />
|
||||
<h3 v-else class="uupg__name">{{ p.name }}</h3>
|
||||
<p class="uupg__country">
|
||||
<a class="uupg__country-link" :href="profileUrl(p)">{{ label(p.country_code) }}</a>
|
||||
<span v-if="label(p.rop1)"> ({{ label(p.rop1) }})</span>
|
||||
<a class="uupg__country-link" :href="profileUrl(p)">
|
||||
<span v-if="p.country_label" v-html="p.country_label" /><span v-else>{{ label(p.country_code) }}</span>
|
||||
</a>
|
||||
<span v-if="p.rop1_label"> (<span v-html="p.rop1_label" />)</span>
|
||||
<span v-else-if="label(p.rop1)"> ({{ label(p.rop1) }})</span>
|
||||
</p>
|
||||
<!-- their matches rows, UupgsList.vue:28-32 in the card -->
|
||||
<p v-for="(m, mi) in (p.matches || [])" :key="mi" class="uupg__match">
|
||||
<strong>{{ m.key }}</strong>: <span v-html="m.label" />
|
||||
</p>
|
||||
</div>
|
||||
<p v-if="p.location_description" class="uupg__content">{{ p.location_description }}</p>
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -838,7 +838,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+",
|
||||
"integrity": "sha384-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -858,7 +858,7 @@
|
|||
"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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"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-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -872,7 +872,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-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"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-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -886,7 +886,7 @@
|
|||
"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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"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-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,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-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,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-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,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-W0oDAJmpV9sW5+jcEQSY6rPPcbw3+bjGjMNqDlBo8Py5eYgZ2bSKaijh88eA15a+" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-vUmTHedu6c546r9hvqMDA2q8i6IqkXijvOqgnTG6m9qVyl6U5NoFkWPYpGljvbaP" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue