feat: rss page component
This commit is contained in:
parent
59290dafc2
commit
445ef93102
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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." })
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue