feat: rss page component

This commit is contained in:
DIYgod 2024-01-24 19:36:31 +08:00
parent 59290dafc2
commit 445ef93102
No known key found for this signature in database
6 changed files with 157 additions and 0 deletions

View File

@ -144,6 +144,7 @@
"remark-rehype": "11.1.0",
"remark-stringify": "11.0.0",
"remove-markdown": "0.5.0",
"rss-parser": "3.13.0",
"serialize-javascript": "6.0.2",
"sharp": "0.32.6",
"sortablejs": "1.15.2",

View File

@ -351,6 +351,9 @@ dependencies:
remove-markdown:
specifier: 0.5.0
version: 0.5.0
rss-parser:
specifier: 3.13.0
version: 3.13.0
serialize-javascript:
specifier: 6.0.2
version: 6.0.2
@ -7424,6 +7427,10 @@ packages:
tapable: 2.2.1
dev: true
/entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
dev: false
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@ -12852,6 +12859,13 @@ packages:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
dev: false
/rss-parser@3.13.0:
resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==}
dependencies:
entities: 2.2.0
xml2js: 0.5.0
dev: false
/run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
@ -12911,6 +12925,10 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
/saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@ -14651,6 +14669,19 @@ packages:
engines: {node: '>=18'}
dev: false
/xml2js@0.5.0:
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
engines: {node: '>=4.0.0'}
dependencies:
sax: 1.3.0
xmlbuilder: 11.0.1
dev: false
/xmlbuilder@11.0.1:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'}
dev: false
/xmlbuilder@15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}

View File

@ -0,0 +1,37 @@
import Parser from "rss-parser"
import { cacheGet } from "~/lib/redis.server"
import { getQuery, NextServerResponse } from "~/lib/server-helper"
const parser = new Parser()
export async function GET(req: Request): Promise<Response> {
let { url } = getQuery(req)
const res = new NextServerResponse()
if (!url) {
return res.status(400).json({ error: "Bad request." })
}
try {
const parsed = (await cacheGet({
key: `rss-parser/${url}`,
expireTime: 60 * 60 * 1,
getValueFun: async () => {
let feed: Parser.Output<{
[key: string]: any
}> | null = null
try {
feed = await parser.parseURL(url)
} catch (error) {}
return feed
},
})) as Parser.Output<{
[key: string]: any
}> | null
return res.status(parsed ? 200 : 404).json({ data: parsed ?? null })
} catch (error: any) {
console.error(error)
return res.status(400).json({ error: error?.message ?? "Bad request." })
}
}

84
src/components/ui/RSS.tsx Normal file
View File

@ -0,0 +1,84 @@
import Parser from "rss-parser"
import { Image } from "~/components/ui/Image"
import dayjs from "~/lib/dayjs"
import { SITE_URL } from "~/lib/env"
function extractImgSrc(htmlString: string) {
const regex = /<img[^>]+src="([^">]+)"/g
const matches = htmlString.match(regex)
if (matches?.[0]) {
const src = matches[0].match(/src="([^">]+)"/)?.[1]
return src
}
return null
}
export default async function RSS({
src,
limit,
}: {
src: string
limit?: number
}) {
let feed: Parser.Output<{
[key: string]: any
}> | null = null
if (src) {
try {
feed = (
await (await fetch(`${SITE_URL}/api/rss-parser?url=${src}`)).json()
).data
} catch (error) {}
}
if (limit && feed?.items) {
feed = {
...feed,
items: feed.items.slice(0, +limit),
}
}
return (
<div className="grid gap-3 grid-cols-1 sm:grid-cols-3 sm:gap-6">
{feed?.items?.map((item) => {
const img = extractImgSrc(item.content || item.description || "")
console.log(img, img?.startsWith("https://"))
return (
<a
key={item.guid || item.link}
href={item.link}
className="rounded-2xl border aspect-square relative flex overflow-hidden group text-sm"
>
{img?.startsWith("https://") && (
<Image
src={img}
alt={item.title || item.description || ""}
width={300}
height={300}
className="rounded-2xl object-cover"
/>
)}
<span className="text-white text-center absolute bottom-0 bg-gradient-to-b from-transparent to-black/70 w-full h-16 flex flex-col items-center justify-center group-hover:h-full transition-[height] hover:backdrop-blur">
<span className="font-bold">{item.title}</span>
<span className="hidden group-hover:block mt-4">
{item.contentSnippet}
</span>
<span className="text-white">
{dayjs
.duration(
dayjs(item.isoDate).diff(dayjs(), "minute"),
"minute",
)
.humanize()}{" "}
ago
</span>
</span>
</a>
)
})}
</div>
)
}

View File

@ -60,6 +60,7 @@ const GithubRepo = dynamic(() => import("~/components/ui/GithubRepo"))
const XLogPost = dynamic(() => import("~/components/ui/XLogPost"))
const APlayer = dynamic(() => import("~/components/ui/APlayer"))
const DPlayer = dynamic(() => import("~/components/ui/DPlayer"))
const RSS = dynamic(() => import("~/components/ui/RSS"))
refractor.alias("html", ["svelte", "vue"])
refractor.register(langTsx)
@ -169,6 +170,7 @@ export const renderPageContent = (content: string, strictMode?: boolean) => {
"xlog-post": XLogPost,
// @ts-expect-error
style: Style,
rss: RSS,
},
ignoreInvalidStyle: true,
jsx,

View File

@ -14,6 +14,7 @@ const scheme = deepmerge(defaultSchema, {
"source",
"mermaid",
"aside",
"rss",
...["svg", "path", "circle"],
],
attributes: {
@ -45,6 +46,7 @@ const scheme = deepmerge(defaultSchema, {
],
path: ["d", "fill"],
circle: ["cx", "cy", "r", "fill"],
rss: ["src", "limit"],
},
protocols: {
href: ["magnet", "ed2k"],