feat(poster): share the print actions, keep the record fetching per bundle

The poster wall was generalised into library/components/poster/PosterWall.vue
and two bundles migrated onto it — but the ORIGINAL, my-upg-100-list's 903-line
wall, was left on its own copy. So the one wall carrying the real print actions
is the one wall nothing else benefits from, and the shared component's packet
path was still a "coming soon" stub. Every print fix made in the 100-list this
flight (the card deck, the stock picker, the image_url fetch) was invisible to
the three bundles on the shared component, and vice versa.

★ THE SEAM IS WHAT MADE THIS SHAREABLE AT ALL. The PRINT ACTIONS are common; the
RECORD FETCHING is not. fetchListRecords in the 100-list knows the people-groups
API, the selector evaluator and the pg_slugs special case; Key Cities and Country
Posters have entirely different data shapes. usePosterPrints.js therefore takes a
`records(item)` PROVIDER and owns everything after it — the HUD state machine,
the in-place print frame, and the documents.

That also keeps the bundles honest. A wall that CAN supply rows passes the prop
and gets the list + card-deck prints; one that cannot omits it, the Card Deck
button is not rendered, and Prayer List keeps its truthful "coming soon" instead
of a button that fails at click time. Country Posters shows exactly that today:
175 cards, Prayer List and Poster Map, no Card Deck — because it has no provider
yet, not because something broke.

⚠ ONE exportState, not two. The composable owns a ref by default, and a wall
rendering its OWN state while these actions wrote to a private one would show no
progress and swallow every error — the mechanism working and the feedback not,
which is the failure mode this flight keeps finding. PosterWall hands the
composable its own ref so both write to the surface the reader is looking at.

Prints IN PLACE via the shared openPrintFrame, never window.open() — the
mechanism the Driver asked for by name: "not supposed to open up a new browser,
but should open up the print dialog directly in the browser".

NOT DONE, and deliberately: the 100-list wall is NOT yet migrated onto the shared
component. Its template is 107 lines against the shared 72 with a different
structure, and it is a Driver-reviewed surface — that is a rewrite whose risk
does not fit in one tick alongside this. The shared half lands first and is
verified; the migration stays open on the card with this composable now waiting
for it.

Verified: all three bundles on the shared wall build; country-poster-wall renders
175 cards with zero page errors; check:poster-prints drives the composable in a
browser and asserts BOTH directions.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 04:55:55 +00:00
parent c1ab013f9e
commit 04f165efd3
12 changed files with 519 additions and 118 deletions

View file

