From 14f0e4df180503c1d67b636ad3b3a003a9f33267 Mon Sep 17 00:00:00 2001 From: ivgo Date: Mon, 17 Jul 2023 12:16:25 +0200 Subject: [PATCH] Allow more RSS Feed customization Signed-off-by: ivgo --- .changeset/famous-teachers-breathe.md | 9 ++ .../home/backstage-plugin-home-rss/README.md | 5 + .../backstage-plugin-home-rss/package.json | 2 +- .../src/RSSCard/Content.tsx | 96 ++++++++----------- .../src/RSSCard/index.ts | 1 + .../src/RSSCard/types.ts | 13 +++ .../backstage-plugin-home-rss/src/index.ts | 1 + .../backstage-plugin-home-rss/src/plugin.ts | 9 +- 8 files changed, 73 insertions(+), 63 deletions(-) create mode 100644 .changeset/famous-teachers-breathe.md diff --git a/.changeset/famous-teachers-breathe.md b/.changeset/famous-teachers-breathe.md new file mode 100644 index 000000000..3848e3c96 --- /dev/null +++ b/.changeset/famous-teachers-breathe.md @@ -0,0 +1,9 @@ +--- +'@roadiehq/backstage-plugin-home-rss': minor +--- + +Allow more customization for RSS Feed component and minor tweaks. + +- Enable/disable paging if required +- Allow modifying the rows for the table with any custom setup +- Minor changes in the component to work better with new custom homepage diff --git a/plugins/home/backstage-plugin-home-rss/README.md b/plugins/home/backstage-plugin-home-rss/README.md index b8f02cbe1..1330c6f61 100644 --- a/plugins/home/backstage-plugin-home-rss/README.md +++ b/plugins/home/backstage-plugin-home-rss/README.md @@ -39,9 +39,14 @@ export const HomePage = () => { ... ); }; ``` + +## Customization + +It's also possible to customize each row in the RSS Feed table. You have to use the property `rowRenderer`. Check the [default renderer](./src/RSSCard/Content.tsx#L65-L93) as an example. diff --git a/plugins/home/backstage-plugin-home-rss/package.json b/plugins/home/backstage-plugin-home-rss/package.json index e4c847894..c02b805dc 100644 --- a/plugins/home/backstage-plugin-home-rss/package.json +++ b/plugins/home/backstage-plugin-home-rss/package.json @@ -35,7 +35,7 @@ "@backstage/config": "^1.0.8", "@backstage/core-components": "^0.13.2", "@backstage/core-plugin-api": "^1.5.2", - "@backstage/plugin-home": "^0.5.3", + "@backstage/plugin-home-react": "^0.1.0", "@material-ui/core": "^4.12.3", "@material-ui/lab": "^4.0.0-alpha.60", "cross-fetch": "^3.1.4", diff --git a/plugins/home/backstage-plugin-home-rss/src/RSSCard/Content.tsx b/plugins/home/backstage-plugin-home-rss/src/RSSCard/Content.tsx index 46e18332e..c87084be3 100644 --- a/plugins/home/backstage-plugin-home-rss/src/RSSCard/Content.tsx +++ b/plugins/home/backstage-plugin-home-rss/src/RSSCard/Content.tsx @@ -15,10 +15,10 @@ */ import React from 'react'; -import { ErrorPanel, Table } from '@backstage/core-components'; -import { RSSContentProps } from './types'; +import { ErrorPanel, Link, Table } from '@backstage/core-components'; +import { DataItem, RSSContentProps } from './types'; import { useAsync } from 'react-use'; -import { Box, Typography, Link, makeStyles } from '@material-ui/core'; +import { Typography, makeStyles } from '@material-ui/core'; import { DateTime } from 'luxon'; import { Skeleton } from '@material-ui/lab'; @@ -34,10 +34,6 @@ const useStyles = makeStyles(theme => ({ }, })); -type DataItem = { - title: any; -}; - const columns = [ { title: '', @@ -54,45 +50,27 @@ const skeletonDataItem = { ), }; -const skeletonData = [ - skeletonDataItem, - skeletonDataItem, - skeletonDataItem, - skeletonDataItem, - skeletonDataItem, -]; +const skeletonData = Array(6).fill(skeletonDataItem); /** - * A component to render a RSS feed + * A component to render an RSS feed * * @public */ export const Content = (props: RSSContentProps) => { const parser = new DOMParser(); const classes = useStyles(); + const { feedURL, paging = true, rowRenderer } = props; - const { value, loading, error } = useAsync(async () => { - const headers = new Headers({ - Accept: 'application/rss+xml', - }); - - const response = await fetch(props.feedURL, { headers: headers }); - - const body = await response.text(); - const feedData = parser.parseFromString(body, 'application/xml'); - const title = feedData.querySelector('title')?.textContent || undefined; - - const items = feedData.querySelectorAll('item'); + const defaultRow = (items: NodeListOf): DataItem[] => { const result: DataItem[] = []; items.forEach(item => { const link = item.querySelector('link')?.textContent; const itemTitle = item.querySelector('title')?.textContent; const pubDate = item.querySelector('pubDate')?.textContent; - let pubDateString: string | undefined = undefined; - if (pubDate) { - const publishedAt = DateTime.fromRFC2822(pubDate); - pubDateString = publishedAt.toLocaleString(DateTime.DATE_MED); - } + const pubDateString = pubDate + ? DateTime.fromRFC2822(pubDate).toLocaleString(DateTime.DATE_MED) + : undefined; if (link && itemTitle) { const itemComponent = ( @@ -100,7 +78,7 @@ export const Content = (props: RSSContentProps) => { {pubDateString} - + {itemTitle} @@ -111,34 +89,38 @@ export const Content = (props: RSSContentProps) => { }); } }); - return { data: result, title }; + return result; + }; + + const { value = skeletonData, error } = useAsync(async () => { + const headers = new Headers({ + Accept: 'application/rss+xml', + }); + + const response = await fetch(feedURL, { headers: headers }); + + const body = await response.text(); + const feedData = parser.parseFromString(body, 'application/xml'); + const items = feedData.querySelectorAll('item'); + + return rowRenderer ? await rowRenderer(items) : defaultRow(items); }, []); + if (error) { return ; } - let tableData: DataItem[] = []; - let title; - if (loading) { - tableData = skeletonData; - title = ; - } else if (value) { - tableData = value.data; - title = value.title; - } + return ( - - - +
); }; diff --git a/plugins/home/backstage-plugin-home-rss/src/RSSCard/index.ts b/plugins/home/backstage-plugin-home-rss/src/RSSCard/index.ts index a4960ba78..04ed58eca 100644 --- a/plugins/home/backstage-plugin-home-rss/src/RSSCard/index.ts +++ b/plugins/home/backstage-plugin-home-rss/src/RSSCard/index.ts @@ -15,3 +15,4 @@ */ export { Content } from './Content'; +export { type DataItem } from './types'; diff --git a/plugins/home/backstage-plugin-home-rss/src/RSSCard/types.ts b/plugins/home/backstage-plugin-home-rss/src/RSSCard/types.ts index c00c82880..7fc3209b0 100644 --- a/plugins/home/backstage-plugin-home-rss/src/RSSCard/types.ts +++ b/plugins/home/backstage-plugin-home-rss/src/RSSCard/types.ts @@ -14,6 +14,15 @@ * limitations under the License. */ +/** + * Element displayed in each row for the RSS Feed {@link Content}. + * + * @public + */ +export type DataItem = { + title: JSX.Element; +}; + /** * Props for RSS content component {@link Content}. * @@ -21,4 +30,8 @@ */ export type RSSContentProps = { feedURL: string; + paging?: boolean; + rowRenderer?: ( + items: NodeListOf, + ) => DataItem[] | Promise; }; diff --git a/plugins/home/backstage-plugin-home-rss/src/index.ts b/plugins/home/backstage-plugin-home-rss/src/index.ts index 333df37be..48b056b7e 100644 --- a/plugins/home/backstage-plugin-home-rss/src/index.ts +++ b/plugins/home/backstage-plugin-home-rss/src/index.ts @@ -15,3 +15,4 @@ */ export { HomePageRSS, rssPlugin } from './plugin'; +export { type DataItem } from './RSSCard'; diff --git a/plugins/home/backstage-plugin-home-rss/src/plugin.ts b/plugins/home/backstage-plugin-home-rss/src/plugin.ts index b917fdfba..a444623f5 100644 --- a/plugins/home/backstage-plugin-home-rss/src/plugin.ts +++ b/plugins/home/backstage-plugin-home-rss/src/plugin.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { createCardExtension } from '@backstage/plugin-home'; +import { createCardExtension } from '@backstage/plugin-home-react'; import { createPlugin } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; +import { RSSContentProps } from './RSSCard/types'; /** @public */ export const rssPlugin = createPlugin({ @@ -32,11 +33,9 @@ export const rssPlugin = createPlugin({ * @public */ export const HomePageRSS = rssPlugin.provide( - createCardExtension<{ - feedURL: string; - }>({ + createCardExtension({ name: 'HomePageRSS', - title: '', + title: 'RSS Feed', components: () => import('./RSSCard'), }), );