mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(blog): rss added
This commit is contained in:
parent
cfd19415ea
commit
59ef7bc6b6
37
apps/docs/app/feed.xml/route.ts
Normal file
37
apps/docs/app/feed.xml/route.ts
Normal 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",
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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},
|
||||
},
|
||||
|
||||
103
apps/docs/libs/contentlayer.ts
Normal file
103
apps/docs/libs/contentlayer.ts
Normal 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;
|
||||
}
|
||||
@ -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
33
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user