openvibers/openvibe.shared Commits

4.93.2: a folded footer column says how many links it holds (#251)

OP OpenVibers committed 11 days ago · 54bdd0d85c · 6 files, +27 −11

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 8e51aeb..67256a5 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,13 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.93.2 — 2026-11-02
++
++- **A folded footer column says how many links it holds** (from #208, 10-31). On a phone the full footer's columns are
++ folds (4.53.0), and a fold showed only its heading. Each now carries its count on its line ("9 links", "1 link"), the way
++ the network's other folds say what they hold. On a desktop, where every column is open, the count is hidden. Every link stays in the
++ page.
++
## 4.93.1 — 2026-11-02
- **An `ov:` icon the ring set lacks draws as its glyph** (`showcase.icon`). Before, it drew the bare OV mark: Today's
ddiff --git a/README.md b/README.md
iindex fcbeacd..aee1df0 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.93.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.93.2 (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 8284867..4ecffac 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.93.1",
++ "version": "4.93.2",
"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 9cdc2be..5914bac 100644
---- a/footer.js
++++ b/footer.js
@@@ -281,10 +281,12 @@
function fullBody(c, { network, popular, discover, brand, tagline, compact = false }) {
// Each link column is a fold: open on a desktop (its heading reads and behaves as a heading), folded on a phone
// (a 48 px row with a chevron), so a phone's footer is about one screen shorter. Every link stays in the page.
-- const fold = (label, heading, body) => `
-- <nav class="ovf-col" aria-label="${esc(label)}"><details class="ovf-fold" open><summary><h3>${esc(heading)}</h3></summary>
++ // The fold's line carries its count ("9 links"), so a folded column still says how much it holds (#208).
++ const fold = (label, heading, body, count) => `
++ <nav class="ovf-col" aria-label="${esc(label)}"><details class="ovf-fold" open><summary><h3>${esc(heading)}</h3><span class="ovf-fold-n">${esc(String(count))} ${count === 1 ? 'link' : 'links'}</span></summary>
<div class="ovf-fold-body">${body}</div></details></nav>`;
-- const cols = (c.links || []).map(group => fold(group.heading, group.heading, group.items.map(linkTag).join(''))).join('');
++ const cols = (c.links || []).map(group => fold(group.heading, group.heading, group.items.map(linkTag).join(''), group.items.length)).join('');
++ const networkLinks = network.slice(0, 8);
return `<div class="ovf-inner${compact ? ' ovf-inner--expanded' : ''}">
${c.pulse === false ? '' : '<p class="ovf-pulse" data-ovf-pulse hidden></p>'}
@@@ -298,12 +300,12 @@
</div>
</div>
${cols}
-- ${c.showNetwork ? fold('The OpenVibe network', 'Network', `${network.slice(0, 8).map(linkTag).join('')}
-- <a class="ovf-more" href="${NETWORK_URL}" target="_blank" rel="noopener">Everything else ${icon('arrow')}</a>`) : ''}
++ ${c.showNetwork ? fold('The OpenVibe network', 'Network', `${networkLinks.map(linkTag).join('')}
++ <a class="ovf-more" href="${NETWORK_URL}" target="_blank" rel="noopener">Everything else ${icon('arrow')}</a>`, networkLinks.length + 1) : ''}
${popular.length ? fold('Popular tools', 'Popular tools', `${popular.slice(0, 6).concat(discover.slice(0, 2)).map(linkTag).join('')}
-- <a class="ovf-more" href="https://openvibe.tools/all-tools">All tools ${icon('arrow')}</a>`) : ''}
++ <a class="ovf-more" href="https://openvibe.tools/all-tools">All tools ${icon('arrow')}</a>`, Math.min(popular.length, 6) + Math.min(discover.length, 2) + 1) : ''}
${fold('Legal', 'Legal', `${LEGAL.map(l => `<a href="${esc(legalHref(l, c))}">${esc(l.name)}</a>`).join('')}
-- ${reportLink()}`)}
++ ${reportLink()}`, LEGAL.length + 1)}
</div>
${compact ? '' : `<div class="ovf-account" id="ovf-account" hidden></div>
<div class="ovf-bar">
@@@ -376,6 +378,7 @@
.ovf-fold>summary{list-style:none;cursor:default;pointer-events:none}
.ovf-fold>summary::-webkit-details-marker{display:none}
.ovf-fold-body{display:flex;flex-direction:column;gap:8px;margin-top:8px}
++.ovf-fold-n{display:none}
.ovf-col a{color:var(--text-secondary,#b6bdd2);font-size:.86rem;text-decoration:none;line-height:1.4;width:fit-content;transition:color .15s,transform .15s}
.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}
@media (pointer:coarse){.ovf-col a{display:inline-flex;align-items:center;min-height:32px}.ovf-col .ovf-social a{width:44px;height:44px}.ovf-legal-inline a,.ovf-compact-links a{position:relative}.ovf-legal-inline a::after,.ovf-compact-links a::after{content:'';position:absolute;left:0;right:0;top:-12px;bottom:-12px}}
@@@ -447,6 +450,7 @@
.ovf-col:not(.ovf-col--brand):last-child{border-bottom:1px solid var(--border,rgba(255,255,255,.08))}
.ovf-fold>summary{pointer-events:auto;cursor:pointer;display:flex;align-items:center;justify-content:space-between;min-height:48px}
.ovf-fold>summary h3{margin:0}
++ .ovf-fold-n{display:inline;margin-left:auto;margin-right:10px;color:var(--text-muted,#8b93ad);font-size:.82rem;font-weight:600;font-variant-numeric:tabular-nums}
.ovf-fold>summary::after{content:"";width:.5em;height:.5em;margin-right:4px;border:solid var(--text-muted,#8b93ad);border-width:0 1.8px 1.8px 0;transform:translateY(-.2em) rotate(45deg);transition:transform .2s}
.ovf-fold[open]>summary::after{transform:translateY(.1em) rotate(-135deg)}
.ovf-fold-body{margin-top:0;padding-bottom:14px}
ddiff --git a/package.json b/package.json
iindex 484ac8d..33d624b 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.93.1",
++ "version": "4.93.2",
"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-folds.test.js b/test/footer-folds.test.js
iindex e7752d4..5b3ea49 100644
---- a/test/footer-folds.test.js
++++ b/test/footer-folds.test.js
@@@ -15,8 +15,13 @@ const html = footer.buildHTML({ service: 'live', variant: 'full', links });
const folds = html.match(/<details class="ovf-fold"[^>]*>/g) || [];
assert.ok(folds.length >= 4, `the site's columns, Network and Legal are folds (${folds.length})`);
assert.ok(folds.every((d) => / open\b/.test(d)), 'every fold is written open');
++// Each fold's line says how many links it holds (4.93.2): a site's own columns, the network's 8 and Everything else, Legal.
++const counts = { Watch: null, Create: null, Network: 9, Legal: null };
for (const [label, heading] of [['Watch', 'Watch'], ['Create', 'Create'], ['The OpenVibe network', 'Network'], ['Legal', 'Legal']]) {
-- const re = new RegExp(`<nav class="ovf-col" aria-label="${label}"><details class="ovf-fold" open><summary><h3>${heading}</h3></summary>`);
++ const re = new RegExp(`<nav class="ovf-col" aria-label="${label}"><details class="ovf-fold" open><summary><h3>${heading}</h3><span class="ovf-fold-n">(\\d+) links?</span></summary>`);
++ const m = re.exec(html);
++ assert.ok(m && Number(m[1]) > 0, `${label}: the fold's line carries its count`);
++ if (counts[heading]) assert.strictEqual(Number(m[1]), counts[heading], `${label}: ${counts[heading]} links`);
assert.match(html, re, `${label}: a nav, its fold and its heading as the summary`);
}
for (const href of ['/browse', '/clips', '/go-live']) assert.ok(html.includes(`href="${href}"`), `${href} stays in the page`);