diff --git a/common/styleguide.tsx b/common/styleguide.tsx index 70c3fc0c5..5677fa408 100644 --- a/common/styleguide.tsx +++ b/common/styleguide.tsx @@ -108,7 +108,10 @@ export function A({ const linkStyles = tw`font-sans text-black underline decoration-pewter dark:text-white dark:decoration-palette-gray5`; const linkHoverStyles = tw`decoration-primary-dark`; - if ((target === '_self' && !href.startsWith('#')) || href.startsWith('/')) { + if ( + (target === '_self' && !href.startsWith('#')) || + (target !== '_blank' && href.startsWith('/')) + ) { const passedStyle = StyleSheet.flatten(style); return ( ; count?: number; queryParams?: Query; + rss?: string; }; -export default function HomeSection({ data, title, Icon, count = 8, queryParams = {} }: Props) { +export default function HomeSection({ + data, + title, + Icon, + rss, + count = 8, + queryParams = {}, +}: Props) { const hashLink = title.replace(/\s/g, '').toLowerCase(); return ( @@ -35,6 +44,18 @@ export default function HomeSection({ data, title, Icon, count = 8, queryParams hoverStyle={tw`text-palette-gray4 dark:text-palette-gray5`}> {title} + {rss && ( + + + + + + }> + RSS feed + + )} {renderLibs(data, count)}

diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index a54c2ed45..6c000e309 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -1247,3 +1247,27 @@ export function FundingGitHub({ width = 24, height = 24, style }: IconProps) { ); } + +export function RSS({ width = 24, height = 24, style }: IconProps) { + return ( + + + + + + ); +} diff --git a/pages/rss/added.xml.ts b/pages/rss/added.xml.ts new file mode 100644 index 000000000..9cc3c1c49 --- /dev/null +++ b/pages/rss/added.xml.ts @@ -0,0 +1,47 @@ +import { type NextPageContext } from 'next'; + +import { type APIResponseType } from '~/types'; +import { generateLibrariesRss } from '~/util/rss'; +import { ssrFetch } from '~/util/SSRFetch'; + +const RSS_LIMIT = 20; + +export async function getServerSideProps(ctx: NextPageContext) { + const { res } = ctx; + + if (!res) { + return { notFound: true }; + } + + try { + const response = await ssrFetch( + '/libraries', + { order: 'added', limit: RSS_LIMIT.toString() }, + ctx + ); + + const { libraries } = (await response.json()) as APIResponseType; + + res.statusCode = 200; + res.write( + generateLibrariesRss( + 'Recently Added', + 'The recently added packages to the directory', + libraries + ) + ); + } catch { + res.statusCode = 500; + res.write('Error: Cannot generate RSS feed'); + } + + res.setHeader('Content-Type', 'text/xml; charset=utf-8'); + res.setHeader('Cache-Control', 'public, s-maxage=600, stale-while-revalidate=300'); + res.end(); + + return { props: {} }; +} + +export default function RSSFeedAdded() { + return null; +} diff --git a/pages/rss/updated.xml.ts b/pages/rss/updated.xml.ts new file mode 100644 index 000000000..0c6163927 --- /dev/null +++ b/pages/rss/updated.xml.ts @@ -0,0 +1,47 @@ +import { type NextPageContext } from 'next'; + +import { type APIResponseType } from '~/types'; +import { generateLibrariesRss } from '~/util/rss'; +import { ssrFetch } from '~/util/SSRFetch'; + +const RSS_LIMIT = 20; + +export async function getServerSideProps(ctx: NextPageContext) { + const { res } = ctx; + + if (!res) { + return { notFound: true }; + } + + try { + const response = await ssrFetch( + '/libraries', + { order: 'updated', limit: RSS_LIMIT.toString() }, + ctx + ); + + const { libraries } = (await response.json()) as APIResponseType; + + res.statusCode = 200; + res.write( + generateLibrariesRss( + 'Just Updated', + 'The recently updated packages in the directory', + libraries + ) + ); + } catch { + res.statusCode = 500; + res.write('Error: Cannot generate RSS feed'); + } + + res.setHeader('Content-Type', 'text/xml; charset=utf-8'); + res.setHeader('Cache-Control', 'public, s-maxage=600, stale-while-revalidate=300'); + res.end(); + + return { props: {} }; +} + +export default function RSSFeedAdded() { + return null; +} diff --git a/scenes/HomeScene.tsx b/scenes/HomeScene.tsx index 36fefb9f7..dc537a7c7 100644 --- a/scenes/HomeScene.tsx +++ b/scenes/HomeScene.tsx @@ -167,12 +167,14 @@ export default function HomeScene({ title="Just updated" Icon={Calendar} queryParams={{ order: 'updated' }} + rss="/rss/updated.xml" /> diff --git a/tsconfig.json b/tsconfig.json index 6d57f5398..5fa8630a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ "paths": { "~/*": ["./*"] }, - "typeRoots": ["types", "node_modules/@types"] + "typeRoots": ["types", "node_modules/@types"], + "esModuleInterop": true }, "exclude": ["node_modules"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] diff --git a/util/rss.ts b/util/rss.ts new file mode 100644 index 000000000..5519233fc --- /dev/null +++ b/util/rss.ts @@ -0,0 +1,31 @@ +import { type LibraryType } from '~/types'; + +export function generateLibrariesRss(title: string, description: string, libraries: LibraryType[]) { + return ` + + + React Native Directory - ${title} + https://reactnative.directory/ + ${description} + ${libraries + .map( + (lib, i) => ` + + ${lib.npmPkg} + + https://reactnative.directory/package/${lib.npmPkg} + ${ + lib.github.topics?.length + ? lib.github.topics + .slice(0, 5) + .map(topic => `${topic}`) + .join('') + : '' + } + ${title === 'Just Updated' ? `${new Date(lib.github.stats.updatedAt).toUTCString()}` : `${`${i}-${lib.npmPkg}`}`} + ` + ) + .join('')} + +`; +}