diff --git a/theme/version-picker.css b/theme/version-picker.css index e6f8075..6667a1a 100644 --- a/theme/version-picker.css +++ b/theme/version-picker.css @@ -30,6 +30,14 @@ outline: none; } +.version-picker-name { + color: var(--fg); + font-weight: 600; + padding: 0.2em 0.45em; + border: 1px solid transparent; + border-radius: 3px; +} + .version-picker-floating { position: fixed; top: 0.5em; diff --git a/theme/version-picker.js b/theme/version-picker.js index 6b0b059..bb6217b 100644 --- a/theme/version-picker.js +++ b/theme/version-picker.js @@ -1,15 +1,11 @@ // version-picker.js // -// Renders a version dropdown in the mdBook menu bar for multi-version -// deployments. Reads //versions.json at page load and lets the -// reader switch to the same page in a different version. -// -// The deployed URL layout is //, where -// is "latest" or a semver-like tag (e.g., "v0.1.0"). When the reader -// chooses a different version, the picker navigates to the matching -// page under the new version subtree; if that page doesn't exist in -// the target version, the browser will show a 404, which is fine for -// a v0.1 deployment. +// Shows the current spec version in the mdBook menu bar. On a deployed +// multi-version site (path like //) it upgrades +// into a dropdown that switches between versions. On a single-version +// or local build (file:// or no versions.json reachable) it just +// renders a static "version: " label so the reader can always +// see which version they're reading. (function () { 'use strict'; @@ -17,6 +13,10 @@ // Match the version segment in a path. Accepts "latest" or "vX[.Y[.Z]][-pre]". var VERSION_RE = /\/(latest|v\d+(?:\.\d+){0,2}(?:-[A-Za-z0-9.]+)?)(\/|$)/; + function labelFor(version) { + return (version === 'latest') ? 'latest (main)' : version; + } + function parseLocation() { var path = window.location.pathname; var m = path.match(VERSION_RE); @@ -28,16 +28,35 @@ return { version: version, basePath: basePath, pagePath: pagePath }; } - function buildPicker(versions, current, basePath, pagePath) { + function buildLabel(version) { + var wrap = document.createElement('div'); + wrap.className = 'version-picker version-picker-label-only'; + wrap.setAttribute('role', 'group'); + wrap.setAttribute('aria-label', 'Spec version'); + + var prefix = document.createElement('span'); + prefix.className = 'version-picker-label'; + prefix.textContent = 'version:'; + wrap.appendChild(prefix); + + var name = document.createElement('span'); + name.className = 'version-picker-name'; + name.textContent = labelFor(version); + wrap.appendChild(name); + + return wrap; + } + + function buildDropdown(versions, current, basePath, pagePath) { var wrap = document.createElement('div'); wrap.className = 'version-picker'; wrap.setAttribute('role', 'group'); wrap.setAttribute('aria-label', 'Spec version'); - var label = document.createElement('span'); - label.className = 'version-picker-label'; - label.textContent = 'version:'; - wrap.appendChild(label); + var prefix = document.createElement('span'); + prefix.className = 'version-picker-label'; + prefix.textContent = 'version:'; + wrap.appendChild(prefix); var select = document.createElement('select'); select.className = 'version-picker-select'; @@ -47,7 +66,7 @@ var v = versions[i]; var opt = document.createElement('option'); opt.value = v; - opt.textContent = (v === 'latest') ? 'latest (main)' : v; + opt.textContent = labelFor(v); if (v === current) opt.selected = true; select.appendChild(opt); } @@ -62,8 +81,29 @@ return wrap; } + function mount(picker) { + var slot = document.querySelector('.right-buttons'); + if (slot) { + slot.insertBefore(picker, slot.firstChild); + } else { + picker.classList.add('version-picker-floating'); + document.body.appendChild(picker); + } + } + function install() { var loc = parseLocation(); + var current = loc ? loc.version : 'latest'; + + // Always mount the label first — visible immediately, even before + // (or without) a versions.json fetch. + var picker = buildLabel(current); + mount(picker); + + // Only attempt the upgrade-to-dropdown path when we have a real + // version segment in the URL. Without one (e.g., file:// or local + // `just build` opened directly), we can't compute navigation + // targets, so leave the label as-is. if (!loc) return; fetch(loc.basePath + 'versions.json', { cache: 'no-cache' }) @@ -74,21 +114,13 @@ // ["latest", "v0.1.0"] // { "versions": ["latest", "v0.1.0"] } var versions = Array.isArray(data) ? data : (data && data.versions); - if (!versions || !versions.length) return; + if (!versions || versions.length < 2) return; - var picker = buildPicker(versions, loc.version, loc.basePath, loc.pagePath); - - // Prefer slotting into mdBook's menu bar; fall back to floating. - var slot = document.querySelector('.right-buttons'); - if (slot) { - slot.insertBefore(picker, slot.firstChild); - } else { - picker.classList.add('version-picker-floating'); - document.body.appendChild(picker); - } + var dropdown = buildDropdown(versions, current, loc.basePath, loc.pagePath); + picker.replaceWith(dropdown); }) .catch(function (err) { - // Non-fatal: dev / file:// / pre-deploy will all land here. + // Non-fatal: keep the label-only widget mounted. if (window.console) console.warn('version-picker:', err); }); }