feat(blog): rss added

This commit is contained in:
Junior Garcia 2023-08-01 11:16:50 -03:00
parent cfd19415ea
commit 59ef7bc6b6
7 changed files with 180 additions and 0 deletions

View File

@ -0,0 +1,37 @@
import Rss from "rss";
import {allBlogPosts} from "contentlayer/generated";
import {siteConfig} from "@/config/site";
import {allCoreContent} from "@/libs/contentlayer";
export async function GET() {
const feed = new Rss({
title: siteConfig.name,
description: siteConfig.description,
feed_url: `${siteConfig.siteUrl}/feed.xml`,
site_url: siteConfig.siteUrl,
webMaster: `${siteConfig.author} <${siteConfig.email}>`,
managingEditor: `${siteConfig.author} <${siteConfig.email}>`,
language: "en-US",
});
allCoreContent(allBlogPosts).forEach((post) => {
const author = post.author ? post.author : siteConfig.author;
feed.item({
title: post.title,
description: post.description ?? "",
url: `${siteConfig.siteUrl}/blog/${post.slug}`,
guid: `${siteConfig.siteUrl}/blog/${post.slug}`,
date: post.date,
author: `${author} <${siteConfig.email}>`,
categories: post.tags ?? [],
});
});
return new Response(feed.xml(), {
headers: {
"Content-Type": "application/xml",
},
});
}

View File

