openvibers/openvibe.shared Commits
4.155.0: the menu names each site by a short product name, never a tagline (#348)
Changes
@@@ -1,5 +1,18 @@# Changelog++## 4.155.0 — 2026-11-08++++- **The menu names each site by a short product name** (the owner, 11-08: "the names of websites … not cut off …++ cleaner and nicer across all the websites"). On openvibe.website the first tab read "Websites that l…", the box++ "Search Websites that look finished, or a…" and the heading "On Websites that look finished": the page title's last++ part, a tagline. The name now comes, in order, from the site's own short label, the network's name for the address,++ the "OpenVibe.X" in the title, then the address itself (openvibe.website → Website, openvibe.party → Party), and a++ title part only when it is short. Website, My, Link, Party and Computer read right now; every other site keeps its name.++- **A site under a path is that site only on that path:** Inventory lives at openvibe.my/inventory, so openvibe.my++ itself is My, not Inventory (the match was by host alone).++- Tests: `test/search-site-name-chrome.test.js` (a tagline title, a long label, and a path-aware site; it fails on++ 4.154.0 with the owner's exact text).++## 4.154.0 — 2026-11-08- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab@@@ -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.154.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.155.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.154.0",++ "version": "4.155.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)",@@@ -539,7 +539,7 @@const NAV_ICONS_REV = 'd9a4febbbad7'; // 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': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };++ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'b2e5ef9b3773', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };// 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">.@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.154.0",++ "version": "4.155.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -112,9 +112,18 @@return FALLBACK_SITES;}/** The site this page belongs to: the longest matching host (play.openvibe.network is Play, not Network). */++ // The site this page is on: the longest matching address, and a site that lives under a path (openvibe.my/inventory)++ // only on that path.function hereSite(list) {-- const h = location.hostname.toLowerCase(); let best = null, len = 0;-- for (const s of list) { const sh = hostOf(s.url); if (sh && (h === sh || h.endsWith(`.${sh}`)) && sh.length > len) { best = s; len = sh.length; } }++ const h = location.hostname.toLowerCase(), at = location.pathname || '/'; let best = null, score = 0;++ for (const s of list) {++ let u; try { u = new URL(s.url); } catch { continue; }++ const sh = u.hostname, sp = u.pathname.replace(/\/+$/, '');++ if (!sh || !(h === sh || h.endsWith(`.${sh}`))) continue;++ if (sp && at !== sp && !at.startsWith(`${sp}/`)) continue;++ const n = sh.length * 1000 + sp.length + 1;++ if (n > score) { best = s; score = n; }++ }return best;}/** The person's own use of each site (their history's counts), for "your sites" and for ranking results. */@@@ -678,14 +687,27 @@if (typeof ctx.icons === 'function') { try { ctx.icons(listEl); } catch { /* the icons are optional */ } }}-- // This site's short name for the field and the headings: the frame's name for this address ("Download"), not the page-- // title ("OpenVibe.Download: a free 25 GB drive with share links" was cut off in the field and ran over two lines).-- const shortName = (t) => String(t || '').split(/\s[·|–—-]\s|:\s/)[0].replace(/^OpenVibe\./, '').trim().slice(0, 28);++ // This site's short name for the field, the first tab and the headings: a product name ("Download", "Space"), never a++ // page title or a tagline ("OpenVibe.Download: a free 25 GB drive with share links" ran over two lines; on 11-08 the++ // tab read "Websites that l…", openvibe.website's tagline). In order: the site's own short label, the network's name++ // for this address (path-aware: openvibe.my/inventory is Inventory, openvibe.my itself is not), the "OpenVibe.X" in++ // the title, the address (openvibe.website → Website, connect.openvibe.link → Connect), then a short title segment.++ const SHORT_NAME = 18;++ const shortName = (t) => String(t || '').split(/\s[·|–—-]\s|:\s/)[0].replace(/^OpenVibe\./, '').trim();++ const brief = (t) => { const v = shortName(t); return v && v.length <= SHORT_NAME ? v : ''; };++ const capital = (w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : '');++ function addressName(host) {++ const p = String(host || '').toLowerCase().replace(/^www\./, '').split('.');++ if (p.length === 2 && p[0] === 'openvibe') return p[1] === 'ai' ? 'AI' : capital(p[1]);++ if (p.length >= 3 && p[p.length - 2] === 'openvibe' && /^[a-z][a-z-]{1,17}$/.test(p[0])) return capital(p[0]);++ return '';++ }const here = () => {-- if (ctx.scope && ctx.scope.label) return shortName(ctx.scope.label);const f = ctx.frame, list = f && Array.isArray(f.nav) ? f.nav.map((n) => ({ name: n.name, url: safeUrl(n.url) })) : [];const s = list.length ? hereSite(list) : null;-- return (s && s.name) || shortName(document.title.split(/[·|–-]/).pop()) || location.hostname;++ const titled = /OpenVibe\.([A-Za-z][A-Za-z0-9-]{0,17})\b/.exec(document.title || '');++ return brief(ctx.scope && ctx.scope.label) || (s && brief(s.name)) || (titled && titled[1]) || addressName(location.hostname)++ || brief(String(document.title || '').split(/\s[·|–—-]\s/).pop()) || location.hostname;};const siteItem = (s, all) => ({ title: s.name, meta: s.desc, url: s.url, site: s.icon || s.id, kind: 'site', here: all && s === hereSite(all) });function pagesOf(q) {nnew file mode 100644@@@ -0,0 +1,82 @@++'use strict';++// The menu names the site it is on by a short product name (4.155.0). On 11-08 the owner sent openvibe.website's menu:++// its first tab read "Websites that l…" and the box "Search Websites that look finished, or a…", the site's tagline++// taken from the page title. Now: the site's own short label, the network's name for the address (a site under a path,++// like openvibe.my/inventory, only on that path), the "OpenVibe.X" in the title, then the address; a tagline never.++// In a real headless Chrome; skipped without one.++const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++const http = require('http');++const h = require('../browser-harness');++++if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {++ console.log('search-site-name-chrome: skipped (no Chrome; set CHROME_BIN)');++ process.exit(0);++}++++const ROOT = path.join(__dirname, '..');++const page = (title, search) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title}</title><link rel="icon" href="data:,"></head>++<body><div id="navbar-mount"></div><main></main><script src="/navbar.js"></script>++<script>++ window.__realFetch = window.fetch;++ window.fetch = async (u, init) => {++ const url = new URL(String(u), location.href);++ // The network's sites (https only, as the navbar keeps them): an account hub at localhost's root and Inventory under++ // /inventory there. The other pages are on 127.0.0.1, an address the list does not know.++ if (url.hostname === 'openvibe.network' && url.pathname === '/api/frame') return new Response(JSON.stringify({++ nav: [{ name: 'My', url: 'https://localhost/' }, { name: 'Inventory', url: 'https://localhost/inventory' }] }), { headers: { 'content-type': 'application/json' } });++ if (url.hostname === 'openvibe.network' || url.hostname === 'search.openvibe.network') return new Response('{}', { status: 404 });++ return window.__realFetch(u, init);++ };++ OpenVibeNavbar.init({ service: 'test', user: null, links: [{ label: 'Home', href: '/' }], search: ${JSON.stringify(search || {})} });++</script></body></html>`;++const PAGES = {++ '/website': page('OpenVibe.Website · Websites that look finished'),++ '/long-label': page('Templates · OpenVibe.Space', { label: 'Websites that look finished, from the first preview' }),++ '/inventory/items': page('Your items'),++ '/account': page('Account settings and everything else you own'),++};++const server = http.createServer((req, res) => {++ const u = new URL(req.url, 'http://x');++ if (PAGES[u.pathname]) { res.writeHead(200, { 'content-type': 'text/html' }); return res.end(PAGES[u.pathname]); }++ const name = u.pathname.slice(1);++ if (/^[a-z-]+\.js$/.test(name) && fs.existsSync(path.join(ROOT, name))) { res.writeHead(200, { 'content-type': 'application/javascript' }); return res.end(fs.readFileSync(path.join(ROOT, name))); }++ res.writeHead(404); return res.end();++});++++(async () => {++ await new Promise((ok) => server.listen(0, '127.0.0.1', ok));++ const base = `http://127.0.0.1:${server.address().port}`;++ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-search-name-'));++ const browser = await h.launch({ tmpDir });++ const wait = (ms) => new Promise((r) => setTimeout(r, ms));++ const seen = {};++ try {++ for (const at of Object.keys(PAGES)) {++ const context = await browser.newContext();++ const p = await h.openPage(browser, context, { width: 390 });++ const origin = /^\/(inventory|account)/.test(at) ? base.replace('127.0.0.1', 'localhost') : base;++ await p.goto(`${origin}${at}`, { maxMs: 5000 });++ await wait(300);++ await p.evaluate('document.dispatchEvent(new KeyboardEvent("keydown", { key: "/", bubbles: true })); 1');++ for (let i = 0; i < 50 && !(await p.evaluate('!!document.querySelector(".ovs.is-open .ovs-input")')); i++) await wait(100);++ await wait(300);++ seen[at] = await p.evaluate(`({ placeholder: document.querySelector('.ovs-input').placeholder,++ tab: (document.querySelector('.ovs-tab[data-tab]') || {}).textContent || '',++ group: ((document.querySelector('.ovs-group') || {}).firstChild || {}).textContent || '' })`);++ }++ const name = (at) => seen[at].placeholder.replace(/^Search /, '').replace(/, or ask Vibe$/, '');++ assert.strictEqual(name('/website'), 'Website', `a tagline title gives the OpenVibe.X name: ${JSON.stringify(seen['/website'])}`);++ assert.ok(!/look finished/.test(seen['/website'].tab + seen['/website'].group), 'the tagline is nowhere in the tab or the heading');++ assert.strictEqual(name('/long-label'), 'Space', 'a long label is passed over for the name in the title');++ assert.strictEqual(name('/inventory/items'), 'Inventory', 'under /inventory the site is Inventory');++ assert.strictEqual(name('/account'), 'My', 'at the root of the same address it is not Inventory');++ for (const at of Object.keys(seen)) assert.ok(name(at).length <= 18, `${at}: a short name (${name(at)})`);++ console.log('search-site-name-chrome: short product names (tagline title, long label, path-aware sites) ok');++ } finally {++ await browser.close();++ server.close();++ }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -1,5 +1,18 @@
@@ -1,5 +1,18 @@
# Changelog
# Changelog
+## 4.155.0 — 2026-11-08
+
+- **The menu names each site by a short product name** (the owner, 11-08: "the names of websites … not cut off …
+ cleaner and nicer across all the websites"). On openvibe.website the first tab read "Websites that l…", the box
+ "Search Websites that look finished, or a…" and the heading "On Websites that look finished": the page title's last
+ part, a tagline. The name now comes, in order, from the site's own short label, the network's name for the address,
+ the "OpenVibe.X" in the title, then the address itself (openvibe.website → Website, openvibe.party → Party), and a
+ title part only when it is short. Website, My, Link, Party and Computer read right now; every other site keeps its name.
+- **A site under a path is that site only on that path:** Inventory lives at openvibe.my/inventory, so openvibe.my
+ itself is My, not Inventory (the match was by host alone).
+- Tests: `test/search-site-name-chrome.test.js` (a tagline title, a long label, and a path-aware site; it fails on
+ 4.154.0 with the owner's exact text).
+
## 4.154.0 — 2026-11-08
## 4.154.0 — 2026-11-08
- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab
- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab
@@ -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.154.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.155.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.154.0",
+ "version": "4.155.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)",
@@ -539,7 +539,7 @@
@@ -539,7 +539,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = 'd9a4febbbad7'; // 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': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };
+ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'b2e5ef9b3773', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };
// 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">.
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.154.0",
+ "version": "4.155.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": {
@@ -112,9 +112,18 @@
@@ -112,9 +112,18 @@
return FALLBACK_SITES;
return FALLBACK_SITES;
}
}
/** The site this page belongs to: the longest matching host (play.openvibe.network is Play, not Network). */
/** The site this page belongs to: the longest matching host (play.openvibe.network is Play, not Network). */
+ // The site this page is on: the longest matching address, and a site that lives under a path (openvibe.my/inventory)
+ // only on that path.
function hereSite(list) {
function hereSite(list) {
- const h = location.hostname.toLowerCase(); let best = null, len = 0;
- for (const s of list) { const sh = hostOf(s.url); if (sh && (h === sh || h.endsWith(`.${sh}`)) && sh.length > len) { best = s; len = sh.length; } }
+ const h = location.hostname.toLowerCase(), at = location.pathname || '/'; let best = null, score = 0;
+ for (const s of list) {
+ let u; try { u = new URL(s.url); } catch { continue; }
+ const sh = u.hostname, sp = u.pathname.replace(/\/+$/, '');
+ if (!sh || !(h === sh || h.endsWith(`.${sh}`))) continue;
+ if (sp && at !== sp && !at.startsWith(`${sp}/`)) continue;
+ const n = sh.length * 1000 + sp.length + 1;
+ if (n > score) { best = s; score = n; }
+ }
return best;
return best;
}
}
/** The person's own use of each site (their history's counts), for "your sites" and for ranking results. */
/** The person's own use of each site (their history's counts), for "your sites" and for ranking results. */
@@ -678,14 +687,27 @@
@@ -678,14 +687,27 @@
if (typeof ctx.icons === 'function') { try { ctx.icons(listEl); } catch { /* the icons are optional */ } }
if (typeof ctx.icons === 'function') { try { ctx.icons(listEl); } catch { /* the icons are optional */ } }
}
}
- // This site's short name for the field and the headings: the frame's name for this address ("Download"), not the page
- // title ("OpenVibe.Download: a free 25 GB drive with share links" was cut off in the field and ran over two lines).
- const shortName = (t) => String(t || '').split(/\s[·|–—-]\s|:\s/)[0].replace(/^OpenVibe\./, '').trim().slice(0, 28);
+ // This site's short name for the field, the first tab and the headings: a product name ("Download", "Space"), never a
+ // page title or a tagline ("OpenVibe.Download: a free 25 GB drive with share links" ran over two lines; on 11-08 the
+ // tab read "Websites that l…", openvibe.website's tagline). In order: the site's own short label, the network's name
+ // for this address (path-aware: openvibe.my/inventory is Inventory, openvibe.my itself is not), the "OpenVibe.X" in
+ // the title, the address (openvibe.website → Website, connect.openvibe.link → Connect), then a short title segment.
+ const SHORT_NAME = 18;
+ const shortName = (t) => String(t || '').split(/\s[·|–—-]\s|:\s/)[0].replace(/^OpenVibe\./, '').trim();
+ const brief = (t) => { const v = shortName(t); return v && v.length <= SHORT_NAME ? v : ''; };
+ const capital = (w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : '');
+ function addressName(host) {
+ const p = String(host || '').toLowerCase().replace(/^www\./, '').split('.');
+ if (p.length === 2 && p[0] === 'openvibe') return p[1] === 'ai' ? 'AI' : capital(p[1]);
+ if (p.length >= 3 && p[p.length - 2] === 'openvibe' && /^[a-z][a-z-]{1,17}$/.test(p[0])) return capital(p[0]);
+ return '';
+ }
const here = () => {
const here = () => {
- if (ctx.scope && ctx.scope.label) return shortName(ctx.scope.label);
const f = ctx.frame, list = f && Array.isArray(f.nav) ? f.nav.map((n) => ({ name: n.name, url: safeUrl(n.url) })) : [];
const f = ctx.frame, list = f && Array.isArray(f.nav) ? f.nav.map((n) => ({ name: n.name, url: safeUrl(n.url) })) : [];
const s = list.length ? hereSite(list) : null;
const s = list.length ? hereSite(list) : null;
- return (s && s.name) || shortName(document.title.split(/[·|–-]/).pop()) || location.hostname;
+ const titled = /OpenVibe\.([A-Za-z][A-Za-z0-9-]{0,17})\b/.exec(document.title || '');
+ return brief(ctx.scope && ctx.scope.label) || (s && brief(s.name)) || (titled && titled[1]) || addressName(location.hostname)
+ || brief(String(document.title || '').split(/\s[·|–—-]\s/).pop()) || location.hostname;
};
};
const siteItem = (s, all) => ({ title: s.name, meta: s.desc, url: s.url, site: s.icon || s.id, kind: 'site', here: all && s === hereSite(all) });
const siteItem = (s, all) => ({ title: s.name, meta: s.desc, url: s.url, site: s.icon || s.id, kind: 'site', here: all && s === hereSite(all) });
function pagesOf(q) {
function pagesOf(q) {
new file mode 100644
new file mode 100644
@@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
+'use strict';
+// The menu names the site it is on by a short product name (4.155.0). On 11-08 the owner sent openvibe.website's menu:
+// its first tab read "Websites that l…" and the box "Search Websites that look finished, or a…", the site's tagline
+// taken from the page title. Now: the site's own short label, the network's name for the address (a site under a path,
+// like openvibe.my/inventory, only on that path), the "OpenVibe.X" in the title, then the address; a tagline never.
+// In a real headless Chrome; skipped without one.
+const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const http = require('http');
+const h = require('../browser-harness');
+
+if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
+ console.log('search-site-name-chrome: skipped (no Chrome; set CHROME_BIN)');
+ process.exit(0);
+}
+
+const ROOT = path.join(__dirname, '..');
+const page = (title, search) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title}</title><link rel="icon" href="data:,"></head>
+<body><div id="navbar-mount"></div><main></main><script src="/navbar.js"></script>
+<script>
+ window.__realFetch = window.fetch;
+ window.fetch = async (u, init) => {
+ const url = new URL(String(u), location.href);
+ // The network's sites (https only, as the navbar keeps them): an account hub at localhost's root and Inventory under
+ // /inventory there. The other pages are on 127.0.0.1, an address the list does not know.
+ if (url.hostname === 'openvibe.network' && url.pathname === '/api/frame') return new Response(JSON.stringify({
+ nav: [{ name: 'My', url: 'https://localhost/' }, { name: 'Inventory', url: 'https://localhost/inventory' }] }), { headers: { 'content-type': 'application/json' } });
+ if (url.hostname === 'openvibe.network' || url.hostname === 'search.openvibe.network') return new Response('{}', { status: 404 });
+ return window.__realFetch(u, init);
+ };
+ OpenVibeNavbar.init({ service: 'test', user: null, links: [{ label: 'Home', href: '/' }], search: ${JSON.stringify(search || {})} });
+</script></body></html>`;
+const PAGES = {
+ '/website': page('OpenVibe.Website · Websites that look finished'),
+ '/long-label': page('Templates · OpenVibe.Space', { label: 'Websites that look finished, from the first preview' }),
+ '/inventory/items': page('Your items'),
+ '/account': page('Account settings and everything else you own'),
+};
+const server = http.createServer((req, res) => {
+ const u = new URL(req.url, 'http://x');
+ if (PAGES[u.pathname]) { res.writeHead(200, { 'content-type': 'text/html' }); return res.end(PAGES[u.pathname]); }
+ const name = u.pathname.slice(1);
+ if (/^[a-z-]+\.js$/.test(name) && fs.existsSync(path.join(ROOT, name))) { res.writeHead(200, { 'content-type': 'application/javascript' }); return res.end(fs.readFileSync(path.join(ROOT, name))); }
+ res.writeHead(404); return res.end();
+});
+
+(async () => {
+ await new Promise((ok) => server.listen(0, '127.0.0.1', ok));
+ const base = `http://127.0.0.1:${server.address().port}`;
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-search-name-'));
+ const browser = await h.launch({ tmpDir });
+ const wait = (ms) => new Promise((r) => setTimeout(r, ms));
+ const seen = {};
+ try {
+ for (const at of Object.keys(PAGES)) {
+ const context = await browser.newContext();
+ const p = await h.openPage(browser, context, { width: 390 });
+ const origin = /^\/(inventory|account)/.test(at) ? base.replace('127.0.0.1', 'localhost') : base;
+ await p.goto(`${origin}${at}`, { maxMs: 5000 });
+ await wait(300);
+ await p.evaluate('document.dispatchEvent(new KeyboardEvent("keydown", { key: "/", bubbles: true })); 1');
+ for (let i = 0; i < 50 && !(await p.evaluate('!!document.querySelector(".ovs.is-open .ovs-input")')); i++) await wait(100);
+ await wait(300);
+ seen[at] = await p.evaluate(`({ placeholder: document.querySelector('.ovs-input').placeholder,
+ tab: (document.querySelector('.ovs-tab[data-tab]') || {}).textContent || '',
+ group: ((document.querySelector('.ovs-group') || {}).firstChild || {}).textContent || '' })`);
+ }
+ const name = (at) => seen[at].placeholder.replace(/^Search /, '').replace(/, or ask Vibe$/, '');
+ assert.strictEqual(name('/website'), 'Website', `a tagline title gives the OpenVibe.X name: ${JSON.stringify(seen['/website'])}`);
+ assert.ok(!/look finished/.test(seen['/website'].tab + seen['/website'].group), 'the tagline is nowhere in the tab or the heading');
+ assert.strictEqual(name('/long-label'), 'Space', 'a long label is passed over for the name in the title');
+ assert.strictEqual(name('/inventory/items'), 'Inventory', 'under /inventory the site is Inventory');
+ assert.strictEqual(name('/account'), 'My', 'at the root of the same address it is not Inventory');
+ for (const at of Object.keys(seen)) assert.ok(name(at).length <= 18, `${at}: a short name (${name(at)})`);
+ console.log('search-site-name-chrome: short product names (tagline title, long label, path-aware sites) ok');
+ } finally {
+ await browser.close();
+ server.close();
+ }
+})().catch((e) => { console.error(e); process.exit(1); });