openvibers/openvibe.shared Commits

The menu's Home: a site's home has a place in "On <site>" (#352)

OP OpenVibers committed 4 days ago · eeaf73fcf0 · 5 files, +20 −5

* The menu's Home: a site's home has a place in "On <site>"

A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in the menu's list, and
on the home page Home wears the highlight, the way every other page's link does. A site that already names its home
gets no second one.

Test: test/menu-here-current-chrome.test.js (Home added and marked on /, not elsewhere, never twice).

* Changelog: the menu's Home

* search-loading test: Home leads a site's pages when no link names the home

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 4b9ff98..ef76c96 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -2,6 +2,10 @@
## Unreleased
++- **The menu's Home.** A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in
++ "On <site>", and on the home page Home wears the highlight like every other page's link (owner, 11-09). A site that
++ already names its home gets no second one.
++
- **Footer legal links are 44 px wide to a finger.** On a touch screen, Terms, Privacy, DMCA and the compact links
reached 36 px across (11-09 tap audit on six sites, `tools/tap-audit.js`); their hit area now extends 5 px each side
(they sit 20 px apart, so the areas never meet).
ddiff --git a/navbar.js b/navbar.js
iindex caa7eed..9cdc417 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -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': '8e55bfe93238', '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' };
++ 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': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'ad94f524fa45', '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">.
ddiff --git a/search.js b/search.js
iindex af4471e..16f4408 100644
---- a/search.js
++++ b/search.js
@@@ -965,6 +965,11 @@
// This site's own cards (a drop zone on Download, go-live status on Live, a new task on Codes…).
for (const c of cards) groups.push({ title: c.title || '', as: 'card', card: c, section: `card:${c.id}`, items: [] });
const pages = (ctx.links || []).filter((l) => l && l.label && typeof l.href === 'string' && !l.hidden).map((l) => ({ title: l.label, url: safeUrl(l.href), icon: 'go', drawn: l.icon && typeof ctx.navIcon === 'function' ? ctx.navIcon(l.icon) : '', kind: 'page' })).filter((x) => x.url);
++ // A site's links that never name its home get one first, so the home page has a place of its own in the menu
++ // and wears the highlight there too (owner 11-09, on openvibe.exchange: "shouldn't there be a link to home that's
++ // highlighted").
++ const rootHere = (x) => { try { const u = new URL(x.url, root.location.href); return u.origin === root.location.origin && (u.pathname.replace(/\/+$/, '') || '/') === '/'; } catch { return false; } };
++ if (pages.length && !pages.some(rootHere)) pages.unshift({ title: 'Home', url: '/', icon: 'go', drawn: typeof ctx.navIcon === 'function' ? ctx.navIcon('fa-house') : '', kind: 'page' });
// The page the person is on wears the highlight (owner 11-08): the link whose path is this page's, or the longest one
// this page sits under (/messages/abc under Messages); "/" only on the home itself.
let best = -1, bestLen = -1;
ddiff --git a/test/menu-here-current-chrome.test.js b/test/menu-here-current-chrome.test.js
iindex 653001b..ad277e4 100644
---- a/test/menu-here-current-chrome.test.js
++++ b/test/menu-here-current-chrome.test.js
@@@ -19,13 +19,13 @@ if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
const FRAME = { nav: [{ id: 'chat', name: 'Chat', url: 'https://openvibe.chat/', icon: 'chat', group: 'talk' }], groups: [{ id: 'talk', name: 'Talk' }], soon: [], footer: {} };
const LINKS = [{ label: 'Vibe', href: '/' }, { label: 'Global chat', href: '/global' }, { label: 'Messages', href: '/messages' }, { label: 'Message settings', href: '/messages/settings' }, { label: 'Elsewhere', href: 'https://example.test/messages/abc' }];
--const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Here</title><link rel="icon" href="data:,">
++const PAGE_OF = (links) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Here</title><link rel="icon" href="data:,">
<script>
const realFetch = window.fetch;
window.fetch = (u, o) => (String(u).startsWith('https://openvibe.network/api/frame?') ? Promise.resolve({ ok: true, json: async () => (${JSON.stringify(FRAME)}) }) : String(u).startsWith('https://') ? Promise.resolve({ ok: false, status: 404, json: async () => ({}) }) : realFetch(u, o));
</script>
<script src="/shared/navbar.js" defer></script></head><body><div id="navbar-mount"></div>
--<script>document.addEventListener('DOMContentLoaded', function () { OpenVibeNavbar.init({ service: 'site', user: null, links: ${JSON.stringify(LINKS)}, countViews: false, notifications: false }); });</script>
++<script>document.addEventListener('DOMContentLoaded', function () { OpenVibeNavbar.init({ service: 'site', user: null, links: ${JSON.stringify(links)}, countViews: false, notifications: false }); });</script>
</body></html>`;
const server = http.createServer((req, res) => {
const u = new URL(req.url, 'http://x');
@@@ -34,7 +34,7 @@ const server = http.createServer((req, res) => {
if (!fs.existsSync(path.join(ROOT, m[1]))) { res.writeHead(404); return res.end(); }
res.writeHead(200, { 'content-type': 'application/javascript' }); return res.end(fs.readFileSync(path.join(ROOT, m[1])));
}
-- res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE);
++ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); return res.end(PAGE_OF(u.searchParams.has('nohome') ? LINKS.filter((l) => l.href !== '/') : LINKS));
});
const HERE = `[...document.querySelectorAll('.ovs-links .ovs-link')].filter((a) => a.classList.contains('is-here') || a.getAttribute('aria-current')).map((a) => a.textContent + ':' + a.getAttribute('aria-current'))`;
@@@ -69,6 +69,12 @@ const HERE = `[...document.querySelectorAll('.ovs-links .ovs-link')].filter((a)
assert.deepStrictEqual(await visit(p, '/messages/settings'), ['Message settings:page'], 'the exact page');
assert.deepStrictEqual(await visit(p, '/'), ['Vibe:page'], 'the home');
assert.deepStrictEqual(await visit(p, '/nowhere'), [], 'a page with no link of its own marks none');
++ // A site whose links never name its home (Exchange, 11-09) gets Home first, and the home page wears it.
++ assert.deepStrictEqual(await visit(p, '/?nohome=1'), ['Home:page'], 'Home is added and marked on the home');
++ assert.strictEqual(await p.evaluate(`(document.querySelector('.ovs-links .ovs-link') || {}).textContent`), 'Home', 'Home leads the list');
++ assert.deepStrictEqual(await visit(p, '/messages?nohome=1'), ['Messages:page'], 'elsewhere Home is not marked');
++ assert.strictEqual(await p.evaluate(`[...document.querySelectorAll('.ovs-links .ovs-link')].filter((a) => a.textContent === 'Home').length`), 1, 'one Home');
++ assert.strictEqual(await (async () => { await visit(p, '/'); return p.evaluate(`[...document.querySelectorAll('.ovs-links .ovs-link')].filter((a) => a.textContent === 'Home').length`); })(), 0, 'a site that names its home gets no second one');
// On a computer the menu is a search: the field is focused.
const d = await h.openPage(browser, context, { width: 1280, height: 900, block: blocked });
ddiff --git a/test/search-loading-chrome.test.js b/test/search-loading-chrome.test.js
iindex 588b19e..258a69e 100644
---- a/test/search-loading-chrome.test.js
++++ b/test/search-loading-chrome.test.js
@@@ -95,7 +95,7 @@ const server = http.createServer((req, res) => {
// 4.149.0 (the owner, 11-08: "tabs at the top … the main tab should just show links that exist on that site, and
// under that the full list of sites"): this site's tab, Feed and You; nothing typed opens on this site's tab.
assert.strictEqual(v.tabs, 3, 'three tabs at the top');
-- assert.deepStrictEqual([v.groups[0], v.links], ['On Chat', ['Rooms', 'Servers']], 'nothing typed: this site\'s own pages first');
++ assert.deepStrictEqual([v.groups[0], v.links], ['On Chat', ['Home', 'Rooms', 'Servers']], 'nothing typed: this site\'s own pages first, Home leading them (no link names the home)');
assert.ok(v.sites.includes('Live') && v.sites.includes('Tools'), 'then every site, even before the Frame answers');
assert.strictEqual(v.placeholder, 'Search Chat, or ask Vibe', 'the site by its short name (4.48.0)');
// The box completes from this site's pages (and the person's searches, history and sites); Tab takes it.