openvibers/openvibe.chat Commits
Server-render the site navigation in the first HTML (#526)
Changes
@@@ -190,8 +190,7 @@ ${o.app ? `<script src="${asset('vibe-md.js')}" defer></script>\n<script src="${</head><body class="${[cls, o.app ? 'oc-app-body' : ''].filter(Boolean).join(' ')}"${scale}><a class="oc-skip" href="#main">Skip to content</a>--<div id="navbar-mount"></div>--${frame.noscriptNav({ name: siteName, home: '/', links: siteLinks.map(({ label, href }) => ({ label, href })) })}++${frame.serverNav({ name: siteName, service: siteService, home: '/', links: siteLinks.map(({ label, href }) => ({ label, href })) })}<main id="main" class="${o.app ? 'vb-wrap' : `oc-main${o.shell ? ' oc-main--shell' : (o.wide ? ' oc-main--wide' : '')}`}"${o.mainAttrs ? ` ${o.mainAttrs}` : ''}><noscript><p class="oc-account">${who}</p></noscript>${o.body || ''}@@@ -38,6 +38,10 @@ t('global chat reads without JavaScript; signing in shows the composer', async (assert.match(r.text, /Sign in with OpenVibe/);assert.match(r.text, /<meta name="robots" content="index, follow">/);assert.match(r.text, /\/shared\/navbar\.js\?v=/, 'the Frame from this site\'s own /shared/');++ // The first HTML carries the site's navigation for a crawler, not only the scripts that draw it.++ const serverNav = /<div id="navbar-mount"><nav class="openvibe-navbar"[^>]*data-ov-server-nav="true"[\s\S]*?<\/nav><\/div>/.exec(r.text);++ assert.ok(serverNav, 'the first HTML has the server navigation');++ assert.match(serverNav[0], /href="\/rooms"[^>]*>Rooms</, 'and its places');// A Content-Security-Policy with a per-response nonce, carried by every inline script and only by them.const csp = r.headers.get('content-security-policy') || '';const nonce = (/'nonce-([^']+)'/.exec(csp) || [])[1];
@@ -190,8 +190,7 @@ ${o.app ? `<script src="${asset('vibe-md.js')}" defer></script>\n<script src="${
@@ -190,8 +190,7 @@ ${o.app ? `<script src="${asset('vibe-md.js')}" defer></script>\n<script src="${
</head>
</head>
<body class="${[cls, o.app ? 'oc-app-body' : ''].filter(Boolean).join(' ')}"${scale}>
<body class="${[cls, o.app ? 'oc-app-body' : ''].filter(Boolean).join(' ')}"${scale}>
<a class="oc-skip" href="#main">Skip to content</a>
<a class="oc-skip" href="#main">Skip to content</a>
-<div id="navbar-mount"></div>
-${frame.noscriptNav({ name: siteName, home: '/', links: siteLinks.map(({ label, href }) => ({ label, href })) })}
+${frame.serverNav({ name: siteName, service: siteService, home: '/', links: siteLinks.map(({ label, href }) => ({ label, href })) })}
<main id="main" class="${o.app ? 'vb-wrap' : `oc-main${o.shell ? ' oc-main--shell' : (o.wide ? ' oc-main--wide' : '')}`}"${o.mainAttrs ? ` ${o.mainAttrs}` : ''}>
<main id="main" class="${o.app ? 'vb-wrap' : `oc-main${o.shell ? ' oc-main--shell' : (o.wide ? ' oc-main--wide' : '')}`}"${o.mainAttrs ? ` ${o.mainAttrs}` : ''}>
<noscript><p class="oc-account">${who}</p></noscript>
<noscript><p class="oc-account">${who}</p></noscript>
${o.body || ''}
${o.body || ''}
@@ -38,6 +38,10 @@ t('global chat reads without JavaScript; signing in shows the composer', async (
@@ -38,6 +38,10 @@ t('global chat reads without JavaScript; signing in shows the composer', async (
assert.match(r.text, /Sign in with OpenVibe/);
assert.match(r.text, /Sign in with OpenVibe/);
assert.match(r.text, /<meta name="robots" content="index, follow">/);
assert.match(r.text, /<meta name="robots" content="index, follow">/);
assert.match(r.text, /\/shared\/navbar\.js\?v=/, 'the Frame from this site\'s own /shared/');
assert.match(r.text, /\/shared\/navbar\.js\?v=/, 'the Frame from this site\'s own /shared/');
+ // The first HTML carries the site's navigation for a crawler, not only the scripts that draw it.
+ const serverNav = /<div id="navbar-mount"><nav class="openvibe-navbar"[^>]*data-ov-server-nav="true"[\s\S]*?<\/nav><\/div>/.exec(r.text);
+ assert.ok(serverNav, 'the first HTML has the server navigation');
+ assert.match(serverNav[0], /href="\/rooms"[^>]*>Rooms</, 'and its places');
// A Content-Security-Policy with a per-response nonce, carried by every inline script and only by them.
// A Content-Security-Policy with a per-response nonce, carried by every inline script and only by them.
const csp = r.headers.get('content-security-policy') || '';
const csp = r.headers.get('content-security-policy') || '';
const nonce = (/'nonce-([^']+)'/.exec(csp) || [])[1];
const nonce = (/'nonce-([^']+)'/.exec(csp) || [])[1];