- internal/playground → internal/plugin-gallery; playground-smoke → plugin-gallery-smoke;
generate-spa → generate-gallery (scripts: gallery, smoke:gallery; smoke:playground stays as alias)
- generate-gallery exports renderGallery(model, { mode }) — the dev plugin serves the same page
at / from the discovered registry (dev mode imports the dev entries, ladder popovers on);
the build writes public/index.html from the manifest (byte-identical to before)
- Docs and probes use the gallery vocabulary
113 lines
5.6 KiB
JavaScript
113 lines
5.6 KiB
JavaScript
/**
|
|
* embedBase.js — Where THIS bundle's built tree is served from, resolved at runtime.
|
|
*
|
|
* WHY: the built tree (<root>/<bundle>/<bundle>.js + <bundle>/<profile>/index.html …) is
|
|
* dropped onto many hosts — the dev plugin gallery, doxa.life under /js/doxa-maps-build/,
|
|
* any CDN subpath. Share links + embed snippets must point INTO that tree wherever it
|
|
* lands, so profiles declare their embed page TREE-RELATIVE (`<bundle>/<profile>/index.html`
|
|
* or `<bundle>/<profile>/<instance>/index.html`) and this module supplies the tree root.
|
|
* A hardcoded `/js/doxa-maps/…` 404s the moment the tree is served anywhere else.
|
|
*
|
|
* Tree root — derived from the bundle's OWN script URL. Rollup rewrites `import.meta.url`
|
|
* in the IIFE build to `document.currentScript.src` (captured at script start, with a
|
|
* `document.baseURI` fallback), so in a built bundle it is `<treeRoot>/<bundle>/<bundle>.js`
|
|
* and the root is two path segments up. It is trusted ONLY when the last two segments look
|
|
* like `<name>/<name>.js` (the layout generate-tree.mjs emits). In dev (ESM) it is this
|
|
* module's own URL (`…/library/utils/embedBase.js`) and is rejected — the plugin gallery passes
|
|
* a whole `shareUrl` instead.
|
|
*
|
|
* Embed base ladder — resolveEmbedBase(profileConfig):
|
|
* 1. profileConfig.embedBaseUrl — host-supplied per embed (`profile-config` JSON)
|
|
* 2. window.MAP_EMBED_BASE_URL — host-supplied page global, set before the IIFE loads
|
|
* 3. bundleTreeRoot() — derived from the script URL (a dropped tree works anywhere)
|
|
* 4. '' — none known; a tree-relative path resolves against the page
|
|
*
|
|
* Share URL ladder — resolveShareUrl({ shareUrl, embedPath, profileConfig }):
|
|
* 1. shareUrl | profileConfig.shareUrl — a WHOLE url from the host wins (plugin gallery deep link)
|
|
* 2. embedPath | profileConfig.shareEmbedPath
|
|
* absolute http(s) → passes through
|
|
* leading '/' → legacy root-relative: location.origin + path
|
|
* otherwise (tree-relative) → embedBase + '/' + path when a base is known,
|
|
* else resolved against document.baseURI
|
|
* 3. location.origin + location.pathname — the page itself ("Share shares THE MAP")
|
|
*
|
|
* Host usage (inject before the IIFE loads):
|
|
* <script>window.MAP_EMBED_BASE_URL = 'https://cdn.example.org/maps/doxa-maps';</script>
|
|
* <script src="https://cdn.example.org/maps/doxa-maps/doxa-simple-map/doxa-simple-map.js"></script>
|
|
*
|
|
* Pure JS, SSR-safe: every window/document access is guarded.
|
|
*/
|
|
|
|
// Evaluated ONCE at module init — inside the IIFE that is script start, while
|
|
// document.currentScript still points at the bundle's own <script> tag.
|
|
const SCRIPT_URL = import.meta.url
|
|
|
|
const stripSlashes = (s) => String(s || '').trim().replace(/\/+$/, '')
|
|
|
|
/**
|
|
* The served root of the built tree, derived from the bundle's own script URL.
|
|
* Returns null when that URL does not look like a built bundle (`…/<name>/<name>.js`),
|
|
* e.g. the dev ESM module URL — never guess from an untrusted layout.
|
|
* @returns {string|null} e.g. "https://doxa.life/js/doxa-maps-build/doxa-maps"
|
|
*/
|
|
export function bundleTreeRoot() {
|
|
try {
|
|
const href = String(SCRIPT_URL || '').split(/[?#]/)[0]
|
|
// …/<dir>/<file>.js — the dir must equal the file's basename (generate-tree layout).
|
|
const m = href.match(/^(.*)\/([^/]+)\/([^/]+)\.js$/)
|
|
if (!m || m[2] !== m[3]) return null
|
|
return m[1] || null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The base URL tree-relative embed pages are joined to (no trailing slash).
|
|
* Ladder: profileConfig.embedBaseUrl → window.MAP_EMBED_BASE_URL → bundleTreeRoot() → ''.
|
|
* @param {object|null} [profileConfig] the parsed profile-config (already unref'd)
|
|
* @returns {string}
|
|
*/
|
|
export function resolveEmbedBase(profileConfig) {
|
|
const fromConfig = profileConfig?.embedBaseUrl
|
|
if (typeof fromConfig === 'string' && fromConfig.trim()) return stripSlashes(fromConfig)
|
|
if (typeof window !== 'undefined') {
|
|
const fromHost = window.MAP_EMBED_BASE_URL
|
|
if (typeof fromHost === 'string' && fromHost.trim()) return stripSlashes(fromHost)
|
|
}
|
|
return stripSlashes(bundleTreeRoot() || '')
|
|
}
|
|
|
|
/**
|
|
* The URL the Share button shows, copies and iframes. See the header for the ladder.
|
|
* @param {object} [opts]
|
|
* @param {string} [opts.shareUrl] explicit whole url (beats everything)
|
|
* @param {string} [opts.embedPath] the profile's embed page — tree-relative, root-relative or absolute
|
|
* @param {object|null} [opts.profileConfig] the parsed profile-config (already unref'd)
|
|
* @returns {string}
|
|
*/
|
|
export function resolveShareUrl({ shareUrl, embedPath, profileConfig } = {}) {
|
|
const cfg = profileConfig || null
|
|
const hasWindow = typeof window !== 'undefined'
|
|
|
|
// (1) a whole url supplied by the host — the plugin gallery's own deep link, a CMS page, …
|
|
const whole = shareUrl || cfg?.shareUrl
|
|
if (typeof whole === 'string' && whole.trim()) return whole.trim()
|
|
|
|
// (2) the profile's embed page
|
|
const path = String(embedPath || cfg?.shareEmbedPath || '').trim()
|
|
if (path) {
|
|
if (/^https?:\/\//i.test(path)) return path
|
|
if (path.startsWith('/')) return hasWindow ? window.location.origin + path : path
|
|
const base = resolveEmbedBase(cfg)
|
|
const rel = path.replace(/^\.\//, '')
|
|
if (base) return base + '/' + rel
|
|
if (typeof document !== 'undefined') {
|
|
try { return new URL(rel, document.baseURI).href } catch { /* fall through */ }
|
|
}
|
|
return rel
|
|
}
|
|
|
|
// (3) the page itself — each map builds to a self-contained page.
|
|
return hasWindow ? window.location.origin + window.location.pathname : ''
|
|
}
|