openvibers/openvibe.shared Commits

4.94.0: a labelled thing's poster may be named by @username (#252)

OP OpenVibers committed 11 days ago · 2c8ba7d315 · 7 files, +35 −9

* 4.94.0: a labelled thing's poster may be named by @username

* On a thing's own page, hide covers it instead of emptying the page (data-ov-labels-opened)

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 67256a5..e640242 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,19 @@ 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.94.0 — 2026-11-02
++
++- **A labelled thing's poster may be named by username** (`data-ov-labels-owner="@name"`). The owner rule (their own
++ content always shows to them) matched only a subject (`usr_…`), and a server-rendered page such as openvibe.video's
++ cards knows the maker's username, not their subject. `ov-labels.js` now also matches `@username` against the
++ signed-in person's username, in any case. A bare name is never an owner, so a username that looks like someone's
++ subject matches nothing.
++- **On a thing's own page, hide covers it instead of emptying the page** (`data-ov-labels-opened`). A watch page or a
++ picture's page is somewhere the person went on purpose; with hide chosen for one of its labels, the player vanished
++ and left a title over nothing. Marked opened, it is covered with the calm "Show" button that names the labels, as
++ blur does. Cards and lists still hide.
++- `navbar.js`'s `SIBLING_REV` moves with `ov-labels.js`.
++
## 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
ddiff --git a/README.md b/README.md
iindex aee1df0..6bd3b01 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.2 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.94.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 4ecffac..b79c6e0 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.93.2",
++ "version": "4.94.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/navbar.js b/navbar.js
iindex 90162d2..65ba114 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -478,7 +478,7 @@
const NAV_ICONS_REV = 'a571e2967b16'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
-- const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': 'bb9d0a92e0f2', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': '1e528aa9c97b' };
++ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': 'bb9d0a92e0f2', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
ddiff --git a/ov-labels.js b/ov-labels.js
iindex a9ec331..c354432 100644
---- a/ov-labels.js
++++ b/ov-labels.js
@@@ -2,7 +2,10 @@
*
* A page marks what carries labels: data-ov-labels="nudity,18+" on a card, a player or a picture. Optionally:
* data-ov-labels-media on the child to cover (the thumbnail, the <video>), else the marked element itself;
-- * data-ov-labels-owner the poster's subject (usr_…): their own content always shows to them.
++ * data-ov-labels-owner the poster, as their subject (usr_…) or as @username for a page that knows only the name:
++ * their own content always shows to them;
++ * data-ov-labels-opened on the thing's own page (a watch page, a picture's page): the person opened it themselves, so
++ * hide covers it as blur does instead of leaving the page empty.
* The navbar loads this file the first time such an element appears (navbar.js, like ov-glyphs.js). It reads the
* person's choices once from Network (GET /api/v1/me/label-prefs, network.label-prefs@1) with the navbar's token:
* show as it is (the web default, and what a signed-out visitor gets);
@@@ -21,6 +24,7 @@
const CACHE_MS = 5 * 60 * 1000;
let prefs = null;
let me = null;
++ let meName = null; // '@name', lower-cased: a page may name the poster by username
let loading = null;
const CSS = `
@@@ -49,10 +53,10 @@
function decide(labels, el) {
if (!prefs || !labels.length) return 'show';
const owner = el.getAttribute('data-ov-labels-owner');
-- if (owner && me && owner === me) return 'show';
++ if (owner && ((me && owner === me) || (meName && owner.charAt(0) === '@' && owner.toLowerCase() === meName))) return 'show';
let out = 'show';
for (const l of labels) {
-- if (prefs[l] === 'hide') return 'hide';
++ if (prefs[l] === 'hide') return el.hasAttribute('data-ov-labels-opened') ? 'blur' : 'hide';
if (prefs[l] === 'blur') out = 'blur';
}
return out;
@@@ -115,6 +119,7 @@
const token = session && session.token;
const user = session && session.user;
me = user && (user.subject_id || user.subject || null);
++ meName = user && user.username ? `@${String(user.username).toLowerCase()}` : null;
if (!token) { prefs = null; apply(); return; }
const cached = readCache(me);
if (cached) { prefs = cached; apply(); return; }
ddiff --git a/package.json b/package.json
iindex 33d624b..8f5348a 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.93.2",
++ "version": "4.94.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/labels.test.js b/test/labels.test.js
iindex fe908ee..a46377e 100644
---- a/test/labels.test.js
++++ b/test/labels.test.js
@@@ -24,7 +24,10 @@ const PAGE = `<div id="a" data-ov-labels="nudity"><img data-ov-labels-media src=
<div id="b" data-ov-labels="18+"><img src="b.jpg"></div>
<div id="c" data-ov-labels="language"><img src="c.jpg"></div>
<div id="d" data-ov-labels="18+" data-ov-labels-owner="usr_me"><img src="d.jpg"></div>
--<div id="e"><img src="e.jpg"></div>`;
++<div id="e"><img src="e.jpg"></div>
++<div id="f" data-ov-labels="18+" data-ov-labels-owner="@Me_Name"><img src="f.jpg"></div>
++<div id="g" data-ov-labels="18+" data-ov-labels-owner="me_name"><img src="g.jpg"></div>
++<div id="h" data-ov-labels="18+" data-ov-labels-opened><video src="h.mp4"></video></div>`;
async function page({ session, prefs }) {
// linkedom's window falls back to the process's globals: each page starts without the last one's module.
@@@ -54,7 +57,7 @@ async function page({ session, prefs }) {
// Signed in: nudity blurred, 18+ hidden, language shown; their own 18+ post shows to them.
{
const prefs = { nudity: 'blur', language: 'show', graphic: 'show', '18+': 'hide', flashing: 'show', spoilers: 'show' };
-- const { document, calls } = await page({ session: { token: 'tok', user: { subject_id: 'usr_me' }, apiBase: 'https://openvibe.network' }, prefs });
++ const { document, calls } = await page({ session: { token: 'tok', user: { subject_id: 'usr_me', username: 'me_name' }, apiBase: 'https://openvibe.network' }, prefs });
assert.strictEqual(calls.length, 1, 'the choices are asked once');
assert.strictEqual(calls[0].url, 'https://openvibe.network/api/v1/me/label-prefs');
assert.strictEqual(calls[0].auth, 'Bearer tok');
@@@ -67,6 +70,11 @@ async function page({ session, prefs }) {
assert.strictEqual(document.getElementById('b').getAttribute('data-ov-labels-state'), 'hide', '18+ hidden');
assert.strictEqual(document.getElementById('c').getAttribute('data-ov-labels-state'), 'show', 'language shown');
assert.strictEqual(document.getElementById('d').getAttribute('data-ov-labels-state'), 'show', 'the poster sees their own');
++ assert.strictEqual(document.getElementById('f').getAttribute('data-ov-labels-state'), 'show', 'named by @username, in any case: still their own');
++ assert.strictEqual(document.getElementById('g').getAttribute('data-ov-labels-state'), 'hide', 'a bare name is not an owner (it could be anyone\'s subject-like name)');
++ const h = document.getElementById('h');
++ assert.strictEqual(h.getAttribute('data-ov-labels-state'), 'blur', 'on its own page, hide covers instead of emptying the page');
++ assert.ok(h.querySelector('.ov-labels-gate') && h.querySelector('.ov-labels-gate').textContent.includes('18+'), 'and names why');
assert.ok(!document.getElementById('e').hasAttribute('data-ov-labels-state'), 'an unlabelled thing is untouched');
// Show: uncovered, and it stays uncovered when the page applies again.
gate.dispatchEvent(new (document.defaultView || globalThis).Event('click', { bubbles: true, cancelable: true }));