openvibers/openvibe.shared Commits

4.19.0: footer variant 'auto': the full footer on a site's front page (and fullOn), the one quiet line everywhere else, on the server (ssr path) and in the browser (#142)

OP OpenVibers committed 17 days ago · 4db26c7384 · 6 files, +83 −8

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex ec062de..223fb47 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,18 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
## Unreleased
++## 4.19.0 — 2026-10-28
++
++- **Footer `variant: 'auto'`** (`footer.js`). The owner, 2026-10-28: "the full-size footer on the chat pages might be a
++ little much … if we want to have that app-like design … make it a little more sleeker across sites".
++ - It shows the full footer on the site's front page (and the paths in `fullOn`, default `['/']`), and the one quiet
++ line (`compact`, with "More from OpenVibe" to open the rest) everywhere else.
++ - On the server, `ssr({ variant: 'auto', path })` picks from the request's path; with no path it is compact.
++ - In the browser it reads `location.pathname`, follows back and forward on a single-page site, and
++ `setVariant('auto')` is a variant like the others.
++ - A claim still wins over it (Live's channel while live).
++ - `autoFor(path, fullOn)` is exported.
++
## 4.18.0 — 2026-10-28
- **The account menu, reviewed** (`navbar.js`). The owner, 2026-10-28: "duplicated things … could just be organized
ddiff --git a/README.md b/README.md
iindex bfbd9c9..dbf6a3d 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.18.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.19.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex 69d9d0c..2ec2e94 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.18.0",
++ "version": "4.19.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/footer.js b/footer.js
iindex 0f55046..95da71c 100644
---- a/footer.js
++++ b/footer.js
@@@ -2,7 +2,7 @@
* openvibe-shared/footer.js — the cross-site footer, the counterpart to navbar.js.
*
* OpenVibeFooter.init({ service, variant, links, apiBase, mount, pulse })
-- * OpenVibeFooter.setVariant('full' | 'compact') the page's own variant (SPA route changes)
++ * OpenVibeFooter.setVariant('full' | 'compact' | 'auto') the page's own variant (SPA route changes)
* OpenVibeFooter.claim('compact' | 'full', key) → release(): a part of the page asks for a variant while it needs
* it (Live's channel while its stream is live: compact; offline:
* the page's own full). Compact wins over full, any claim over the
@@@ -16,6 +16,10 @@
* property or changing a policy link updates every site at once.
*
* Variants:
++ * auto full on the site's front page (and the paths in fullOn), compact everywhere else: a site looks like an app
++ * once someone is in it (the owner, 2026-10-28: "the full-size footer on the chat pages might be a little
++ * much … if we want to have that app-like design … make it a little more sleeker across sites"). On the
++ * server, ssr({ variant: 'auto', path }) picks with the request's path (no path: compact).
* full the multi-column footer, for home and browse pages
* compact one quiet line, for pages where the footer is not the point — a streamer's
* channel, a player, a tool someone is mid-task in. "More from OpenVibe" opens the
@@@ -126,7 +130,8 @@
service: null, // 'live' | 'tools' | 'dev' | … (auto-detected when omitted)
brandName: null,
tagline: 'Open source and community run. Built in the open by the people using it.',
-- variant: 'full',
++ variant: 'full', // 'full' | 'compact' | 'auto'
++ fullOn: ['/'], // with variant 'auto': the paths that get the full footer
mount: '#ov-footer',
links: [], // [{ heading, items: [{ name, url, onclick? }] }]
legalBase: null, // origin that hosts /tos, /privacy, /dmca for this site
@@@ -143,11 +148,19 @@
let _cfg = { ...DEFAULTS };
let _mounted = null;
const _claims = new Map(); // key → 'compact' | 'full' (claim())
-- /** The variant shown: a compact claim, else a full one, else the page's own. */
++ /** 'auto' for a path: full on the front page and the paths in fullOn, compact elsewhere (no path: compact). */
++ function autoFor(path, fullOn) {
++ if (typeof path !== 'string' || !path) return 'compact';
++ const p = path.replace(/[?#].*$/, '').replace(/(.)\/+$/, '$1') || '/';
++ return (Array.isArray(fullOn) && fullOn.length ? fullOn : ['/']).includes(p) ? 'full' : 'compact';
++ }
++ const herePath = () => (typeof location !== 'undefined' && location.pathname) || '/';
++ /** The variant shown: a compact claim, else a full one, else the page's own ('auto' read from where the page is). */
function shownVariant(base) {
const asked = [..._claims.values()];
if (asked.includes('compact')) return 'compact';
if (asked.includes('full')) return 'full';
++ if (base === 'auto') return autoFor(herePath(), _cfg.fullOn);
return base === 'compact' ? 'compact' : 'full';
}
@@@ -549,12 +562,19 @@
if (!el) return null;
_mounted = el;
render();
++ // 'auto' on a single-page site follows its routes: back and forward re-read the path (a route the site pushes
++ // itself calls setVariant or render).
++ if (_cfg.variant === 'auto' && !_routeWatch && typeof window !== 'undefined' && window.addEventListener) {
++ _routeWatch = true;
++ window.addEventListener('popstate', () => { if (_cfg.variant === 'auto' && _mounted) render(); });
++ }
return el;
}
++ let _routeWatch = false;
/** The page's own variant (an SPA route's); a claim still wins while it holds. */
function setVariant(variant) {
-- if (variant !== 'full' && variant !== 'compact') return;
++ if (variant !== 'full' && variant !== 'compact' && variant !== 'auto') return;
if (_cfg.variant === variant) return;
const before = shownVariant(_cfg.variant);
_cfg.variant = variant;
@@@ -587,9 +607,10 @@
*/
function ssr(cfg = {}) {
const c = { ...DEFAULTS, ...cfg };
++ if (c.variant === 'auto') c.variant = autoFor(cfg.path, c.fullOn); // the request's path, on the server
const id = String(cfg.mountId || 'ov-footer').replace(/[^\w-]/g, '');
return `<style id="ovf-css">${CSS}</style><footer id="${id}" class="ovf" data-variant="${c.variant === 'compact' ? 'compact' : 'full'}">${buildHTML(c)}</footer>`;
}
-- return { init, ssr, setVariant, claim, buildHTML, render, CSS, NETWORK, LEGAL, detectService, pulseHTML, get config() { return { ..._cfg }; }, get variant() { return shownVariant(_cfg.variant); } };
++ return { init, ssr, setVariant, claim, buildHTML, render, CSS, NETWORK, LEGAL, detectService, pulseHTML, autoFor, get config() { return { ..._cfg }; }, get variant() { return shownVariant(_cfg.variant); } };
});
ddiff --git a/package.json b/package.json
iindex c3ea672..2f37ed6 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.18.0",
++ "version": "4.19.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/test/footer-auto.test.js b/test/footer-auto.test.js
nnew file mode 100644
iindex 0000000..5403b34
---- /dev/null
++++ b/test/footer-auto.test.js
@@@ -0,0 +1,42 @@
++'use strict';
++// The footer's 'auto' variant (footer.js; the owner, 2026-10-28: "the full-size footer on the chat pages might be a
++// little much … if we want to have that app-like design … make it a little more sleeker across sites"): the full footer
++// on a site's front page (and the paths it names in fullOn), the one quiet line everywhere else, on the server as in the
++// browser, and a claim still wins.
++const assert = require('assert');
++const footer = require('../footer');
++
++assert.strictEqual(footer.autoFor('/', ['/']), 'full');
++assert.strictEqual(footer.autoFor('/?ref=x', ['/']), 'full', 'a query is not another page');
++assert.strictEqual(footer.autoFor('/r/staff/tasks/1', ['/']), 'compact');
++assert.strictEqual(footer.autoFor('/updates/', ['/', '/updates']), 'full', 'a trailing slash is the same path');
++assert.strictEqual(footer.autoFor('/updates', undefined), 'compact', 'fullOn defaults to the front page');
++assert.strictEqual(footer.autoFor(undefined, ['/']), 'compact', 'no path on the server: compact');
++
++const ssr = (cfg) => (/class="ovf" data-variant="(\w+)"/.exec(footer.ssr({ service: 'chat', ...cfg })) || [])[1];
++assert.strictEqual(ssr({ variant: 'auto', path: '/' }), 'full');
++assert.strictEqual(ssr({ variant: 'auto', path: '/messages/12' }), 'compact');
++assert.strictEqual(ssr({ variant: 'auto', path: '/developers', fullOn: ['/', '/developers'] }), 'full');
++assert.strictEqual(ssr({ variant: 'full', path: '/messages/12' }), 'full', 'full stays full');
++assert.strictEqual(ssr({ variant: 'compact', path: '/' }), 'compact', 'compact stays compact');
++
++// In the browser: the page's path decides, a claim wins over it, and setVariant('auto') is a variant like the others
++// (unmounted here: the variant is what a render would draw).
++global.location = { pathname: '/', hostname: 'openvibe.chat' };
++footer.setVariant('auto');
++assert.strictEqual(footer.variant, 'full', 'the front page');
++global.location.pathname = '/r/staff';
++assert.strictEqual(footer.variant, 'compact', 'a room');
++const release = footer.claim('full', 'test');
++assert.strictEqual(footer.variant, 'full', 'a claim wins');
++release();
++assert.strictEqual(footer.variant, 'compact');
++footer.setVariant('full');
++assert.strictEqual(footer.variant, 'full');
++footer.setVariant('nonsense');
++assert.strictEqual(footer.variant, 'full', 'an unknown variant changes nothing');
++footer.setVariant('auto');
++assert.strictEqual(footer.variant, 'compact');
++delete global.location;
++
++console.log('footer-auto: all checks passed');