diff --git a/plugin-designs/gospel-internet/board.vue b/plugin-designs/gospel-internet/board.vue index b1105c883..1e2195659 100644 --- a/plugin-designs/gospel-internet/board.vue +++ b/plugin-designs/gospel-internet/board.vue @@ -23,6 +23,8 @@ */ import { ref, computed, onMounted, onBeforeUnmount, watch, getCurrentInstance } from 'vue' import { useDashboardLayout } from '@map/composables/useDashboardLayout.js' +import { useShareableView } from '@map/composables/useShareableView.js' +import { SHARE_FACETS } from './src/model.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { listLabel } from '@map/lists/peopleGroupList.js' import { useBoard } from './src/useBoard.js' @@ -66,6 +68,11 @@ onBeforeUnmount(() => { if (selTimer) clearTimeout(selTimer) }) // board opens already in its state: no flash of the default view, and the first render mounts the panel // the preset asks for (the language tree included) where a headless mount check can see it. board.readUrl() +// SHARE LINKS FROM THE KIT (useShareableView, query mode): the filters and the typed search live in the +// URL under the board's published names (?access= ?band= ?country= …, the alias map keeps links already +// shared working), restored on every load, written ONLY on our own leaves (urlState:'query') so a +// partner's address bar is never rewritten. facetKeys is explicit: its default reads nothing on a cold load. +useShareableView(bus, { mode: 'query', write: board.profileConfig.value?.urlState === 'query', facetKeys: SHARE_FACETS }) // ── responsive: one panel at a time under 760px (ResizeObserver on the shell, not @container: a // container query cannot target the container itself, which is unreliable inside a shadow root) ────── diff --git a/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs b/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs index 3f2025ccc..00c15b66d 100644 --- a/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs +++ b/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs @@ -141,6 +141,19 @@ for (const n of ix.nodes) if (!n.p) { const x = scZero.get(n.id); gy += x.g + x. ok(gy === 0, `with only zero-access groups in scope, no family counts a green or yellow language (${gy})`) bus.clearFilters() +// 12. share links through the kit (useShareableView): the board's published names map to its bus keys, +// and a link already shared restores (single values, and multi-values written with '|') +const SV = await import(resolve(REPO, 'library/composables/useShareableView.js')) +const r1 = SV.parseQuery('?access=bridge&country=IND&wayin=deaf&app=Telegram%20Messenger', M.SHARE_FACETS) +ok(r1.filters.access_level === 'bridge' && r1.filters.country === 'IND' && r1.filters.way_in === 'deaf' && r1.filters.messaging === 'Telegram Messenger', + 'kit parser maps ?access= ?wayin= ?app= to the bus keys (old single-value links restore)') +const r2 = SV.parseQuery('?band=yes|partial', M.SHARE_FACETS) +ok(Array.isArray(r2.filters.band) && r2.filters.band.join(',') === 'yes,partial', `an old multi-value link (band=yes|partial) restores as two values (got ${JSON.stringify(r2.filters.band)})`) +const w = SV.serializeQuery({ access_level: 'bridge', band: ['yes', 'partial'] }, '', '?view=zero&utm_source=x', { facetKeys: M.SHARE_FACETS }) +ok(w.includes('access=bridge') && w.includes('view=zero') && w.includes('utm_source=x') && !w.includes('access_level'), `the kit writes the published names and keeps the board's own params (${w})`) +const mine = M.buildQuery({ view: 'zero', filterSet: { band: 'yes' }, keep: '?band=yes,partial', filters: false }) +ok(mine.includes('band=yes%2Cpartial') && mine.includes('view=zero'), 'the board\'s own writer leaves the filter params to the kit (filters:false)') + // 11. translation readiness: current against its snapshot, every roster language present, the no-NT set is // the tree's, the 31 codes Glottolog lacks read as UNKNOWN (null) not "none", and the signal count is a // recount of the five named facts (no hidden weighting) diff --git a/plugin-designs/gospel-internet/src/model.js b/plugin-designs/gospel-internet/src/model.js index f16731a9d..122fbde53 100644 --- a/plugin-designs/gospel-internet/src/model.js +++ b/plugin-designs/gospel-internet/src/model.js @@ -82,6 +82,9 @@ export const URL_KEYS = { app: 'messaging', } export const VIEWS = ['board', 'zero', 'province', 'platforms'] +/** The board's URL filter names -> bus keys, for the kit's useShareableView (query mode, alias map). + * The published names are kept so links already shared keep restoring. */ +export const SHARE_FACETS = Object.fromEntries(Object.entries(URL_KEYS)) /** Parse `?view=&access=…&group=` into { view, filters, group }. Unknown views fall back to board. */ export function parseQuery(search) { @@ -109,13 +112,15 @@ export function parseQuery(search) { } /** The inverse: filterSet + view + group -> a query string (no leading '?'), stable key order. */ -export function buildQuery({ view, filterSet, group, keep, panel, tree } = {}) { +export function buildQuery({ view, filterSet, group, keep, panel, tree, filters = true } = {}) { const q = new URLSearchParams(keep || '') - for (const name of [...Object.keys(URL_KEYS), 'view', 'group', 'panel', 'tree']) q.delete(name) + // filters:false = the FILTER params belong to another writer (the kit's useShareableView): neither + // delete nor write them, so the two writers never fight over one address bar + for (const name of [...(filters ? Object.keys(URL_KEYS) : []), 'view', 'group', 'panel', 'tree']) q.delete(name) if (view && view !== 'board') q.set('view', view) if (panel === 'tree') q.set('panel', 'tree') if (tree) q.set('tree', tree) - for (const [name, key] of Object.entries(URL_KEYS)) { + if (filters) for (const [name, key] of Object.entries(URL_KEYS)) { if (tree && key === 'language') continue const v = filterSet?.[key] if (v === undefined || v === null || v === '') continue diff --git a/plugin-designs/gospel-internet/src/useBoard.js b/plugin-designs/gospel-internet/src/useBoard.js index feb2c6b70..a7a4022b0 100644 --- a/plugin-designs/gospel-internet/src/useBoard.js +++ b/plugin-designs/gospel-internet/src/useBoard.js @@ -164,9 +164,12 @@ export function useBoard() { const q = M.parseQuery(location.search) const hostView = cfg().view setView(q.view !== 'board' ? q.view : (M.VIEWS.includes(hostView) ? hostView : 'board')) - // Explicit facets in the URL (or the host's profile-config `filters`) narrow the preset further. + // The host's profile-config `filters` narrow the preset; URL FILTER params are read by the kit's + // useShareableView (board.vue), except Gospel Resources' ?codes= / ?code=, which are this board's. const hostFilters = cfg().filters && typeof cfg().filters === 'object' ? cfg().filters : {} - for (const [k, v] of Object.entries({ ...hostFilters, ...q.filters })) if (k in M.FACET_ACC) bus.setFilter(k, v) + for (const [k, v] of Object.entries(hostFilters)) if (k in M.FACET_ACC) bus.setFilter(k, v) + const codes = [...new URLSearchParams(location.search).getAll('codes'), ...new URLSearchParams(location.search).getAll('code')].length ? q.filters.language : null + if (codes) bus.setFilter('language', codes) const group = q.group || cfg().group || '' if (group) pendingGroup = group const panel = q.panel === 'tree' || cfg().panel === 'tree' ? 'tree' : 'map' @@ -186,7 +189,7 @@ export function useBoard() { function syncUrl() { if (!writeUrl.value || typeof history === 'undefined') return const qs = M.buildQuery({ view: view.value, filterSet: bus.filterSet, group: openSlug.value, keep: location.search, - panel: centre.value, tree: treeNode.value }) + panel: centre.value, tree: treeNode.value, filters: false }) const next = location.pathname + (qs ? '?' + qs : '') + location.hash if (next !== location.pathname + location.search + location.hash) { try { history.replaceState(history.state, '', next) } catch (_) { /* sandboxed */ } diff --git a/public/dx-maps/doxa-maps-spa/manifest.json b/public/dx-maps/doxa-maps-spa/manifest.json index 8936e0a69..b2cf2c0f6 100644 --- a/public/dx-maps/doxa-maps-spa/manifest.json +++ b/public/dx-maps/doxa-maps-spa/manifest.json @@ -604,7 +604,7 @@ "bundle": "gospel-internet", "scriptUrl": "./gospel-internet/gospel-internet.js", "output": "dx-maps/gospel-internet/gospel-internet.js", - "integrity": "sha384-wRWCJhXf3QrvCUO5i22k46dpJFvQWnI8dmA1jLl2l4YkiomWTVYPjTfXlKz3HRez", + "integrity": "sha384-3be8zy4914UlmNZka99aGQPzEbp80c2Zr4ES+kvPMwW7SEzWNfxYEJgqgpbHr18d", "elements": [ "gospel-internet-board" ], @@ -623,7 +623,7 @@ "scriptUrl": "./gospel-internet/gospel-internet.js", "embed": { "iframe": "", - "script": "\n\n\n\n" + "script": "\n\n\n\n" }, "instances": [ { diff --git a/public/dx-maps/gospel-internet/board/board/index.html b/public/dx-maps/gospel-internet/board/board/index.html index 1b56433fc..675cb5873 100644 --- a/public/dx-maps/gospel-internet/board/board/index.html +++ b/public/dx-maps/gospel-internet/board/board/index.html @@ -36,7 +36,7 @@ - + - + - + - + - + - + - + \n\n" + "script": "\n\n\n\n" }, "instances": [ {