diff options
Diffstat (limited to 'docs/_static')
| -rw-r--r-- | docs/_static/css/version-picker.css | 11 | ||||
| -rw-r--r-- | docs/_static/js/pagefind-wrapper.js | 69 | ||||
| -rw-r--r-- | docs/_static/js/version-picker.js | 153 |
3 files changed, 233 insertions, 0 deletions
diff --git a/docs/_static/css/version-picker.css b/docs/_static/css/version-picker.css new file mode 100644 index 00000000..16ce16c3 --- /dev/null +++ b/docs/_static/css/version-picker.css @@ -0,0 +1,11 @@ +.vyos-version-picker-item { list-style: none; } +#vyos-version-picker { display: flex; align-items: center; gap: .5em; padding: .4em 0; font-size: .9em; } +#vyos-version-picker select { max-width: 14em; padding: .15em .3em; } +#vyos-version-picker select:focus { outline: 2px solid #ffae12; outline-offset: 1px; } +.vyos-pdf-link { font-weight: 600; } +.vyos-version-banner { position: relative; padding: .6em 2.2em .6em 1em; font-size: .95em; line-height: 1.4; } +.vyos-banner-dev { background: #e7f2fa; color: #2a6496; } +.vyos-banner-newer-lts { background: #fff6e5; color: #6b5900; } +.vyos-banner-eol { background: #fdecea; color: #8a1f11; } +.vyos-version-banner a { text-decoration: underline; color: inherit; font-weight: 600; } +.vyos-banner-dismiss { position: absolute; right: .5em; top: .35em; background: none; border: 0; font-size: 1.2em; cursor: pointer; color: inherit; } diff --git a/docs/_static/js/pagefind-wrapper.js b/docs/_static/js/pagefind-wrapper.js new file mode 100644 index 00000000..a4acadf8 --- /dev/null +++ b/docs/_static/js/pagefind-wrapper.js @@ -0,0 +1,69 @@ +/* Version-scoped Pagefind loader. Derives its base from the current URL so the + * same file works on production, canary, and /pr-<n>/ previews (§9). */ +(function (window) { + 'use strict'; + + function basePathFor(pathname) { + var m = pathname.match(/^((\/pr-\d+)?\/[a-z]{2}(?:_[A-Z]{2})?\/[^/]+\/)/); + if (!m) return null; + return { base: m[1], prefix: m[2] || '' }; + } + + function prefixResultUrl(url, prefix) { + return prefix ? prefix + url : url; + } + + function assetUrlsFor(base) { + return { + css: base + 'pagefind/pagefind-ui.css', + js: base + 'pagefind/pagefind-ui.js', + }; + } + + function init() { + var mount = document.getElementById('vyos-search'); + if (!mount) return; + var ctx = basePathFor(window.location.pathname); + if (!ctx) return; + var assets = assetUrlsFor(ctx.base); + /* Stylesheet first so the UI never renders unstyled. CSS load failure is + * cosmetic only — no error handler on the link. */ + var l = document.createElement('link'); + l.rel = 'stylesheet'; + l.href = assets.css; + document.head.appendChild(l); + var s = document.createElement('script'); + s.src = assets.js; + s.onerror = function () { + /* Per-version Pagefind assets missing (index not built yet, preview + * namespace, transient failure) — show a visible notice instead of + * leaving #vyos-search a silent empty div. */ + var p = document.createElement('p'); + p.className = 'vyos-search-unavailable'; + p.textContent = 'Search is temporarily unavailable for this version.'; + mount.appendChild(p); + }; + s.onload = function () { + /* global PagefindUI */ + new window.PagefindUI({ + element: '#vyos-search', + baseUrl: ctx.base, + bundlePath: ctx.base + 'pagefind/', + processResult: function (result) { + result.url = prefixResultUrl(result.url, ctx.prefix); + return result; + }, + }); + }; + document.head.appendChild(s); + } + + window.VyOSSearch = { + basePathFor: basePathFor, + prefixResultUrl: prefixResultUrl, + assetUrlsFor: assetUrlsFor, + init: init, + }; + if (typeof document !== 'undefined' && document.addEventListener) + document.addEventListener('DOMContentLoaded', init); +})(window); diff --git a/docs/_static/js/version-picker.js b/docs/_static/js/version-picker.js new file mode 100644 index 00000000..f13ad60f --- /dev/null +++ b/docs/_static/js/version-picker.js @@ -0,0 +1,153 @@ +/* VyOS docs version picker + status banner + language scaffold. + * Vanilla JS, no dependencies, no build step. Degrades silently when + * /versions.json is unreachable (docs stay fully readable). */ +(function (window) { + 'use strict'; + + // Deliberately does not match /pr-<n>/ preview prefixes — previews are single-version, + // so the picker has nothing to switch between and stays hidden there by design. + function parseLocation(pathname) { + var m = pathname.match(/^\/([a-z]{2}(?:_[A-Z]{2})?)\/([^/]+)\/(.*)$/); + if (!m) return null; + return { lang: m[1], slug: m[2], rest: m[3] }; + } + + // Contract: versions.json lists versions newest-first, so the first 'lts' entry found + // here is the newest LTS — callers rely on that ordering rather than comparing versions. + function newestLts(manifest) { + for (var i = 0; i < manifest.versions.length; i++) + if (manifest.versions[i].status === 'lts') return manifest.versions[i].slug; + return null; + } + + function bannerFor(slug, manifest) { + var entry = null, i; + for (i = 0; i < manifest.versions.length; i++) + if (manifest.versions[i].slug === slug) entry = manifest.versions[i]; + if (!entry) return null; + var newest = newestLts(manifest); + if (entry.status === 'dev') return { kind: 'dev', newest: newest }; + if (entry.status === 'eol') return { kind: 'eol', newest: newest }; + if (entry.status === 'lts' && newest && newest !== slug) + return { kind: 'newer-lts', newest: newest }; + return null; + } + + function targetUrlFor(loc, targetSlug) { + return '/' + loc.lang + '/' + targetSlug + '/' + loc.rest; + } + + /* Full navigation URL for a version switch: same path on the target version + * with the current query string + fragment re-attached, so deep links + * (?highlight=…, #section) survive the switch (§4 URL-stability contract). */ + function navUrlFor(loc, targetSlug, search, hash) { + return targetUrlFor(loc, targetSlug) + (search || '') + (hash || ''); + } + + /* ---- DOM layer (no execution at import time) ---- */ + function bannerText(b, manifest) { + if (b.kind === 'dev') return 'You are reading the development (rolling) docs.'; + if (b.kind === 'eol') return 'This VyOS version is end-of-life; these docs are frozen. See the ' + b.newest + ' (LTS) docs.'; + return 'A newer LTS (' + b.newest + ') is available.'; + } + + function init() { + var anchor = document.getElementById('vyos-version-picker'); + if (!anchor) return; + var loc = parseLocation(window.location.pathname); + if (!loc) return; + + fetch('/versions.json', { headers: { accept: 'application/json' } }) + .then(function (r) { if (!r.ok) throw new Error('versions.json ' + r.status); return r.json(); }) + .then(function (manifest) { + renderPicker(anchor, loc, manifest); + renderLang(anchor, loc, manifest); + renderBanner(loc, manifest); + }) + .catch(function () { /* silent degradation (§4) */ }); + } + + function renderPicker(anchor, loc, manifest) { + var label = document.createElement('label'); + label.setAttribute('for', 'vyos-version-select'); + label.textContent = 'Version: '; + var sel = document.createElement('select'); + sel.id = 'vyos-version-select'; + manifest.versions.forEach(function (v) { + var o = document.createElement('option'); + o.value = v.slug; o.textContent = v.label; o.selected = v.slug === loc.slug; + sel.appendChild(o); + }); + sel.addEventListener('change', function () { + var search = window.location.search, hash = window.location.hash; + var target = navUrlFor(loc, sel.value, search, hash); + var fallback = '/' + loc.lang + '/' + sel.value + '/' + (search || '') + (hash || ''); + fetch(targetUrlFor(loc, sel.value), { method: 'HEAD' }) + .then(function (r) { + window.location.href = (r.status === 404) ? fallback : target; + }) + .catch(function () { window.location.href = fallback; }); + }); + anchor.appendChild(label); + anchor.appendChild(sel); + + var entry = manifest.versions.filter(function (v) { return v.slug === loc.slug; })[0]; + if (entry && entry.pdf) { + var a = document.createElement('a'); + a.href = entry.pdf; a.className = 'vyos-pdf-link'; a.textContent = 'PDF'; + anchor.appendChild(a); + } + } + + function renderLang(anchor, loc, manifest) { + if (!manifest.languages || manifest.languages.length <= 1) return; // scaffold: hidden while en-only (§4) + var sel = document.createElement('select'); + sel.id = 'vyos-lang-select'; + sel.setAttribute('aria-label', 'Language'); + manifest.languages.forEach(function (l) { + var o = document.createElement('option'); + o.value = l.code; o.textContent = l.label; o.selected = l.code === loc.lang; + sel.appendChild(o); + }); + sel.addEventListener('change', function () { + window.location.href = '/' + sel.value + '/' + loc.slug + '/' + loc.rest + + window.location.search + window.location.hash; + }); + anchor.appendChild(sel); + } + + function renderBanner(loc, manifest) { + var b = bannerFor(loc.slug, manifest); + if (!b) return; + var key = 'vyos-banner-dismissed-' + loc.slug; + try { if (window.localStorage.getItem(key)) return; } catch (e) { /* private mode */ } + var div = document.createElement('div'); + div.className = 'vyos-version-banner vyos-banner-' + b.kind; + div.setAttribute('role', b.kind === 'eol' ? 'alert' : 'note'); + var span = document.createElement('span'); + span.textContent = bannerText(b, manifest); + div.appendChild(span); + if (b.newest && b.kind !== 'dev') { + var link = document.createElement('a'); + link.href = '/' + loc.lang + '/' + b.newest + '/'; + link.textContent = ' Switch to ' + b.newest + '.'; + div.appendChild(link); + } + var x = document.createElement('button'); + x.textContent = '×'; x.className = 'vyos-banner-dismiss'; + x.setAttribute('aria-label', 'Dismiss'); + x.addEventListener('click', function () { + try { window.localStorage.setItem(key, '1'); } catch (e) { /* ignore */ } + div.remove(); + }); + div.appendChild(x); + document.body.insertBefore(div, document.body.firstChild); + } + + window.VyOSVersionPicker = { + parseLocation: parseLocation, bannerFor: bannerFor, + targetUrlFor: targetUrlFor, navUrlFor: navUrlFor, init: init, + }; + if (typeof document !== 'undefined' && document.addEventListener) + document.addEventListener('DOMContentLoaded', init); +})(window); |
