'use strict'; const seo = require('./seo'); const catalog = require('./crawl-sites.json'); const esc = (value) => String(value == null ? '' : value).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); const absolute = (url, base) => new URL(url, base).href; /** Static navigation, network links and discovery metadata for HTML sent before JavaScript runs. */ function render({ name, service, url, description = '', links, pages = [], searchUrl } = {}) { if (!name || !url) throw new TypeError('crawlable.render requires name and url'); const base = new URL(url).origin; const ownLinks = Array.isArray(links) ? links : []; const ownItems = ownLinks.flatMap((item) => Array.isArray(item.items) ? item.items : [item]) .filter((item) => item && (item.href || item.url) && (item.label || item.name || item.title)) .map((item) => ({ label: item.label || item.name || item.title, href: item.href || item.url })); const nav = ``; const network = catalog.sites.filter((site) => site.id !== service); const footer = ``; const sameAs = [...new Set(catalog.sites.map((site) => new URL(site.url).origin))]; const jsonLd = [seo.jsonLd.organization({ sameAs }), seo.jsonLd.website({ name, url: base + '/', description, searchUrl })]; const head = jsonLd.map((item) => ``).join('\n'); const llmsTxt = seo.llmsTxt({ name, summary: description || name, sections: [ { title: 'Main pages', links: [{ title: 'Home', url: base + '/' }, ...pages.map((page) => ({ title: page.title || page.name, url: absolute(page.url || page.href, base) }))] }, ...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), ] }); return { nav, footer, head, jsonLd, llmsTxt }; } module.exports = { render, catalog };