@ -4,6 +4,9 @@ export const siteConfig = {
name: "NextUI - Beautiful, fast and modern React UI Library",
description: "Make beautiful websites regardless of your design experience.",
ogImage: "https://nextui.org/twitter-cards/nextui.jpeg",
author: "Junior Garcia",
email: "jrgarciadev@gmail.com",
siteUrl: "https://nextui.org",
creator: "@getnextui",
openGraph: {
type: "website",

View File

@ -3,6 +3,7 @@ title: "Introducing NextUI Version 2.0"
description: "Discover NextUI v2.0 TailwindCSS integration, enhanced theming, improved animations, and more."
date: "2023-07-31"
image: "/blog/nextuiv2.jpg"
tags: ["nextui", "tailwindcss", "react", "nextjs", "react-server-components"]
author:
name: "Junior Garcia"
username: "@jrgarciadev"

View File

@ -48,6 +48,7 @@ export const BlogPost = defineDocumentType(() => ({
title: {type: "string", required: true},
description: {type: "string", required: true},
date: {type: "date", required: true},
tags: { type: 'list', of: { type: 'string' } },
author: {type: "nested",of: AuthorProperties, required: false},
image: {type: "string", required: false},
},

View File

@ -0,0 +1,103 @@
import type {Document, MDX} from "contentlayer/core";
import {slug} from "github-slugger";
export type MDXDocument = Document & {body: MDX};
export type MDXDocumentDate = MDXDocument & {
date: string;
};
export type MDXBlog = MDXDocumentDate & {
tags?: string[];
draft?: boolean;
};
export type MDXAuthor = MDXDocument & {
name: string;
};
export function dateSortDesc(a: string, b: string) {
if (a > b) return -1;
if (a < b) return 1;
return 0;
}
export function sortedBlogPost(allBlogs: MDXDocumentDate[]) {
return allBlogs.sort((a, b) => dateSortDesc(a.date, b.date));
}
type ConvertUndefined<T> = OrNull<{
[K in keyof T as undefined extends T[K] ? K : never]-?: T[K];
}>;
type OrNull<T> = {[K in keyof T]: Exclude<T[K], undefined> | null};
type PickRequired<T> = {
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
};
type ConvertPick<T> = ConvertUndefined<T> & PickRequired<T>;
/**
* A typesafe omit helper function
* @example pick(content, ['title', 'description'])
*
* @param {Obj} obj
* @param {Keys[]} keys
* @return {*} {ConvertPick<{ [K in Keys]: Obj[K] }>}
*/
export const pick = <Obj, Keys extends keyof Obj>(
obj: Obj,
keys: Keys[],
): ConvertPick<{[K in Keys]: Obj[K]}> => {
return keys.reduce((acc, key) => {
acc[key] = obj[key] ?? null;
return acc;
}, {} as any);
};
/**
* A typesafe omit helper function
* @example omit(content, ['body', '_raw', '_id'])
*
* @param {Obj} obj
* @param {Keys[]} keys
* @return {*} {Omit<Obj, Keys>}
*/
export const omit = <Obj, Keys extends keyof Obj>(obj: Obj, keys: Keys[]): Omit<Obj, Keys> => {
const result = Object.assign({}, obj);
keys.forEach((key) => {
delete result[key];
});
return result;
};
export type CoreContent<T> = Omit<T, "body" | "_raw" | "_id">;
export function coreContent<T extends MDXDocument>(content: T) {
return omit(content, ["body", "_raw", "_id"]);
}
export function allCoreContent<T extends MDXDocument>(contents: T[]) {
return contents.map((c) => coreContent(c)).filter((c) => !("draft" in c && c.draft === true));
}
export async function getAllTags(allBlogs: MDXBlog[]) {
const tagCount: Record<string, number> = {};
allBlogs.forEach((file) => {
if (file.tags && file.draft !== true) {
file.tags.forEach((tag) => {
const formattedTag = slug(tag);
if (formattedTag in tagCount) {
tagCount[formattedTag] += 1;
} else {
tagCount[formattedTag] = 1;
}
});
}
});
return tagCount;
}

View File

@ -73,6 +73,7 @@
"remark-autolink-headings": "^6.0.1",
"remark-gfm": "^3.0.1",
"remark-slug": "^6.0.0",
"rss": "^1.2.2",
"scroll-into-view-if-needed": "3.0.10",
"sharp": "^0.32.1",
"shelljs": "^0.8.4",
@ -97,6 +98,7 @@
"@types/react": "18.2.8",
"@types/react-dom": "18.2.4",
"@types/refractor": "^3.0.2",
"@types/rss": "^0.0.30",
"@types/shelljs": "^0.8.9",
"@types/uuid": "^8.3.1",
"algoliasearch": "^4.10.3",

33
pnpm-lock.yaml generated
View File

@ -438,6 +438,9 @@ importers:
remark-slug:
specifier: ^6.0.0
version: 6.0.0
rss:
specifier: ^1.2.2
version: 1.2.2
scroll-into-view-if-needed:
specifier: 3.0.10
version: 3.0.10
@ -505,6 +508,9 @@ importers:
'@types/refractor':
specifier: ^3.0.2
version: 3.0.2
'@types/rss':
specifier: ^0.0.30
version: 0.0.30
'@types/shelljs':
specifier: ^0.8.9
version: 0.8.9
@ -10541,6 +10547,10 @@ packages:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: false
/@types/rss@0.0.30:
resolution: {integrity: sha512-RnWs98qajbcAZqie6EWYraJ2N+1Q1Wy9KN7HcVPJ//sYJGVjLjvkChZdeQPwf88xAcNUCcLXt6Zz3kiid7s/yw==}
dev: true
/@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
@ -20309,10 +20319,22 @@ packages:
brorand: 1.1.0
dev: true
/mime-db@1.25.0:
resolution: {integrity: sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w==}
engines: {node: '>= 0.6'}
dev: false
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
/mime-types@2.1.13:
resolution: {integrity: sha512-ryBDp1Z/6X90UvjUK3RksH0IBPM137T7cmg4OgD5wQBojlAiUwuok0QeELkim/72EtcYuNlmbkrcGuxj3Kl0YQ==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.25.0
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
@ -23657,6 +23679,13 @@ packages:
fsevents: 2.3.2
dev: true
/rss@1.2.2:
resolution: {integrity: sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==}
dependencies:
mime-types: 2.1.13
xml: 1.0.1
dev: false
/rsvp@4.8.5:
resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==}
engines: {node: 6.* || >= 7.*}
@ -26821,6 +26850,10 @@ packages:
engines: {node: '>=12'}
dev: true
/xml@1.0.1:
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
dev: false
/xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
dev: true