openvibers/openvibe.shared Commits
4.94.0: a labelled thing's poster may be named by @username (#252)
Changes
@@@ -4,6 +4,19 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking 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@@@ -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 deployedconsumer 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` namesthe tag it pins.@@@ -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)",@@@ -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">.@@@ -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 usernamelet 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; }@@@ -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": {@@@ -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 }));
@@ -4,6 +4,19 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -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.
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).
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
## 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
- **A folded footer column says how many links it holds** (from #208, 10-31). On a phone the full footer's columns are
@@ -2,7 +2,7 @@
@@ -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.
> 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
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
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.93.2",
+ "version": "4.94.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"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)",
"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)",
@@ -478,7 +478,7 @@
@@ -478,7 +478,7 @@
const NAV_ICONS_REV = 'a571e2967b16'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = 'a571e2967b16'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
// 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
// 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
// (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">.
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@ -2,7 +2,10 @@
@@ -2,7 +2,10 @@
*
*
* A page marks what carries labels: data-ov-labels="nudity,18+" on a card, a player or a picture. Optionally:
* 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-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
* 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:
* 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);
* show as it is (the web default, and what a signed-out visitor gets);
@@ -21,6 +24,7 @@
@@ -21,6 +24,7 @@
const CACHE_MS = 5 * 60 * 1000;
const CACHE_MS = 5 * 60 * 1000;
let prefs = null;
let prefs = null;
let me = null;
let me = null;
+ let meName = null; // '@name', lower-cased: a page may name the poster by username
let loading = null;
let loading = null;
const CSS = `
const CSS = `
@@ -49,10 +53,10 @@
@@ -49,10 +53,10 @@
function decide(labels, el) {
function decide(labels, el) {
if (!prefs || !labels.length) return 'show';
if (!prefs || !labels.length) return 'show';
const owner = el.getAttribute('data-ov-labels-owner');
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';
let out = 'show';
for (const l of labels) {
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';
if (prefs[l] === 'blur') out = 'blur';
}
}
return out;
return out;
@@ -115,6 +119,7 @@
@@ -115,6 +119,7 @@
const token = session && session.token;
const token = session && session.token;
const user = session && session.user;
const user = session && session.user;
me = user && (user.subject_id || user.subject || null);
me = user && (user.subject_id || user.subject || null);
+ meName = user && user.username ? `@${String(user.username).toLowerCase()}` : null;
if (!token) { prefs = null; apply(); return; }
if (!token) { prefs = null; apply(); return; }
const cached = readCache(me);
const cached = readCache(me);
if (cached) { prefs = cached; apply(); return; }
if (cached) { prefs = cached; apply(); return; }
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -24,7 +24,10 @@ const PAGE = `<div id="a" data-ov-labels="nudity"><img data-ov-labels-media src=
@@ -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="b" data-ov-labels="18+"><img src="b.jpg"></div>
<div id="c" data-ov-labels="language"><img src="c.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="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 }) {
async function page({ session, prefs }) {
// linkedom's window falls back to the process's globals: each page starts without the last one's module.
// 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 }) {
@@ -54,7 +57,7 @@ async function page({ session, prefs }) {
// Signed in: nudity blurred, 18+ hidden, language shown; their own 18+ post shows to them.
// 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 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.length, 1, 'the choices are asked once');
assert.strictEqual(calls[0].url, 'https://openvibe.network/api/v1/me/label-prefs');
assert.strictEqual(calls[0].url, 'https://openvibe.network/api/v1/me/label-prefs');
assert.strictEqual(calls[0].auth, 'Bearer tok');
assert.strictEqual(calls[0].auth, 'Bearer tok');
@@ -67,6 +70,11 @@ async function page({ session, prefs }) {
@@ -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('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('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('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');
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.
// Show: uncovered, and it stays uncovered when the page applies again.
gate.dispatchEvent(new (document.defaultView || globalThis).Event('click', { bubbles: true, cancelable: true }));
gate.dispatchEvent(new (document.defaultView || globalThis).Event('click', { bubbles: true, cancelable: true }));