openvibers/openvibe.shared crawlable.js
crawlable.js
| 1 | ; |
| 2 | |
| 3 | const seo = require('./seo'); |
| 4 | const catalog = require('./crawl-sites.json'); |
| 5 | const esc = (value) => String(value == null ? '' : value).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); |
| 6 | const absolute = (url, base) => new URL(url, base).href; |
| 7 | |
| 8 | /** Static navigation, network links and discovery metadata for HTML sent before JavaScript runs. */ |
| 9 | function render({ name, service, url, description = '', links, pages = [], searchUrl } = {}) { |
| 10 | if (!name || !url) throw new TypeError('crawlable.render requires name and url'); |
| 11 | const base = new URL(url).origin; |
| 12 | const ownLinks = Array.isArray(links) ? links : []; |
| 13 | const ownItems = ownLinks.flatMap((item) => Array.isArray(item.items) ? item.items : [item]) |
| 14 | .filter((item) => item && (item.href || item.url) && (item.label || item.name || item.title)) |
| 15 | .map((item) => ({ label: item.label || item.name || item.title, href: item.href || item.url })); |
| 16 | const nav = `<nav aria-label="${esc(name)}"><ul><li><a href="/">Home</a></li>${ownItems.filter((item) => item.href !== '/').map((item) => `<li><a href="${esc(item.href)}">${esc(item.label)}</a></li>`).join('')}</ul></nav>`; |
| 17 | const network = catalog.sites.filter((site) => site.id !== service); |
| 18 | const footer = `<nav aria-label="OpenVibe">${catalog.groups.map((group) => { |
| 19 | const sites = network.filter((site) => site.group === group.id); |
| 20 | return sites.length ? `<section><h2>${esc(group.name)}</h2><ul>${sites.map((site) => `<li><a href="${esc(site.url)}">${esc(site.name)}</a> — ${esc(site.description)}</li>`).join('')}</ul></section>` : ''; |
| 21 | }).join('')}</nav>`; |
| 22 | const sameAs = [...new Set(catalog.sites.map((site) => new URL(site.url).origin))]; |
| 23 | const jsonLd = [seo.jsonLd.organization({ sameAs }), seo.jsonLd.website({ name, url: base + '/', description, searchUrl })]; |
| 24 | const head = jsonLd.map((item) => `<script type="application/ld+json">${JSON.stringify(item).replace(/</g, '\\u003c')}</script>`).join('\n'); |
| 25 | const llmsTxt = seo.llmsTxt({ name, summary: description || name, sections: [ |
| 26 | { title: 'Main pages', links: [{ title: 'Home', url: base + '/' }, ...pages.map((page) => ({ title: page.title || page.name, url: absolute(page.url || page.href, base) }))] }, |
| 27 | ...catalog.groups.map((group) => ({ title: group.name, links: network.filter((site) => site.group === group.id).map((site) => ({ title: site.name, url: site.url, note: site.description })) })).filter((section) => section.links.length), |
| 28 | ] }); |
| 29 | return { nav, footer, head, jsonLd, llmsTxt }; |
| 30 | } |
| 31 | |
| 32 | module.exports = { render, catalog }; |
| 33 |