openvibers/openvibe.network Commits
SEO-31: the network is one WebSite, with the sitelinks search set to OpenVibe.Search (#410)
Changes
@@@ -320,7 +320,14 @@ function render({ releaseId = '' } = {}) {const stat = fs.statSync(SHELL);const key = `${stat.mtimeMs}:${catalog.updated}:${catalog.tools.length}:${act.version}:${releaseId}`;if (pageCache.key === key) return pageCache;-- const ld = seo.jsonLdTag(seo.jsonLd.itemList(sitesHeading(), OPEN.map((site) => ({ name: siteName(site), url: `https://${site.host}/`, description: site.what })))) + seo.jsonLdTag(seo.jsonLd.faq(faqPairs()));++ // The network as one entity (SEO-31): a WebSite whose sitelinks search is the network's own search service, so a++ // search box in a Google result searches OpenVibe rather than the wider web. The description is true of today.++ const ld = seo.jsonLdTag(seo.jsonLd.website({++ name: 'OpenVibe.Network',++ url: 'https://openvibe.network/',++ description: `The front door to the OpenVibe network: one free account across ${OPEN.length} open sites — live streaming, tools, chat, games, a wiki, news, deals and an open developer platform.`,++ searchUrl: 'https://search.openvibe.network/?q={q}',++ })) + seo.jsonLdTag(seo.jsonLd.itemList(sitesHeading(), OPEN.map((site) => ({ name: siteName(site), url: `https://${site.host}/`, description: site.what })))) + seo.jsonLdTag(seo.jsonLd.faq(faqPairs()));const card = social.url('network', 'https://openvibe.network/shared') || 'https://openvibe.network/assets/logo-512.png';const summary = summaryOf(catalog);const html = fs.readFileSync(SHELL, 'utf8').replace('<div id="navbar-mount"></div>', '<div id="navbar-mount"></div>' + summary.body + require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Sign in', href: '/login' }, { label: 'Themes', href: '/themes' }] })).replace('<!--OV:HOME-->', body(catalog, act)).replace('<!--OV:INTENTS-->', intents(catalog)).replace('<!--OV:CONSTELLATION-->', () => orbit() + numbers(catalog, act)).replaceAll('__OV_CARD__', card).replace('<!--OV:SITES-->', String(OPEN.length + 1)).replace('<!--OV:COUNT-->', catalog.tools.length ? String(catalog.tools.length) : 'free').replace('<div id="ov-footer"></div>', require('openvibe-shared/footer').ssr({ service: 'network', variant: 'full' })).replace('</head>', () => `${ld}\n${summary.head}\n${boost.meta(releaseId)}\n</head>`);@@@ -98,6 +98,11 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-buiassert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');++// SEO-31: the network is one WebSite entity, and its sitelinks search is the network's own search service.++const siteLd = JSON.parse(/<script type="application\/ld\+json">(\{"@context":"https:\/\/schema.org","@type":"WebSite"[\s\S]*?)<\/script>/.exec(html)[1]);++assert.strictEqual(siteLd.url, 'https://openvibe.network/', 'the WebSite is the network itself');++assert.strictEqual(siteLd.potentialAction.target.urlTemplate, 'https://search.openvibe.network/?q={search_term_string}', 'the sitelinks search is OpenVibe.Search');++assert.strictEqual(siteLd.potentialAction['query-input'], 'required name=search_term_string');// A tagline that already ends a sentence ("What should we eat?") gets no second period; a family of one is "1 tool".assert.ok(!/[.?!]\. /.test(html.replace(/<[^>]+>/g, ' ')), 'no doubled sentence end after a site tagline');assert.ok(!/\b1 tools\b/.test(html), 'one tool is "1 tool"');
@@ -320,7 +320,14 @@ function render({ releaseId = '' } = {}) {
@@ -320,7 +320,14 @@ function render({ releaseId = '' } = {}) {
const stat = fs.statSync(SHELL);
const stat = fs.statSync(SHELL);
const key = `${stat.mtimeMs}:${catalog.updated}:${catalog.tools.length}:${act.version}:${releaseId}`;
const key = `${stat.mtimeMs}:${catalog.updated}:${catalog.tools.length}:${act.version}:${releaseId}`;
if (pageCache.key === key) return pageCache;
if (pageCache.key === key) return pageCache;
- const ld = seo.jsonLdTag(seo.jsonLd.itemList(sitesHeading(), OPEN.map((site) => ({ name: siteName(site), url: `https://${site.host}/`, description: site.what })))) + seo.jsonLdTag(seo.jsonLd.faq(faqPairs()));
+ // The network as one entity (SEO-31): a WebSite whose sitelinks search is the network's own search service, so a
+ // search box in a Google result searches OpenVibe rather than the wider web. The description is true of today.
+ const ld = seo.jsonLdTag(seo.jsonLd.website({
+ name: 'OpenVibe.Network',
+ url: 'https://openvibe.network/',
+ description: `The front door to the OpenVibe network: one free account across ${OPEN.length} open sites — live streaming, tools, chat, games, a wiki, news, deals and an open developer platform.`,
+ searchUrl: 'https://search.openvibe.network/?q={q}',
+ })) + seo.jsonLdTag(seo.jsonLd.itemList(sitesHeading(), OPEN.map((site) => ({ name: siteName(site), url: `https://${site.host}/`, description: site.what })))) + seo.jsonLdTag(seo.jsonLd.faq(faqPairs()));
const card = social.url('network', 'https://openvibe.network/shared') || 'https://openvibe.network/assets/logo-512.png';
const card = social.url('network', 'https://openvibe.network/shared') || 'https://openvibe.network/assets/logo-512.png';
const summary = summaryOf(catalog);
const summary = summaryOf(catalog);
const html = fs.readFileSync(SHELL, 'utf8').replace('<div id="navbar-mount"></div>', '<div id="navbar-mount"></div>' + summary.body + require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Sign in', href: '/login' }, { label: 'Themes', href: '/themes' }] })).replace('<!--OV:HOME-->', body(catalog, act)).replace('<!--OV:INTENTS-->', intents(catalog)).replace('<!--OV:CONSTELLATION-->', () => orbit() + numbers(catalog, act)).replaceAll('__OV_CARD__', card).replace('<!--OV:SITES-->', String(OPEN.length + 1)).replace('<!--OV:COUNT-->', catalog.tools.length ? String(catalog.tools.length) : 'free').replace('<div id="ov-footer"></div>', require('openvibe-shared/footer').ssr({ service: 'network', variant: 'full' })).replace('</head>', () => `${ld}\n${summary.head}\n${boost.meta(releaseId)}\n</head>`);
const html = fs.readFileSync(SHELL, 'utf8').replace('<div id="navbar-mount"></div>', '<div id="navbar-mount"></div>' + summary.body + require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links: [{ label: 'Sign in', href: '/login' }, { label: 'Themes', href: '/themes' }] })).replace('<!--OV:HOME-->', body(catalog, act)).replace('<!--OV:INTENTS-->', intents(catalog)).replace('<!--OV:CONSTELLATION-->', () => orbit() + numbers(catalog, act)).replaceAll('__OV_CARD__', card).replace('<!--OV:SITES-->', String(OPEN.length + 1)).replace('<!--OV:COUNT-->', catalog.tools.length ? String(catalog.tools.length) : 'free').replace('<div id="ov-footer"></div>', require('openvibe-shared/footer').ssr({ service: 'network', variant: 'full' })).replace('</head>', () => `${ld}\n${summary.head}\n${boost.meta(releaseId)}\n</head>`);
@@ -98,6 +98,11 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-bui
@@ -98,6 +98,11 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-bui
assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');
assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');
assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);
assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);
assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');
assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');
+// SEO-31: the network is one WebSite entity, and its sitelinks search is the network's own search service.
+const siteLd = JSON.parse(/<script type="application\/ld\+json">(\{"@context":"https:\/\/schema.org","@type":"WebSite"[\s\S]*?)<\/script>/.exec(html)[1]);
+assert.strictEqual(siteLd.url, 'https://openvibe.network/', 'the WebSite is the network itself');
+assert.strictEqual(siteLd.potentialAction.target.urlTemplate, 'https://search.openvibe.network/?q={search_term_string}', 'the sitelinks search is OpenVibe.Search');
+assert.strictEqual(siteLd.potentialAction['query-input'], 'required name=search_term_string');
// A tagline that already ends a sentence ("What should we eat?") gets no second period; a family of one is "1 tool".
// A tagline that already ends a sentence ("What should we eat?") gets no second period; a family of one is "1 tool".
assert.ok(!/[.?!]\. /.test(html.replace(/<[^>]+>/g, ' ')), 'no doubled sentence end after a site tagline');
assert.ok(!/[.?!]\. /.test(html.replace(/<[^>]+>/g, ' ')), 'no doubled sentence end after a site tagline');
assert.ok(!/\b1 tools\b/.test(html), 'one tool is "1 tool"');
assert.ok(!/\b1 tools\b/.test(html), 'one tool is "1 tool"');