Merge pull request #30 from yougotwill/rss_feed

RSS integration
This commit is contained in:
William Grant 2021-09-03 16:11:36 +02:00 committed by GitHub
commit f92def3cd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 17 deletions

1
.gitignore vendored
View file

@ -16,6 +16,7 @@
# production
/build
/public/rss
# misc
.DS_Store

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -10 44 44">
<path
d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z" />
</svg>

After

Width:  |  Height:  |  Size: 480 B

View file

@ -7,6 +7,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { v4 as uuid } from 'uuid';
import GithubSVG from '../../assets/svgs/socials/github.svg';
import RedditSVG from '../../assets/svgs/socials/reddit.svg';
import RssSVG from '../../assets/svgs/socials/rss.svg';
import SessionSVG from '../../assets/svgs/socials/session.svg';
import TelegramSVG from '../../assets/svgs/socials/telegram.svg';
import TwitterSVG from '../../assets/svgs/socials/twitter.svg';
@ -77,10 +78,10 @@ export function SideMenuInner() {
</Contained>
{isDesktop ? (
<div className="px-6 pb-3">
<div className="px-3 pb-3">
<SocialsRow />
<div className="flex items-center justify-between font-medium whitespace-nowrap">
<div className="flex items-center justify-between px-3 font-medium whitespace-nowrap">
<a
href="/downloads/oxen-media-kit.zip"
target="_blank"
@ -123,42 +124,45 @@ const SocialsRow = () => {
return (
<div
className={classNames(
'flex pt-3 pb-3',
'flex pt-4 pb-4',
isTablet ? 'justify-start space-x-5 px-6' : 'justify-between',
)}
>
<a href="https://t.me/Oxen_Community" target="_blank" rel="noreferrer">
<TelegramSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<TelegramSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
<a href="https://twitter.com/Oxen_io" target="_blank" rel="noreferrer">
<TwitterSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<TwitterSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
<a href="https://github.com/oxen-io" target="_blank" rel="noreferrer">
<GithubSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<GithubSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
{/* <a
href="https://discord.com/invite/67GXfD6"
target="_blank"
rel="noreferrer"
>
<DiscordSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<DiscordSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a> */}
<a
href="https://www.youtube.com/channel/UCN7LL0dEffQ7FSjbY5wwlnw"
target="_blank"
rel="noreferrer"
>
<YouTubeSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<YouTubeSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
<a
href="https://www.reddit.com/r/oxen_io/"
target="_blank"
rel="noreferrer"
>
<RedditSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<RedditSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
<a href="https://sessiongroups.com/" target="_blank" rel="noreferrer">
<SessionSVG className="h-12 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current tablet:h-10 hover:bg-primary hover:text-secondary border-primary" />
<SessionSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
<a href="/feed" target="_self" rel="noreferrer">
<RssSVG className="h-10 placeholder-current duration-300 border rounded-full cursor-pointer fill-current stroke-current hover:bg-primary hover:text-secondary border-primary" />
</a>
</div>
);

View file

@ -34,6 +34,19 @@ const nextConfig = {
},
];
},
async rewrites() {
return [
{
source: '/feed',
destination: '/api/feed/rss',
},
{
// The /:slug part is a generic parameter handler to catch all other cases
source: '/feed/:slug',
destination: '/api/feed/:slug',
},
];
},
};
module.exports = withPlugins([withFonts, withSvgr], nextConfig);

View file

@ -14,6 +14,7 @@
},
"dependencies": {
"@ant-design/icons": "^4.2.2",
"@contentful/rich-text-html-renderer": "^15.0.0",
"@contentful/rich-text-plain-text-renderer": "^15.0.0",
"@contentful/rich-text-react-renderer": "^15.0.0",
"@tailwindcss/aspect-ratio": "^0.2.0",
@ -29,6 +30,7 @@
"date-fns": "^2.23.0",
"dotenv": "^8.2.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"feed": "^4.2.2",
"global": "^4.4.0",
"groq": "^1.149.16",
"himalaya": "^1.1.0",

View file

@ -24,6 +24,27 @@ export default class CustomDocument extends Document<any> {
sizes="16x16"
href="/favicon-16x16.png"
/>
<link
key="rss-feed"
rel="alternative"
type="application/rss+xml"
title="RSS feed for just-be.dev"
href="/feed"
/>
<link
key="atom-feed"
rel="alternative"
type="application/atom+xml"
title="Atom feed for just-be.dev"
href="/feed/atom"
/>
<link
key="json-feed"
rel="alternative"
type="application/feed+json"
title="JSON feed for just-be.dev"
href="/feed/json"
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="theme-color" content="#ffffff" />

8
pages/api/feed/atom.ts Normal file
View file

@ -0,0 +1,8 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { readFileSync } from 'fs';
export default async (req: NextApiRequest, res: NextApiResponse) => {
res.statusCode = 200;
res.setHeader('content-type', 'application/atom+xml');
res.end(readFileSync('./public/rss/atom.xml'));
};

8
pages/api/feed/json.ts Normal file
View file

@ -0,0 +1,8 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { readFileSync } from 'fs';
export default async (req: NextApiRequest, res: NextApiResponse) => {
res.statusCode = 200;
res.setHeader('content-type', 'application/feed+json');
res.end(readFileSync('./public/rss/feed.json'));
};

8
pages/api/feed/rss.ts Normal file
View file

@ -0,0 +1,8 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { readFileSync } from 'fs';
export default async (req: NextApiRequest, res: NextApiResponse) => {
res.statusCode = 200;
res.setHeader('content-type', 'application/xml');
res.end(readFileSync('./public/rss/feed.xml'));
};

View file

@ -1,10 +1,15 @@
import { GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
import React from 'react';
import { IPost } from '../types/cms';
import { CMS, METADATA } from '../constants';
import { CmsApi } from '../services/cms';
import generateRSSFeed from '../utils/rss';
import { HomeHero } from '../components/HomeHero';
import { HomeHeroBubble } from '../components/HomeHeroBubble';
import { METADATA } from '../constants';
const Index = () => {
export default function Index() {
const imageURL = `${METADATA.OXEN_HOST_URL}/site-banner.png`;
return (
<>
@ -43,6 +48,35 @@ const Index = () => {
<HomeHeroBubble />
</>
);
};
}
export default Index;
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext,
) => {
if (process.env.NEXT_PUBLIC_SITE_ENV !== 'development') {
const cms = new CmsApi();
const posts: IPost[] = [];
let page = 1;
let foundAllPosts = false;
// Contentful only allows 100 at a time
while (!foundAllPosts) {
const { entries: _posts } = await cms.fetchBlogEntries(100, page);
if (_posts.length === 0) {
foundAllPosts = true;
continue;
}
posts.push(..._posts);
page++;
}
generateRSSFeed(posts);
}
return {
props: {},
revalidate: CMS.CONTENT_REVALIDATE_RATE,
};
};

57
utils/rss.ts Normal file
View file

@ -0,0 +1,57 @@
import { Feed } from 'feed';
import { mkdirSync, writeFileSync } from 'fs';
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
import { IPost } from '../types/cms';
import { METADATA } from '../constants';
const baseUrl = METADATA.OXEN_HOST_URL;
const categories = [
'Privacy',
'decentralisation',
'decentralised',
'Open Source',
'Private messaging',
'Onion routing',
'Cryptocurrency',
'Digital finance',
'Privacy Tools',
];
const date = new Date();
const feed = new Feed({
title: METADATA.TITLE_SUFFIX,
description: METADATA.SITE_META_DESCRIPTION,
id: baseUrl,
link: baseUrl,
language: 'en', // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
image: `${baseUrl}/android-chrome-192x192.png`,
favicon: `${baseUrl}/favicon.ico`,
copyright: `All rights reserved ${date.getFullYear()}, OPTF`,
updated: date, // optional, default = today
generator: 'Next.js using Feed for Node.js', // optional, default = 'Feed for Node.js'
feedLinks: {
rss2: `${baseUrl}/rss/feed.xml`,
json: `${baseUrl}/rss/feed.json`,
atom: `${baseUrl}/rss/atom.xml`,
},
});
categories.forEach(category => {
feed.addCategory(category);
});
export default function generateRSSFeed(posts: IPost[]) {
posts.forEach(post => {
feed.addItem({
title: post.title,
id: post.id,
link: `${baseUrl}/blog/${post.slug}`,
description: post.description,
content: documentToHtmlString(post.body),
date: new Date(post.publishedDate),
});
});
mkdirSync(`./public/rss`, { recursive: true });
writeFileSync(`./public/rss/feed.xml`, feed.rss2());
writeFileSync(`./public/rss/feed.json`, feed.json1());
writeFileSync(`./public/rss/atom.xml`, feed.atom1());
}

View file

@ -1414,6 +1414,14 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@contentful/rich-text-html-renderer@^15.0.0":
version "15.2.0"
resolved "https://registry.yarnpkg.com/@contentful/rich-text-html-renderer/-/rich-text-html-renderer-15.2.0.tgz#112bb9f748b5bc4df9d2e2065cb9f6d6e9ca085a"
integrity sha512-htcPzKiKsfINv6wkHtx38Vw0QLeGwzRTLe8dNHU2lvGZ+tgmqsgmt6C0XcpBbdon98Afl8xdToOpZdfQImI8NA==
dependencies:
"@contentful/rich-text-types" "^15.1.0"
escape-html "^1.0.3"
"@contentful/rich-text-plain-text-renderer@^15.0.0":
version "15.1.0"
resolved "https://registry.yarnpkg.com/@contentful/rich-text-plain-text-renderer/-/rich-text-plain-text-renderer-15.1.0.tgz#d6deb9f78de24b606d96dffd00abe8e2f957801e"
@ -6573,7 +6581,7 @@ escape-goat@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
escape-html@~1.0.3:
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
@ -7201,6 +7209,13 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
feed@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
dependencies:
xml-js "^1.6.11"
figgy-pudding@^3.5.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
@ -14035,7 +14050,7 @@ sass-loader@^10.0.5:
schema-utils "^3.0.0"
semver "^7.3.2"
sax@~1.2.4:
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@ -16674,6 +16689,13 @@ xdg-basedir@^4.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xml-js@^1.6.11:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
sax "^1.2.4"
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"