RSS Feed Block
Renders entries from an external RSS feed. Its items are fetched at render time (by a fetcher you provide) and shown with a configurable item type (variation).
Live example
6.2.2
The Plone Content Management System
6.2.1
The Plone Content Management System
6.1.5
The Plone Content Management System
6.2.0
The Plone Content Management System
6.2.0rc2
The Plone Content Management System
6.2.0rc1
The Plone Content Management System
Developer Reference
Schema
Pass this object inside the blocks option when calling initBridge() to register this block type with the admin UI. See Custom Blocks for the full setup guide.
rssFeed: {
id: 'rssFeed',
title: 'RSS Feed',
group: 'common',
blockSchema: {
fieldsets: [{ id: 'default', title: 'Default', fields: ['feedUrl', 'count', 'variation', 'fieldMapping'] }],
properties: {
feedUrl: { title: 'Feed URL', widget: 'url' },
count: { title: 'Max items', type: 'number', default: 6 },
variation: { title: 'Item Type', widget: 'blockTypeSelect', filterConvertibleFrom: '@default', default: 'summary' },
},
},
schemaEnhancer: {
inheritSchemaFrom: { typeField: 'variation', mappingField: 'fieldMapping', defaultsField: 'itemDefaults' },
// A rule that REFUSES a value, rather than showing or hiding one.
// Asking for fewer than no items is the clearest thing this block
// can be told that it cannot do, and no fixture asks for it — the
// page uses the default 6 — so the rule is inert until a test sets
// a negative count on purpose.
fieldRules: {
count: {
when: { count: { lt: 0 } },
error: 'Max items cannot be negative.',
},
// …and one that SAYS something instead of refusing it. Asking a
// feed for a hundred items is a bad idea, not an impossible
// one, so the author hears about it and the save goes through.
// No fixture asks for that many, so it is inert until a test
// does on purpose.
feedUrl: {
when: { count: { gt: 50 } },
warning: 'That many items will make the page slow.',
},
},
},
}JSON Block Data
Example block JSON (as stored in the CMS). This is the data structure your component will receive in the block prop.
{
"@type": "rssFeed",
"feedUrl": "https://pypi.org/rss/project/plone/releases.xml",
"variation": "default"
}Rendering
How this block renders in your frontend. Add its handling to your renderer, or — for list-style blocks — register a fetcher and reuse your list rendering.
This block has no bespoke renderer. Add its fetcher to your fetchItems map (keyed by @type) and expandListingBlocks expands it in any region you render — the same seam that powers listings and other collection blocks. See Custom Blocks to define the block type. Only the fetcher below is block-specific.
// packages/helpers — client-side, best-effort (CORS-permitting feeds).
export function rssFetcher() {
return async function fetchItems(block, { start, size }) {
let entries = [];
try {
const res = await fetch(block.feedUrl);
entries = parseRssEntries(await res.text()); // → [{ '@id': link, title, description, pubDate }]
} catch {
return { items: [], total: 0 }; // CORS / parse failure → empty feed, never throws
}
if (block.count != null) entries = entries.slice(0, block.count);
return { items: entries.slice(start, start + size), total: entries.length };
};
}
// Dependency-free parse so it runs in the browser AND node (no DOMParser):
function parseRssEntries(xml) {
const out = [];
const tag = (b, n) => (new RegExp(`<${n}\\b[^>]*>([\\s\\S]*?)<\\/${n}>`, 'i').exec(b) || [])[1];
for (const m of xml.matchAll(/<item\b[^>]*>([\s\S]*?)<\/item>/gi)) {
const b = m[1];
out.push({ '@id': tag(b, 'link') || '', title: tag(b, 'title') || '', description: tag(b, 'description') || '', pubDate: tag(b, 'pubDate') });
}
return out;
}// One fetchItems map, keyed by @type, holds every fetch-based block you use.
const fetchItems = {
listing: ploneFetchItems({ apiUrl, contextPath }),
rssFeed: rssFetcher(), // ← this block — just another entry
};
// Call this on each region you render (the list of block ids in that region).
const { items } = await expandListingBlocks(regionBlockIds, {
blocks, fetchItems, itemTypeField: 'variation',
});
items.forEach((item) => renderBlock(item)); // your normal per-block renderer