openvibers/openvibe.shared Commits
Profile panel: a hidden [data-ov-profile-section] around a "me" panel shows with it, so a site can give your stuff a heading (#116)
Changes
@@@ -711,7 +711,8 @@ able to see your inventory anywhere"). Put a fallback link in an element and loaEvery `[data-ov-profile]` on the page is drawn once loaded; `OpenVibeProfilePanel.render(el, username)` draws one addedlater. `data-ov-profile="me"` is whoever is signed in on that site (its own `/auth/me`, the navbar's session lookup), so a--page cached for everyone can still show "your stuff": give the element `hidden` and it appears only once drawn. It reads OpenVibe.Network's public profile (`GET https://openvibe.network/api/v1/profiles/:username`, CORS++page cached for everyone can still show "your stuff": give the element `hidden` and it appears only once drawn. A++hidden ancestor marked `data-ov-profile-section` (a heading and a line around the panel) is shown with it. It reads OpenVibe.Network's public profile (`GET https://openvibe.network/api/v1/profiles/:username`, CORSopen, no credentials), which gathers OpenVibe.Inventory, OpenVibe.Quest and OpenVibe.Play. Values are text, links onlyto OpenVibe's own https hosts, pictures only from openvibe.media and openvibe.network; a private profile shows its nameand picture; no answer leaves the fallback link. `window.OpenVibeProfilePanelConfig = { api }` points it at another@@@ -8,7 +8,9 @@* OpenVibeProfilePanel.render(el, 'ana') or every [data-ov-profile] on the page, by itself, once loaded* <section data-ov-profile="me" hidden>…</section> whoever is signed in on this site (its own /auth/me, the navbar's* session lookup), so a cached page can show "your stuff"; shown-- * only once drawn, left as it was for someone signed out++ * only once drawn, left as it was for someone signed out;++ * a hidden [data-ov-profile-section] around it (a heading and++ * a line) is shown with it** It reads OpenVibe.Network's public profile (GET https://openvibe.network/api/v1/profiles/:username: profile@1, CORS* open, no credentials, cached 60 s by the Network), which gathers Inventory, Quest and Play. The panel replaces the@@@ -78,6 +80,13 @@return box;}++ /** Show the drawn panel, and the section around it that waits for it ([data-ov-profile-section], hidden until then). */++ function reveal(target) {++ target.hidden = false;++ const section = target.closest && target.closest('[data-ov-profile-section]');++ if (section) section.hidden = false;++ }++function draw(target, p) {const card = el('section', 'ovpp');card.setAttribute('aria-label', `${p.display_name || p.username} on OpenVibe`);@@@ -94,7 +103,7 @@if (p.private) {card.appendChild(el('p', 'ovpp-note', 'This profile is private.'));target.replaceChildren(card);-- target.hidden = false;++ reveal(target);return card;}const ach = p.achievements || { total: 0, games: [] };@@@ -147,7 +156,7 @@if (p.inventory_url) { foot.appendChild(root.document.createTextNode(' \u00b7 ')); foot.appendChild(link(p.inventory_url, null, 'Inventory')); }card.appendChild(foot);target.replaceChildren(card);-- target.hidden = false;++ reveal(target);return card;}@@@ -33,7 +33,7 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n<body style="margin:0;padding:16px"><div id="ana" data-ov-profile="ana"><a href="https://openvibe.network/@ana">Ana</a></div><div id="priv" data-ov-profile="hidden"><a href="https://openvibe.network/@hidden">Hidden</a></div><div id="none" data-ov-profile="nobody"><a href="https://openvibe.network/@nobody">Nobody</a></div>--<section id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></section>++<section id="stuff" data-ov-profile-section hidden><h2>Your stuff</h2><div id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></div></section><script>window.OpenVibeProfilePanelConfig = { api: location.origin };</script><script src="/shared/profile-panel.js" defer></script></body></html>`;const server = http.createServer((req, res) => {@@@ -43,7 +43,7 @@ const server = http.createServer((req, res) => {if (u.pathname === '/api/v1/profiles/ana') return json(200, { profile: ANA });// Signed in on the main page; signed out on /out (told apart by the page that asks).if (u.pathname === '/auth/me') return json(200, { user: /\/out$/.test(req.headers.referer || '') ? null : { id: 7, username: 'ana' } });-- if (u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE.replace(/<div id="ana"[\s\S]*?<\/section>/, '<section id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></section>')); }++ if (u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE.replace(/<div id="ana"[\s\S]*?<\/section>/, '<section id="stuff" data-ov-profile-section hidden><h2>Your stuff</h2><div id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></div></section>')); }if (u.pathname === '/api/v1/profiles/hidden') return json(200, { profile: { username: 'hidden', display_name: 'Hidden', profile_url: 'https://openvibe.network/@hidden', private: true } });if (u.pathname.startsWith('/api/v1/profiles/')) return json(404, { error: 'not_found' });if (u.pathname.startsWith('/avatar/')) { res.writeHead(204); return res.end(); }@@@ -75,7 +75,7 @@ const server = http.createServer((req, res) => {priv: document.querySelector('#priv .ovpp').textContent,none: document.getElementById('none').innerHTML,wide: document.documentElement.scrollWidth,-- me: [document.getElementById('me').hidden, document.querySelector('#me .ovpp-who small').textContent],++ me: [document.getElementById('me').hidden, document.querySelector('#me .ovpp-who small').textContent, document.getElementById('stuff').hidden],}));}, 100); } }, 30); })`));assert.strictEqual(got.name, 'Ana <img src=x onerror=alert(1)>', 'a name with markup stays text');@@@ -89,10 +89,10 @@ const server = http.createServer((req, res) => {assert.match(got.priv, /Hidden[\s\S]*This profile is private\./);assert.match(got.none, /href="https:\/\/openvibe\.network\/@nobody"/, 'no profile: the fallback link stays');assert.ok(got.wide <= 360, `fits a 360 px phone (${got.wide})`);-- assert.deepStrictEqual(got.me, [false, '@ana'], '"me" is whoever /auth/me names, shown once drawn');++ assert.deepStrictEqual(got.me, [false, '@ana', false], '"me" is whoever /auth/me names, shown once drawn with the section around it');await p.goto(`${base}/out`, { maxMs: 5000 });-- const out = await p.evaluate(`new Promise((r) => setTimeout(() => r(JSON.stringify([document.getElementById('me').hidden, !!document.querySelector('#me .ovpp')])), 700))`);-- assert.deepStrictEqual(JSON.parse(out), [true, false], 'signed out: "me" stays hidden and undrawn');++ const out = await p.evaluate(`new Promise((r) => setTimeout(() => r(JSON.stringify([document.getElementById('me').hidden, !!document.querySelector('#me .ovpp'), document.getElementById('stuff').hidden])), 700))`);++ assert.deepStrictEqual(JSON.parse(out), [true, false, true], 'signed out: "me" and its section stay hidden, undrawn');console.log('profile-panel: ok');} finally {await browser.close();
@@ -711,7 +711,8 @@ able to see your inventory anywhere"). Put a fallback link in an element and loa
@@ -711,7 +711,8 @@ able to see your inventory anywhere"). Put a fallback link in an element and loa
Every `[data-ov-profile]` on the page is drawn once loaded; `OpenVibeProfilePanel.render(el, username)` draws one added
Every `[data-ov-profile]` on the page is drawn once loaded; `OpenVibeProfilePanel.render(el, username)` draws one added
later. `data-ov-profile="me"` is whoever is signed in on that site (its own `/auth/me`, the navbar's session lookup), so a
later. `data-ov-profile="me"` is whoever is signed in on that site (its own `/auth/me`, the navbar's session lookup), so a
-page cached for everyone can still show "your stuff": give the element `hidden` and it appears only once drawn. It reads OpenVibe.Network's public profile (`GET https://openvibe.network/api/v1/profiles/:username`, CORS
+page cached for everyone can still show "your stuff": give the element `hidden` and it appears only once drawn. A
+hidden ancestor marked `data-ov-profile-section` (a heading and a line around the panel) is shown with it. It reads OpenVibe.Network's public profile (`GET https://openvibe.network/api/v1/profiles/:username`, CORS
open, no credentials), which gathers OpenVibe.Inventory, OpenVibe.Quest and OpenVibe.Play. Values are text, links only
open, no credentials), which gathers OpenVibe.Inventory, OpenVibe.Quest and OpenVibe.Play. Values are text, links only
to OpenVibe's own https hosts, pictures only from openvibe.media and openvibe.network; a private profile shows its name
to OpenVibe's own https hosts, pictures only from openvibe.media and openvibe.network; a private profile shows its name
and picture; no answer leaves the fallback link. `window.OpenVibeProfilePanelConfig = { api }` points it at another
and picture; no answer leaves the fallback link. `window.OpenVibeProfilePanelConfig = { api }` points it at another
@@ -8,7 +8,9 @@
@@ -8,7 +8,9 @@
* OpenVibeProfilePanel.render(el, 'ana') or every [data-ov-profile] on the page, by itself, once loaded
* OpenVibeProfilePanel.render(el, 'ana') or every [data-ov-profile] on the page, by itself, once loaded
* <section data-ov-profile="me" hidden>…</section> whoever is signed in on this site (its own /auth/me, the navbar's
* <section data-ov-profile="me" hidden>…</section> whoever is signed in on this site (its own /auth/me, the navbar's
* session lookup), so a cached page can show "your stuff"; shown
* session lookup), so a cached page can show "your stuff"; shown
- * only once drawn, left as it was for someone signed out
+ * only once drawn, left as it was for someone signed out;
+ * a hidden [data-ov-profile-section] around it (a heading and
+ * a line) is shown with it
*
*
* It reads OpenVibe.Network's public profile (GET https://openvibe.network/api/v1/profiles/:username: profile@1, CORS
* It reads OpenVibe.Network's public profile (GET https://openvibe.network/api/v1/profiles/:username: profile@1, CORS
* open, no credentials, cached 60 s by the Network), which gathers Inventory, Quest and Play. The panel replaces the
* open, no credentials, cached 60 s by the Network), which gathers Inventory, Quest and Play. The panel replaces the
@@ -78,6 +80,13 @@
@@ -78,6 +80,13 @@
return box;
return box;
}
}
+ /** Show the drawn panel, and the section around it that waits for it ([data-ov-profile-section], hidden until then). */
+ function reveal(target) {
+ target.hidden = false;
+ const section = target.closest && target.closest('[data-ov-profile-section]');
+ if (section) section.hidden = false;
+ }
+
function draw(target, p) {
function draw(target, p) {
const card = el('section', 'ovpp');
const card = el('section', 'ovpp');
card.setAttribute('aria-label', `${p.display_name || p.username} on OpenVibe`);
card.setAttribute('aria-label', `${p.display_name || p.username} on OpenVibe`);
@@ -94,7 +103,7 @@
@@ -94,7 +103,7 @@
if (p.private) {
if (p.private) {
card.appendChild(el('p', 'ovpp-note', 'This profile is private.'));
card.appendChild(el('p', 'ovpp-note', 'This profile is private.'));
target.replaceChildren(card);
target.replaceChildren(card);
- target.hidden = false;
+ reveal(target);
return card;
return card;
}
}
const ach = p.achievements || { total: 0, games: [] };
const ach = p.achievements || { total: 0, games: [] };
@@ -147,7 +156,7 @@
@@ -147,7 +156,7 @@
if (p.inventory_url) { foot.appendChild(root.document.createTextNode(' \u00b7 ')); foot.appendChild(link(p.inventory_url, null, 'Inventory')); }
if (p.inventory_url) { foot.appendChild(root.document.createTextNode(' \u00b7 ')); foot.appendChild(link(p.inventory_url, null, 'Inventory')); }
card.appendChild(foot);
card.appendChild(foot);
target.replaceChildren(card);
target.replaceChildren(card);
- target.hidden = false;
+ reveal(target);
return card;
return card;
}
}
@@ -33,7 +33,7 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
@@ -33,7 +33,7 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
<body style="margin:0;padding:16px"><div id="ana" data-ov-profile="ana"><a href="https://openvibe.network/@ana">Ana</a></div>
<body style="margin:0;padding:16px"><div id="ana" data-ov-profile="ana"><a href="https://openvibe.network/@ana">Ana</a></div>
<div id="priv" data-ov-profile="hidden"><a href="https://openvibe.network/@hidden">Hidden</a></div>
<div id="priv" data-ov-profile="hidden"><a href="https://openvibe.network/@hidden">Hidden</a></div>
<div id="none" data-ov-profile="nobody"><a href="https://openvibe.network/@nobody">Nobody</a></div>
<div id="none" data-ov-profile="nobody"><a href="https://openvibe.network/@nobody">Nobody</a></div>
-<section id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></section>
+<section id="stuff" data-ov-profile-section hidden><h2>Your stuff</h2><div id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></div></section>
<script>window.OpenVibeProfilePanelConfig = { api: location.origin };</script>
<script>window.OpenVibeProfilePanelConfig = { api: location.origin };</script>
<script src="/shared/profile-panel.js" defer></script></body></html>`;
<script src="/shared/profile-panel.js" defer></script></body></html>`;
const server = http.createServer((req, res) => {
const server = http.createServer((req, res) => {
@@ -43,7 +43,7 @@ const server = http.createServer((req, res) => {
@@ -43,7 +43,7 @@ const server = http.createServer((req, res) => {
if (u.pathname === '/api/v1/profiles/ana') return json(200, { profile: ANA });
if (u.pathname === '/api/v1/profiles/ana') return json(200, { profile: ANA });
// Signed in on the main page; signed out on /out (told apart by the page that asks).
// Signed in on the main page; signed out on /out (told apart by the page that asks).
if (u.pathname === '/auth/me') return json(200, { user: /\/out$/.test(req.headers.referer || '') ? null : { id: 7, username: 'ana' } });
if (u.pathname === '/auth/me') return json(200, { user: /\/out$/.test(req.headers.referer || '') ? null : { id: 7, username: 'ana' } });
- if (u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE.replace(/<div id="ana"[\s\S]*?<\/section>/, '<section id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></section>')); }
+ if (u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE.replace(/<div id="ana"[\s\S]*?<\/section>/, '<section id="stuff" data-ov-profile-section hidden><h2>Your stuff</h2><div id="me" data-ov-profile="me" hidden><a href="https://openvibe.network/my">Your stuff</a></div></section>')); }
if (u.pathname === '/api/v1/profiles/hidden') return json(200, { profile: { username: 'hidden', display_name: 'Hidden', profile_url: 'https://openvibe.network/@hidden', private: true } });
if (u.pathname === '/api/v1/profiles/hidden') return json(200, { profile: { username: 'hidden', display_name: 'Hidden', profile_url: 'https://openvibe.network/@hidden', private: true } });
if (u.pathname.startsWith('/api/v1/profiles/')) return json(404, { error: 'not_found' });
if (u.pathname.startsWith('/api/v1/profiles/')) return json(404, { error: 'not_found' });
if (u.pathname.startsWith('/avatar/')) { res.writeHead(204); return res.end(); }
if (u.pathname.startsWith('/avatar/')) { res.writeHead(204); return res.end(); }
@@ -75,7 +75,7 @@ const server = http.createServer((req, res) => {
@@ -75,7 +75,7 @@ const server = http.createServer((req, res) => {
priv: document.querySelector('#priv .ovpp').textContent,
priv: document.querySelector('#priv .ovpp').textContent,
none: document.getElementById('none').innerHTML,
none: document.getElementById('none').innerHTML,
wide: document.documentElement.scrollWidth,
wide: document.documentElement.scrollWidth,
- me: [document.getElementById('me').hidden, document.querySelector('#me .ovpp-who small').textContent],
+ me: [document.getElementById('me').hidden, document.querySelector('#me .ovpp-who small').textContent, document.getElementById('stuff').hidden],
}));
}));
}, 100); } }, 30); })`));
}, 100); } }, 30); })`));
assert.strictEqual(got.name, 'Ana <img src=x onerror=alert(1)>', 'a name with markup stays text');
assert.strictEqual(got.name, 'Ana <img src=x onerror=alert(1)>', 'a name with markup stays text');
@@ -89,10 +89,10 @@ const server = http.createServer((req, res) => {
@@ -89,10 +89,10 @@ const server = http.createServer((req, res) => {
assert.match(got.priv, /Hidden[\s\S]*This profile is private\./);
assert.match(got.priv, /Hidden[\s\S]*This profile is private\./);
assert.match(got.none, /href="https:\/\/openvibe\.network\/@nobody"/, 'no profile: the fallback link stays');
assert.match(got.none, /href="https:\/\/openvibe\.network\/@nobody"/, 'no profile: the fallback link stays');
assert.ok(got.wide <= 360, `fits a 360 px phone (${got.wide})`);
assert.ok(got.wide <= 360, `fits a 360 px phone (${got.wide})`);
- assert.deepStrictEqual(got.me, [false, '@ana'], '"me" is whoever /auth/me names, shown once drawn');
+ assert.deepStrictEqual(got.me, [false, '@ana', false], '"me" is whoever /auth/me names, shown once drawn with the section around it');
await p.goto(`${base}/out`, { maxMs: 5000 });
await p.goto(`${base}/out`, { maxMs: 5000 });
- const out = await p.evaluate(`new Promise((r) => setTimeout(() => r(JSON.stringify([document.getElementById('me').hidden, !!document.querySelector('#me .ovpp')])), 700))`);
- assert.deepStrictEqual(JSON.parse(out), [true, false], 'signed out: "me" stays hidden and undrawn');
+ const out = await p.evaluate(`new Promise((r) => setTimeout(() => r(JSON.stringify([document.getElementById('me').hidden, !!document.querySelector('#me .ovpp'), document.getElementById('stuff').hidden])), 700))`);
+ assert.deepStrictEqual(JSON.parse(out), [true, false, true], 'signed out: "me" and its section stay hidden, undrawn');
console.log('profile-panel: ok');
console.log('profile-panel: ok');
} finally {
} finally {
await browser.close();
await browser.close();