openvibers/openvibe.codes Commits
The shared update system and one account UI, on the OpenVibe Frame (openvibe-shared 1.11.0, openvibe/frame): the home shows what shipped, /updates is the shared log and the footer links it; the shared navbar handles sign in and out (logoutUrl ends this site's session) and carries this site's account links in its menu, while the page's own account bar is only for no-JS visitors
Changes
@@@ -18,7 +18,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"},"engines": {"node": ">=22.0.0"@@@ -1048,9 +1048,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.5.1",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",-- "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",++ "version": "1.11.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0",++ "integrity": "sha512-edMW0I25HNFReLcfZ9ak+4q5sy2t7qq7EPG52tOerTBV2fU994C5we/02AD/9i2Vuq0+ayNBBisW0cg60L6DLQ==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -29,7 +29,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"},"engines": {"node": ">=22.0.0"@@@ -73,7 +73,7 @@ function createApp(opts = {}) {contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- // The shared chrome (theme-loader, navbar) comes from the Network; the inline init is ours.++ // The OpenVibe Frame (theme-loader, navbar, footer) comes from the Network; the inline init is ours.scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network'],styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],@@@ -11,6 +11,7 @@* Markdown files as they are.*/const fs = require('fs');++const frame = require('openvibe-shared/frame');const path = require('path');const express = require('express');const { asyncRouter } = require('./router');@@@ -82,6 +83,8 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((// ── Policy ──────────────────────────────────────────────const adr = (id) => docs.adrs.find((a) => a.id === id);++ // What shipped on OpenVibe.Codes: the shared update log every OpenVibe site has.++ r.get('/updates', (req, res) => page(req, res, { index: true, cache: PUBLIC_CACHE, title: 'What shipped on OpenVibe.Codes', body: raw(frame.updatesBody({ service: 'codes', siteName: 'OpenVibe.Codes' }) + frame.shippedScript()) }));r.get('/policy', (req, res) => page(req, res, {index: true, cache: PUBLIC_CACHE, title: 'Policy', crumbs: [{ label: 'Policy' }],body: html`<h1>Policy</h1><ul class="cards">@@@ -3,7 +3,7 @@/*** Page shell. Every page is server-rendered through this and is complete without JavaScript:* - <head>: title, description, canonical, robots (portal pages are noindex), the shared app icon-- * - the shared chrome: navbar.js and theme-loader.js from the Network (progressive), a <noscript>++ * - the OpenVibe Frame: navbar.js and theme-loader.js from the Network (progressive), a <noscript>* navigation bar and the server-rendered shared footer (openvibe-shared)* - Codes' own script only where a page offers an optional in-browser convenience (webhook* signature computed locally, copy buttons); every form works without it@@@ -12,7 +12,7 @@ const crypto = require('crypto');const fs = require('fs');const path = require('path');const appIcon = require('openvibe-shared/app-icon');--const chrome = require('openvibe-shared/chrome-ssr');++const frame = require('openvibe-shared/frame');const { html, raw, esc } = require('./html');const NETWORK_URL = 'https://openvibe.network';@@@ -55,7 +55,12 @@ function renderPage(o) {silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,sessionUrl: '/auth/me',loginUrl: `/auth/login?next=${loginNext}`,++ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too};++ // This site's own account links live in the shared navbar's account menu (the page's account++ // bar below is only for visitors without JavaScript).++ if (signedIn) nav.menu = { before: [...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : []), { label: 'Your projects', href: '/projects', icon: 'fa-folder' }] };++ const footer = { service: 'codes', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };const account = signedIn? html`Signed in as <strong>${viewer.displayName || viewer.username || 'you'}</strong>${viewer.staff ? html` · <a href="/staff">Staff</a>` : ''} · <a href="/projects">Your projects</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`: html`<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a> to manage projects and apps`;@@@ -78,22 +83,25 @@ ${appIcon.headTags({ site: 'codes' })}<link rel="stylesheet" href="${asset('css/codes.css')}"><script src="${NETWORK_URL}/shared/theme-loader.js" defer></script><script src="${NETWORK_URL}/shared/navbar.js" defer></script>++<script src="${NETWORK_URL}/shared/footer.js" defer></script>${render(scripts)}</head><body><a class="skip" href="#main">Skip to content</a><div id="navbar-mount"></div>--${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}--<div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div>++${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}++<noscript><div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div></noscript><main id="main" class="page">${render(crumbs)}${render(o.body)}++${o.path === '/' ? frame.shipped({ service: 'codes', title: `Recently shipped on ${SITE_NAME}` }) : ''}</main>--${chrome.footer({ service: 'codes', variant: 'full' })}++${frame.footer(footer)}<script>--window.__OV_PAGE = ${JSON.stringify({ navbar: nav }).replace(/</g, '\\u003c')};++window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};document.addEventListener('DOMContentLoaded', function () {-- try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the chrome is optional */ }++ try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the Frame is optional */ }++ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }});</script></body>
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"
},
},
"engines": {
"engines": {
"node": ">=22.0.0"
"node": ">=22.0.0"
@@ -1048,9 +1048,9 @@
@@ -1048,9 +1048,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.5.1",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
- "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
+ "version": "1.11.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0",
+ "integrity": "sha512-edMW0I25HNFReLcfZ9ak+4q5sy2t7qq7EPG52tOerTBV2fU994C5we/02AD/9i2Vuq0+ayNBBisW0cg60L6DLQ==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -29,7 +29,7 @@
@@ -29,7 +29,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.34.2",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"
},
},
"engines": {
"engines": {
"node": ">=22.0.0"
"node": ">=22.0.0"
@@ -73,7 +73,7 @@ function createApp(opts = {}) {
@@ -73,7 +73,7 @@ function createApp(opts = {}) {
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- // The shared chrome (theme-loader, navbar) comes from the Network; the inline init is ours.
+ // The OpenVibe Frame (theme-loader, navbar, footer) comes from the Network; the inline init is ours.
scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network'],
scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network'],
styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
@@ -11,6 +11,7 @@
@@ -11,6 +11,7 @@
* Markdown files as they are.
* Markdown files as they are.
*/
*/
const fs = require('fs');
const fs = require('fs');
+const frame = require('openvibe-shared/frame');
const path = require('path');
const path = require('path');
const express = require('express');
const express = require('express');
const { asyncRouter } = require('./router');
const { asyncRouter } = require('./router');
@@ -82,6 +83,8 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((
@@ -82,6 +83,8 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((
// ── Policy ──────────────────────────────────────────────
// ── Policy ──────────────────────────────────────────────
const adr = (id) => docs.adrs.find((a) => a.id === id);
const adr = (id) => docs.adrs.find((a) => a.id === id);
+ // What shipped on OpenVibe.Codes: the shared update log every OpenVibe site has.
+ r.get('/updates', (req, res) => page(req, res, { index: true, cache: PUBLIC_CACHE, title: 'What shipped on OpenVibe.Codes', body: raw(frame.updatesBody({ service: 'codes', siteName: 'OpenVibe.Codes' }) + frame.shippedScript()) }));
r.get('/policy', (req, res) => page(req, res, {
r.get('/policy', (req, res) => page(req, res, {
index: true, cache: PUBLIC_CACHE, title: 'Policy', crumbs: [{ label: 'Policy' }],
index: true, cache: PUBLIC_CACHE, title: 'Policy', crumbs: [{ label: 'Policy' }],
body: html`<h1>Policy</h1><ul class="cards">
body: html`<h1>Policy</h1><ul class="cards">
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
/**
/**
* Page shell. Every page is server-rendered through this and is complete without JavaScript:
* Page shell. Every page is server-rendered through this and is complete without JavaScript:
* - <head>: title, description, canonical, robots (portal pages are noindex), the shared app icon
* - <head>: title, description, canonical, robots (portal pages are noindex), the shared app icon
- * - the shared chrome: navbar.js and theme-loader.js from the Network (progressive), a <noscript>
+ * - the OpenVibe Frame: navbar.js and theme-loader.js from the Network (progressive), a <noscript>
* navigation bar and the server-rendered shared footer (openvibe-shared)
* navigation bar and the server-rendered shared footer (openvibe-shared)
* - Codes' own script only where a page offers an optional in-browser convenience (webhook
* - Codes' own script only where a page offers an optional in-browser convenience (webhook
* signature computed locally, copy buttons); every form works without it
* signature computed locally, copy buttons); every form works without it
@@ -12,7 +12,7 @@ const crypto = require('crypto');
@@ -12,7 +12,7 @@ const crypto = require('crypto');
const fs = require('fs');
const fs = require('fs');
const path = require('path');
const path = require('path');
const appIcon = require('openvibe-shared/app-icon');
const appIcon = require('openvibe-shared/app-icon');
-const chrome = require('openvibe-shared/chrome-ssr');
+const frame = require('openvibe-shared/frame');
const { html, raw, esc } = require('./html');
const { html, raw, esc } = require('./html');
const NETWORK_URL = 'https://openvibe.network';
const NETWORK_URL = 'https://openvibe.network';
@@ -55,7 +55,12 @@ function renderPage(o) {
@@ -55,7 +55,12 @@ function renderPage(o) {
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
sessionUrl: '/auth/me',
loginUrl: `/auth/login?next=${loginNext}`,
loginUrl: `/auth/login?next=${loginNext}`,
+ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
};
};
+ // This site's own account links live in the shared navbar's account menu (the page's account
+ // bar below is only for visitors without JavaScript).
+ if (signedIn) nav.menu = { before: [...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : []), { label: 'Your projects', href: '/projects', icon: 'fa-folder' }] };
+ const footer = { service: 'codes', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const account = signedIn
const account = signedIn
? html`Signed in as <strong>${viewer.displayName || viewer.username || 'you'}</strong>${viewer.staff ? html` · <a href="/staff">Staff</a>` : ''} · <a href="/projects">Your projects</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
? html`Signed in as <strong>${viewer.displayName || viewer.username || 'you'}</strong>${viewer.staff ? html` · <a href="/staff">Staff</a>` : ''} · <a href="/projects">Your projects</a> · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
: html`<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a> to manage projects and apps`;
: html`<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a> to manage projects and apps`;
@@ -78,22 +83,25 @@ ${appIcon.headTags({ site: 'codes' })}
@@ -78,22 +83,25 @@ ${appIcon.headTags({ site: 'codes' })}
<link rel="stylesheet" href="${asset('css/codes.css')}">
<link rel="stylesheet" href="${asset('css/codes.css')}">
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
+<script src="${NETWORK_URL}/shared/footer.js" defer></script>
${render(scripts)}
${render(scripts)}
</head>
</head>
<body>
<body>
<a class="skip" href="#main">Skip to content</a>
<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
<div id="navbar-mount"></div>
-${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}
-<div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div>
+${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV })}
+<noscript><div class="account-bar" role="navigation" aria-label="Account">${render(account)}</div></noscript>
<main id="main" class="page">
<main id="main" class="page">
${render(crumbs)}
${render(crumbs)}
${render(o.body)}
${render(o.body)}
+${o.path === '/' ? frame.shipped({ service: 'codes', title: `Recently shipped on ${SITE_NAME}` }) : ''}
</main>
</main>
-${chrome.footer({ service: 'codes', variant: 'full' })}
+${frame.footer(footer)}
<script>
<script>
-window.__OV_PAGE = ${JSON.stringify({ navbar: nav }).replace(/</g, '\\u003c')};
+window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('DOMContentLoaded', function () {
- try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the chrome is optional */ }
+ try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the Frame is optional */ }
+ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }
});
});
</script>
</script>
</body>
</body>