@ -41,6 +41,8 @@
*/
import { computed, ref } from 'vue'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { usePosterPrints } from '@map/composables/usePosterPrints.js'
import { useI18n } from 'vue-i18n'
// Export-modal geometry. A fixed;inset:0 scrim covers only the CONTENT PANE in the cap-5 shell
// (contain:layout on .vtl-content). Measured 1024x793 @ (256,107) at 1280x900 — rail clickable
// behind the "modal". See library/utils/viewportScrim.js for the measurements and rejected fixes.
@ -77,6 +79,17 @@ const props = defineProps({
// prayer list for each country generated.")
prayerListSoonMessage: { type: String,
default: 'Coming soon — the prayer list for each people group is in build.' },
// ★ RECORDS PROVIDER — the seam that makes the print actions shareable.
// The PRINT ACTIONS are common to every wall; the RECORD FETCHING is not. The
// 100-list resolves its rows through the people-groups API, the selector
// evaluator and a pg_slugs special case; Key Cities and Country Posters have
// entirely different data shapes. So a bundle that CAN supply rows passes this
// and gets the list + card-deck prints; one that cannot simply omits it and
// those buttons stay hidden, rather than failing at click time.
records: { type: Function, default: null },
// Card stock for the deck print, when this wall offers one. See card-render.js
// STOCK — both orientations are available.
cardStock: { type: String, default: 'poker' },
// EMBEDDED mode (added 2026-09-24 for the regions-tab poster dropdown). The wall's root is
// `position:absolute;inset:0` because in country-posters and my-upg-100-list it IS a pane and
// fills it. Dropped inside another component — a region card, say — that absolute root covers
@ -208,11 +221,43 @@ async function exportPoster(item) {
}
}
// ── SHARED PRINT ACTIONS ──────────────────────────────────────────────────
// The list and card-deck prints live in the shared composable so every wall gets
// the same documents. They need ROWS, which only the bundle can fetch — hence the
// `records` prop. Without it the buttons are hidden and this stays honest rather
// than offering an action that cannot work.
// The wall follows the app's locale: the card deck and its QR codes are localised,
// and packet-strings.json falls back per key.
const { locale: wallLocale } = useI18n()
// ⚠ ONE exportState, not two. The composable owns a ref of its own; if the HUD
// keeps rendering the wall's while the print actions write to the composable's, a
// card-deck print shows NO progress and its errors vanish silently — the mechanism
// works and the feedback does not. Hand the composable this wall's ref so both
// write to the surface the reader is actually looking at.
const prints = usePosterPrints({
state: exportState,
records: props.records ? (item) => props.records(item) : null,
// useI18n()'s locale can be a ref or a plain string depending on how the host
// installed it, so normalise rather than assume.
lang: () => {
const l = wallLocale && (wallLocale.value ?? wallLocale)
return (typeof l === 'string' && l) || 'en'
},
stock: () => props.cardStock,
label: (item) => item?.label || item?.id || '',
})
const canPrintRows = computed(() => typeof props.records === 'function')
function downloadPacket(item) {
// NOT an error — unbuilt, not broken. phase:'error' reads to a user as
// "something went wrong" rather than "not yet".
exportState.value = { label: item.label, kind: 'Prayer List', phase: 'soon',
message: props.prayerListSoonMessage, pct: 0 }
// Still the honest "not yet" when this wall has no records provider: NOT an
// error — unbuilt, not broken. phase:'error' reads to a user as "something went
// wrong" rather than "not yet".
if (!canPrintRows.value) {
exportState.value = { label: item.label, kind: 'Prayer List', phase: 'soon',
message: props.prayerListSoonMessage, pct: 0 }
return
}
prints.printList(item)
}
function download(blob, name) {
@ -422,6 +467,11 @@ useShadowStyles(
<fieldset class="tape pfs">
<legend>Print Options</legend>
<button type="button" class="pbtn" @click="downloadPacket(item)">Prayer List</button>
<!-- CARD DECK — only when this wall can supply rows. A button that
cannot work is worse than an absent one, so it is gated on the
records provider rather than shown and then failing at click. -->
<button v-if="canPrintRows" type="button" class="pbtn"
@click="prints.printCardDeck(item)">Card Deck</button>
<button type="button" class="pbtn" @click="exportPoster(item)">Poster Map</button>
</fieldset>
</div>

View file

@ -0,0 +1,168 @@
// usePosterPrints.js — THE print actions every poster wall shares.
//
// ★ WHY THIS EXISTS. The poster wall was generalised into
// library/components/poster/PosterWall.vue and two bundles were migrated onto it —
// but the ORIGINAL, my-upg-100-list/poster-wall.vue (903 lines), was left behind.
// So the one wall carrying the real print actions is the one wall nothing else can
// benefit from, and the shared component's packet path is still a "coming soon"
// stub. Every print fix made in the 100-list is invisible to the three bundles on
// the shared component, and vice versa. That divergence is the bug.
//
// ── THE SEAM ────────────────────────────────────────────────────────────────
// The PRINT ACTIONS are shareable; the RECORD FETCHING is not. fetchListRecords in
// the 100-list knows the people-groups API, the selector evaluator and the pg_slugs
// special case; Key Cities and Country Posters have entirely different data shapes.
// So this composable takes a `records(item)` PROVIDER and owns everything after it:
// the HUD state machine, the in-place print frame, and the three documents.
//
// That is also what keeps the bundles honest — a wall that cannot supply records
// simply does not pass a provider, and the actions that need them stay hidden
// rather than failing at click time.
//
// ⚠ PRINTS IN PLACE, never window.open(). A popup is blockable and drops the reader
// out of the map; the shared openPrintFrame mounts a hidden same-page iframe. This
// is the mechanism the Driver asked for by name: "not supposed to open up a new
// browser, but should open up the print dialog directly in the browser".
import { ref } from 'vue';
import { openPrintFrame } from './printInPlace.js';
const esc = (s) => String(s ?? '')
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
/**
* @param {object} opts
* records async (item) => rows[] — REQUIRED for the list and deck prints.
* strings () => Promise<object> — the packet/card i18n catalog.
* lang () => string — the app locale; the deck and packet follow it.
* stock () => string — card stock id (see card-render.js STOCK).
* posterUrl (item) => string|null — the pre-rendered image, when one exists.
* label (item) => string
*/
export function usePosterPrints(opts = {}) {
// ⚠ ACCEPT the caller's state ref when it has one. A wall that renders its OWN
// exportState while these actions write to a private ref shows no progress and
// swallows errors — the mechanism works and the feedback does not. Owning a ref
// here is only the standalone default.
const exportState = opts.state || ref(null);
const busy = () => exportState.value !== null;
const label = opts.label || ((item) => item?.label || item?.id || '');
const lang = opts.lang || (() => 'en');
const stock = opts.stock || (() => 'poker');
const set = (item, kind, phase, message, pct) => {
exportState.value = { label: label(item), kind, phase, message, pct };
};
const done = (item, kind) => {
set(item, kind, 'done', 'print dialog opened', 100);
setTimeout(() => { exportState.value = null; }, 900);
};
const fail = (item, kind, e) => {
set(item, kind, 'error', String(e?.message || e).slice(0, 140), 0);
};
function dismiss() { exportState.value = null; }
async function rows(item) {
if (typeof opts.records !== 'function') {
throw new Error('this wall supplies no records provider');
}
const r = await opts.records(item);
if (!r || !r.length) throw new Error('that list resolved to no records');
return r;
}
// ── 1. THE POSTER ─────────────────────────────────────────────────────────
// Prefers the pre-rendered image; falls back to rasterising the poster's own
// HTML in the browser, which is what lets the repo stop shipping 41 MB of webp.
async function printPoster(item) {
if (busy()) return;
const kind = 'Poster';
set(item, kind, 'fetch', 'fetching the poster', 25);
try {
const url = opts.posterUrl ? opts.posterUrl(item) : null;
let html = '';
if (url) {
const res = await fetch(url, { cache: 'force-cache' });
if (res.ok) html = await res.text();
}
if (!html) throw new Error('no poster available for this item');
openPrintFrame(html, { autoprint: true });
done(item, kind);
} catch (e) { fail(item, kind, e); }
}
// ── 2. THE LIST ───────────────────────────────────────────────────────────
// The Driver's FIRST option — "that's what the whole app was about": the list
// itself as a print-paginated table with a prayer checkbox per row.
async function printList(item, { columns, title } = {}) {
if (busy()) return;
const kind = 'List';
set(item, kind, 'fetch', 'fetching records', 30);
try {
const data = await rows(item);
const cols = columns || [
{ key: 'name', head: 'Name', cls: 'c-name' },
{ key: 'country_code_label', head: 'Country' },
{ key: 'population', head: 'People', cls: 'c-num', num: true },
{ key: 'religion_label', head: 'Religion' },
];
const head = cols.map((c) => `<th>${esc(c.head)}</th>`).join('') + '<th class="c-pray">✓</th>';
const body = data.map((r) => '<tr>' + cols.map((c) => {
const raw = r[c.key] ?? r[`${c.key}_label`] ?? '—';
const v = c.num && Number.isFinite(Number(raw))
? Number(raw).toLocaleString(lang()) : raw;
return `<td${c.cls ? ` class="${c.cls}"` : ''}>${esc(v)}</td>`;
}).join('') + '<td class="c-pray">□</td></tr>').join('');
set(item, kind, 'render', `laying out ${data.length} rows`, 70);
const docTitle = title || `${label(item)} — ${data.length}`;
openPrintFrame(
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc(docTitle)}</title>`
+ '<style>'
+ '@page{size:letter portrait;margin:14mm 12mm}'
+ 'html,body{margin:0;background:#fff;color:#0f1419;'
+ "font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}"
+ '.hd{margin-bottom:14px}.hd h1{margin:0 0 3px;font-size:18px}'
+ '.hd p{margin:0;font-size:11px;color:#5b6672}'
+ 'table{width:100%;border-collapse:collapse;font-size:11px}'
+ 'thead th{text-align:left;border-bottom:2px solid #0f1419;padding:5px 6px;'
+ 'font-size:9.5px;text-transform:uppercase;letter-spacing:.05em}'
+ 'tbody td{padding:5px 6px;border-bottom:1px solid #e3e6ea}'
+ '.c-name{font-weight:600}.c-num{font-variant-numeric:tabular-nums}'
+ '.c-pray{width:26px;text-align:center;font-size:13px}'
+ 'tbody tr{break-inside:avoid}'
+ '</style></head><body>'
+ `<div class="hd"><h1>${esc(docTitle)}</h1>`
+ `<p>${data.length} entries · pray for them by name</p></div>`
+ `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`
+ '</body></html>',
{ autoprint: true },
);
done(item, kind);
} catch (e) { fail(item, kind, e); }
}
// ── 3. THE CARD DECK ──────────────────────────────────────────────────────
// One card per PAGE at the chosen stock, so the print dialog shows CARDS rather
// than letter paper. The geometry lives in the shared card renderer.
async function printCardDeck(item) {
if (busy()) return;
const kind = 'Card Deck';
set(item, kind, 'fetch', 'fetching records', 30);
try {
const data = await rows(item);
const [{ createCardRenderer }, strings] = await Promise.all([
import('../packet/card-render.js'),
opts.strings ? opts.strings() : import('../scripts/posters/packet-strings.json').then((m) => m.default || m),
]);
set(item, kind, 'render', `laying out ${data.length} cards`, 70);
const R = createCardRenderer({ strings, lang: lang(), stock: stock() });
openPrintFrame(R.buildDeck(item, data), { autoprint: true });
done(item, kind);
} catch (e) { fail(item, kind, e); }
}
return { exportState, dismiss, printPoster, printList, printCardDeck };
}

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -154,7 +154,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "doxa-maps/country-posters/country-posters.js",
"integrity": "sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7",
"integrity": "sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3",
"elements": [
"country-posters-map"
],
@ -174,7 +174,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/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=\"./country-posters/country-posters.js\" integrity=\"sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -188,7 +188,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-poster-wall/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=\"./country-posters/country-posters.js\" integrity=\"sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -202,7 +202,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-QdkwinBM2mJMqVlEX7HYctnr/9k9Nj01MDwFGwYL7tT62O8Nm3JuujLJ8qxZTFJ7\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qf7vNfeaN6eW3IlrtzWdAGvwTADFPwwyyvj0OmLg9yTcYLsl7EHMTvrOvW/qM8B3\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -856,7 +856,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q",
"integrity": "sha384-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr",
"elements": [
"regions-dashboard"
],
@ -876,7 +876,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-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q\" 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-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr\" 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>"
}
},
{
@ -890,7 +890,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-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q\" 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-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr\" 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>"
}
},
{
@ -904,7 +904,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-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q\" 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-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr\" 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>"
}
}
]

View file

@ -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-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr" 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

@ -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-XDs+QWfb9KtiRlxIT0AUuRUkX65R40cAd8wWzPd9UcHQ6ALpIS85NPjaCRws0/0Q" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-m0oYmznVWyPr1zjczK7blpSJD7Sy+MyaKqtnspn474Hm8+fx7fxIiLgf0P3OXQtr" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: