From f571acbb683bc5fb34a50ebc0763ef980b014f08 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 16 Apr 2026 16:57:26 +0200 Subject: [PATCH] Paginate feed --- tasks/generate_feed.js | 131 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 14 deletions(-) diff --git a/tasks/generate_feed.js b/tasks/generate_feed.js index 798a5e66fce..ee31936cf6a 100644 --- a/tasks/generate_feed.js +++ b/tasks/generate_feed.js @@ -11,19 +11,122 @@ const json2xmlOpts = { cdataPropName: '__cdata', indentBy: ' ' } -const builder = new XMLBuilder(json2xmlOpts) -// Load and adapt feed -const outputXML = builder.build(feed) -const validation = XMLValidator.validate(outputXML) -if (validation) { - write( - 'dist/feed.xml', - xmlFormat(outputXML, { - indentation: json2xmlOpts.indentBy, - collapseContent: true - }) - ) -} else { - console.error(validation.err) +const PAGE_SIZE = 21 + +function writePage(feedJson, itemsSlice, pageIndex, totalPages) { + const builder = new XMLBuilder(json2xmlOpts) + const feedCopy = JSON.parse(JSON.stringify(feedJson)) + + // Ensure channel.item is the page slice + feedCopy.rss = feedCopy.rss || {} + feedCopy.rss.channel = feedCopy.rss.channel || {} + feedCopy.rss.channel.item = itemsSlice + + // Build atom links: adapt self, add prev/next where appropriate + const origAtom = (feedJson.rss && feedJson.rss.channel && feedJson.rss.channel['atom:link']) || [] + const origSelf = Array.isArray(origAtom) + ? (origAtom.find(l => l.__attr && l.__attr.rel === 'self') || {}).__attr + : (origAtom && origAtom.__attr) || {} + const originalHref = (origSelf && origSelf.href) || '' + const base = originalHref.replace(/\/feed(?:-page-\d+)?\.xml$/, '') + + const pageNumber = pageIndex + 1 + const selfHref = pageIndex === 0 ? `${base}/feed.xml` : `${base}/feed-page-${pageNumber}.xml` + + const newAtom = [] + newAtom.push({ __attr: { href: selfHref, rel: 'self', type: 'application/rss+xml' } }) + // preserve hub if present + const hub = Array.isArray(origAtom) && origAtom.find(l => l.__attr && l.__attr.rel === 'hub') + if (hub) newAtom.push(hub) + + if (pageIndex > 0) { + const prevHref = pageIndex === 1 ? `${base}/feed.xml` : `${base}/feed-page-${pageIndex}.xml` + newAtom.push({ __attr: { href: prevHref, rel: 'prev', type: 'application/rss+xml' } }) + } + + if (pageIndex < totalPages - 1) { + const nextHref = `${base}/feed-page-${pageNumber + 1}.xml` + newAtom.push({ __attr: { href: nextHref, rel: 'next', type: 'application/rss+xml' } }) + } + + feedCopy.rss.channel['atom:link'] = newAtom + + const outputXML = builder.build(feedCopy) + const validation = XMLValidator.validate(outputXML) + if (validation) { + const filename = pageIndex === 0 ? 'dist/feed.xml' : `dist/feed-page-${pageNumber}.xml` + write( + filename, + xmlFormat(outputXML, { + indentation: json2xmlOpts.indentBy, + collapseContent: true + }) + ) + return filename + } else { + console.error('XML validation error:', validation.err) + return null + } } + +// Main: split items and write pages +const items = (feed.rss && feed.rss.channel && feed.rss.channel.item) || [] +const totalPages = Math.max(1, Math.ceil(items.length / PAGE_SIZE)) +const written = [] +for (let i = 0; i < totalPages; i++) { + const start = i * PAGE_SIZE + const slice = items.slice(start, start + PAGE_SIZE) + const out = writePage(feed, slice, i, totalPages) + if (out) written.push(out) +} + +if (written.length) { + console.log('Wrote paginated feeds:', written.join(', ')) +} else { + console.error('No feed files written') +} + +// Additionally write a full archive feed with all items +function writeFullFeed(feedJson, allItems) { + const builder = new XMLBuilder(json2xmlOpts) + const feedCopy = JSON.parse(JSON.stringify(feedJson)) + + feedCopy.rss = feedCopy.rss || {} + feedCopy.rss.channel = feedCopy.rss.channel || {} + feedCopy.rss.channel.item = allItems + + // preserve and adapt atom links + const origAtom = (feedJson.rss && feedJson.rss.channel && feedJson.rss.channel['atom:link']) || [] + const origSelf = Array.isArray(origAtom) + ? (origAtom.find(l => l.__attr && l.__attr.rel === 'self') || {}).__attr + : (origAtom && origAtom.__attr) || {} + const originalHref = (origSelf && origSelf.href) || '' + const base = originalHref.replace(/\/feed(?:-page-\d+)?\.xml$/, '') + + const newAtom = [] + newAtom.push({ __attr: { href: `${base}/feed-all.xml`, rel: 'self', type: 'application/rss+xml' } }) + const hub = Array.isArray(origAtom) && origAtom.find(l => l.__attr && l.__attr.rel === 'hub') + if (hub) newAtom.push(hub) + feedCopy.rss.channel['atom:link'] = newAtom + + const outputXML = builder.build(feedCopy) + const validation = XMLValidator.validate(outputXML) + if (validation) { + const filename = 'dist/feed-all.xml' + write( + filename, + xmlFormat(outputXML, { + indentation: json2xmlOpts.indentBy, + collapseContent: true + }) + ) + return filename + } else { + console.error('XML validation error (feed-all):', validation.err) + return null + } +} + +const allOut = writeFullFeed(feed, items) +if (allOut) console.log('Wrote full archive feed:', allOut)