openvibers/openvibe.shared frame.js

frame.js

main 5.7 KB · 74 lines · javascript

1'use strict';
2// openvibe-shared/frame — the OpenVibe Frame (the navbar, footer and "shipped" views every OpenVibe site sits
3// in): the parts that can be baked into HTML on the server. (Formerly openvibe-shared/chrome-ssr; the alias was removed in 2.0.0.)
4// footer(cfg) the full shared footer (HTML + CSS); the browser's footer.js renders into the same element
5// noscriptNav(site) a plain navigation bar inside <noscript>: the JavaScript navbar cannot exist without
6// scripts, so visitors who block them (and text browsers) still get the brand and the network
7// shipped(opts) a home page's "shipped" block: the "🚀 shipped X ago" pill and the recent changes,
8// filled by shipped.js; without JavaScript, a link to the update log
9// updatesBody(opts) the body of a site's /updates page (the full log; openvibe.network's server-rendered
10// log is the no-JavaScript fallback)
11// shippedScript() the <script> tag for shipped.js (the footer loads it lazily too; a page that shows the
12// log should load it itself)
13const footerModule = require('./footer');
14const navbarModule = require('./navbar');
15
16const SITES = [['Live', 'https://openvibe.live/'], ['Tools', 'https://openvibe.tools/'], ['Community', 'https://openvibe.community/'], ['Games', 'https://openvibe.games/'], ['Media', 'https://openvibe.media/'], ['Network', 'https://openvibe.network/']];
17const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
18
19function noscriptNav({ name, home = '/', links = [] } = {}) {
20 const own = links.map(l => `<a href="${esc(l.href)}">${esc(l.label)}</a>`).join('');
21 const net = SITES.filter(([n]) => !String(name || '').endsWith('.' + n)).map(([n, u]) => `<a href="${u}">${n}</a>`).join('');
22 return `<noscript><nav aria-label="Site" style="display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;padding:12px 20px;border-bottom:1px solid rgba(255,255,255,.1);font:500 14px/1.4 system-ui,sans-serif"><a href="${esc(home)}" style="font-weight:700;color:inherit;text-decoration:none">${esc(name || 'OpenVibe')}</a>${own}<span style="flex:1"></span>${net}</nav></noscript>`;
23}
24
25// The client navbar replaces this node on its first render. Keeping configured links in the same
26// mount makes the page useful to crawlers before any JavaScript runs.
27function serverNav({ name, service, home = '/', links } = {}) {
28 const source = Array.isArray(links) ? links : navbarModule._defaults.linksFor(service);
29 const items = source.filter(l => l && (l.href || l.url) && (l.label || l.name));
30 const own = items.map(l => `<a data-ov-nav-link="true" href="${esc(l.href || l.url)}">${esc(l.label || l.name)}</a>`).join('');
31 const brand = `<a href="${esc(home)}" style="font-weight:700;color:inherit;text-decoration:none">${esc(name || 'OpenVibe')}</a>`;
32 return `<div id="navbar-mount"><nav class="openvibe-navbar" aria-label="Site" data-ov-server-nav="true" style="box-sizing:border-box;display:flex;align-items:center;gap:8px;padding:0 16px;min-height:52px;font:500 14px/1.4 system-ui,sans-serif">${brand}${own}</nav></div>`;
33}
34
35const NETWORK = 'https://openvibe.network';
36const SERVICE_RE = /^[a-z][a-z0-9-]{1,39}$/;
37const svc = (s) => (SERVICE_RE.test(String(s || '')) ? String(s) : '');
38
39function shipped({ service, updates = '/updates', title = null, limit = 5 } = {}) {
40 const id = svc(service);
41 const n = Math.min(Math.max(parseInt(limit, 10) || 5, 1), 20);
42 return `<section class="ov-shipped-home" aria-label="What shipped">`
43 + `<a data-ov-shipped="latest" data-service="${esc(id)}" href="${esc(updates)}" hidden></a>`
44 + `<div data-ov-shipped="list" data-service="${esc(id)}" data-limit="${n}" data-more="${esc(updates)}"${title ? ` data-title="${esc(title)}"` : ''} hidden></div>`
45 + `<noscript><p><a href="${esc(updates)}">What shipped recently</a></p></noscript>`
46 + `</section>`;
47}
48
49function updatesBody({ service, siteName = null, base = NETWORK } = {}) {
50 const id = svc(service);
51 const name = siteName || (id ? `OpenVibe.${id.charAt(0).toUpperCase()}${id.slice(1)}` : 'OpenVibe');
52 const network = `${String(base).replace(/\/+$/, '')}/updates${id ? `?site=${encodeURIComponent(id)}` : ''}`;
53 return `<section class="ov-updates">`
54 + `<h1>What shipped on ${esc(name)}</h1>`
55 + `<p class="ov-updates-lede">Every change deployed to ${esc(name)}, newest first. Each line is a commit from the OpenVibers repositories, linked to the change itself; batches become <a href="https://openvibe.blog/@openvibe">Patch notes on openvibe.blog</a>. Switch to <a href="${esc(String(base).replace(/\/+$/, ''))}/updates">the whole network</a>.</p>`
56 + `<div data-ov-shipped="log" data-service="${esc(id)}" data-limit="50"><noscript><p>The update log needs JavaScript here; <a href="${esc(network)}">the same log on openvibe.network</a> works without it.</p></noscript></div>`
57 + `</section>`;
58}
59
60// shipped.js by its content hash (4.138.0), so an updates page never runs a copy an edge kept from before a release.
61let _shippedRev = null;
62function shippedRev() {
63 if (_shippedRev === null) {
64 try { _shippedRev = require('./assets').hashAsset(require('fs').readFileSync(require('path').join(__dirname, 'shipped.js'))); } catch { _shippedRev = ''; }
65 }
66 return _shippedRev;
67}
68function shippedScript({ base = NETWORK } = {}) {
69 const v = shippedRev();
70 return `<script src="${esc(String(base).replace(/\/+$/, ''))}/shared/shipped.js${v ? `?v=${v}` : ''}" defer></script>`;
71}
72
73module.exports = { footer: (cfg) => footerModule.ssr(cfg), noscriptNav, serverNav, shipped, updatesBody, shippedScript };
74