feat: add rss

This commit is contained in:
DIYgod 2022-11-26 14:14:06 +00:00
parent 7b834a84b1
commit ddaa1ff6bf
No known key found for this signature in database
GPG Key ID: D159328F47A80DCA
10 changed files with 218 additions and 52 deletions

View File

@ -48,6 +48,7 @@
"idb-keyval": "6.2.0",
"ioredis": "5.2.4",
"js-yaml": "4.1.0",
"jsonfeed-to-rss": "^3.0.6",
"mdast-util-to-string": "3.1.0",
"mdast-util-toc": "6.1.0",
"nanoid": "4.0.0",

View File

@ -61,6 +61,7 @@ specifiers:
idb-keyval: 6.2.0
ioredis: 5.2.4
js-yaml: 4.1.0
jsonfeed-to-rss: ^3.0.6
lint-staged: 13.0.3
mdast-util-to-string: 3.1.0
mdast-util-toc: 6.1.0
@ -141,6 +142,7 @@ dependencies:
idb-keyval: 6.2.0
ioredis: 5.2.4
js-yaml: 4.1.0
jsonfeed-to-rss: 3.0.6
mdast-util-to-string: 3.1.0
mdast-util-toc: 6.1.0
nanoid: 4.0.0
@ -357,6 +359,12 @@ packages:
to-fast-properties: 2.0.0
dev: false
/@bret/truthy/1.0.1:
resolution: {integrity: sha512-mFVhqy/yrh+BZwzqQQptGcTq5SJ+T0uhWQ/hmBkU0RFV3YN2UkcDhxvAzUuMDibOi6s66YM01BUGAi1y/92QGw==}
dependencies:
existy: 1.0.1
dev: false
/@codemirror/autocomplete/6.3.0_cjhreg6sef6bi4tr3erat4xvva:
resolution: {integrity: sha512-4jEvh3AjJZTDKazd10J6ZsCIqaYxDMCeua5ouQxY8hlFIml+nr7le0SgBhT3SIytFBmdzPK3AUhXGuW3T79nVg==}
peerDependencies:
@ -1671,6 +1679,10 @@ packages:
use-sync-external-store: 1.2.0_react@18.2.0
dev: false
/@textlint/ast-node-types/4.4.3:
resolution: {integrity: sha512-qi2jjgO6Tn3KNPGnm6B7p6QTEPvY95NFsIAaJuwbulur8iJUEenp1OnoUfiDaC/g2WPPEFkcfXpmnu8XEMFo2A==}
dev: false
/@types/bn.js/4.11.6:
resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==}
dependencies:
@ -2439,6 +2451,10 @@ packages:
hasBin: true
dev: true
/add-zero/1.0.0:
resolution: {integrity: sha512-WpPiUgy7h9Kd7NY0aTuhfx7vjub3XYbZCq1W2e/LMvUsEmYK/hz8xgFDmd0GnKpk44HXFwIFu1hEOivc+MzJ0Q==}
dev: false
/aes-js/3.0.0:
resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==}
dev: false
@ -2841,6 +2857,10 @@ packages:
text-encoding-utf-8: 1.0.2
dev: false
/boundary/1.0.1:
resolution: {integrity: sha512-AaLhxHwYVh55iOTJncV3DE5o7RakEUSSj64XXEWRTiIhlp7aDI8qR0vY/k8Uw0Z234VjZi/iG/WxfrvqYPUCww==}
dev: false
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@ -3078,6 +3098,15 @@ packages:
resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==}
dev: false
/clean-deep/3.4.0:
resolution: {integrity: sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==}
engines: {node: '>=4'}
dependencies:
lodash.isempty: 4.4.0
lodash.isplainobject: 4.0.6
lodash.transform: 4.6.0
dev: false
/clean-stack/2.2.0:
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
engines: {node: '>=6'}
@ -3236,6 +3265,16 @@ packages:
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/concat-stream/2.0.0:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 3.6.0
typedarray: 0.0.6
dev: false
/consola/2.15.3:
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
dev: true
@ -4516,6 +4555,10 @@ packages:
strip-final-newline: 3.0.0
dev: true
/existy/1.0.1:
resolution: {integrity: sha512-YeYTp9rIyoArnYubrTXHUOpuxwSdlQEctcw9zUgViSof/uJE+LlsiZb9BpIrCqLRcjiCCeF5cxX3wcfKZMqVzg==}
dev: false
/expand-template/2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@ -5549,6 +5592,23 @@ packages:
minimist: 1.2.7
dev: true
/jsonfeed-to-rss/3.0.6:
resolution: {integrity: sha512-lbEMYwPsofOBoUIW3QteZw+ogVUpWbnDZIaWgLvK8+3bZLeCW7m6luk9N2AT/Ax1plHy8nXMZo6TvcRILkDHpA==}
dependencies:
'@bret/truthy': 1.0.1
add-zero: 1.0.0
clean-deep: 3.4.0
existy: 1.0.1
lodash.get: 4.4.2
lodash.merge: 4.6.2
podcast-categories: 2.0.0
sentence-splitter: 3.2.2
striptags: 3.2.0
trim-left: 1.0.1
trim-right: 1.0.1
xmlbuilder: 15.1.1
dev: false
/jsonify/0.0.1:
resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==}
dev: false
@ -5707,18 +5767,33 @@ packages:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
dev: false
/lodash.get/4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
dev: false
/lodash.isarguments/3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
dev: false
/lodash.isempty/4.4.0:
resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
dev: false
/lodash.isplainobject/4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: false
/lodash.merge/4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.throttle/4.1.1:
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
dev: false
/lodash.transform/4.6.0:
resolution: {integrity: sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==}
dev: false
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@ -6620,6 +6695,10 @@ packages:
es-abstract: 1.20.4
dev: true
/object_values/0.1.2:
resolution: {integrity: sha512-tZgUiKLraVH+4OAedBYrr4/K6KmAQw2RPNd1AuNdhLsuz5WP3VB7WuiKBWbOcjeqqAjus2ChIIWC8dSfmg7ReA==}
dev: false
/ohmyfetch/0.4.21:
resolution: {integrity: sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==}
dependencies:
@ -6875,6 +6954,10 @@ packages:
engines: {node: '>=10.13.0'}
dev: false
/podcast-categories/2.0.0:
resolution: {integrity: sha512-WcntiTmj4ERPwaevHeR59Lc6yk37YVwksflBaCu7/J2ChjPA02J703rZMVq4gnoa+XHFHqpjK+qSc8Fv6N62EA==}
dev: false
/postcss-import/14.1.0_postcss@8.4.19:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
@ -7662,6 +7745,16 @@ packages:
dependencies:
lru-cache: 6.0.0
/sentence-splitter/3.2.2:
resolution: {integrity: sha512-hMvaodgK9Fay928uiQoTMEWjXpCERdKD2uKo7BbSyP+uWTo+wHiRjN+ZShyI99rW0VuoV4Cuw8FUmaRcnpN7Ug==}
hasBin: true
dependencies:
'@textlint/ast-node-types': 4.4.3
concat-stream: 2.0.0
object_values: 0.1.2
structured-source: 3.0.2
dev: false
/set-blocking/2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false
@ -7968,6 +8061,16 @@ packages:
acorn: 8.8.1
dev: true
/striptags/3.2.0:
resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==}
dev: false
/structured-source/3.0.2:
resolution: {integrity: sha512-Ap7JHfKgmH40SUjumqyKTHYHNZ8GvGQskP34ks0ElHCDEig+bYGpmXVksxPSrgcY9rkJqhVMzfeg5GIpZelfpQ==}
dependencies:
boundary: 1.0.1
dev: false
/style-mod/4.0.0:
resolution: {integrity: sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==}
dev: false
@ -8167,6 +8270,16 @@ packages:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/trim-left/1.0.1:
resolution: {integrity: sha512-quyxKUsLsY7+lgue8TQ6R8C4kwTe5No3/fvliPNTZd8CIK1KXFaIw+Z7SwAWiWUF5Ge5j7ppbjml3EKa02uPWA==}
engines: {node: '>=0.10.0'}
dev: false
/trim-right/1.0.1:
resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==}
engines: {node: '>=0.10.0'}
dev: false
/trough/2.1.0:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: false
@ -8246,6 +8359,10 @@ packages:
is-typedarray: 1.0.0
dev: false
/typedarray/0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
dev: false
/typescript/4.9.3:
resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==}
engines: {node: '>=4.2.0'}
@ -8949,6 +9066,11 @@ packages:
utf-8-validate: 5.0.10
dev: false
/xmlbuilder/15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}

View File

@ -27,16 +27,22 @@ export const SEOHead: React.FC<{
/>
</>
)}
<link
rel="alternate"
href="/feed/xml"
title={siteName}
type="application/rss+xml"
></link>
<link
rel="alternate"
href="/feed"
title={title}
title={siteName}
type="application/feed+json"
></link>
<link
rel="alternate"
href="/feed"
title={`Notifications on ${title}`}
title={`Notifications on ${siteName}`}
type="application/feed+json"
></link>
<link rel="icon" href={icon || `${SITE_URL}/logo.svg`}></link>

View File

@ -69,6 +69,11 @@ export const SiteHeader: React.FC<{
icon: <RssIcon className="w-4 h-4" />,
url: `/feed`,
},
{
text: "RSS",
icon: <RssIcon className="w-4 h-4" />,
url: `/feed/xml`,
},
]
return (

View File

@ -18,7 +18,8 @@ export const UniLink: React.FC<{
return <span className={className}>{children}</span>
}
const isExternal = href && (/^https?:\/\//.test(href) || href === "/feed")
const isExternal =
href && (/^https?:\/\//.test(href) || href.startsWith("/feed"))
if (isExternal) {
return (

47
src/lib/json-feed.ts Normal file
View File

@ -0,0 +1,47 @@
import { GetServerSideProps } from "next"
import { fetchGetSite } from "~/queries/site.server"
import { fetchGetPagesBySite } from "~/queries/page.server"
import { PageVisibilityEnum } from "~/lib/types"
import { getSiteLink } from "~/lib/helpers"
import { QueryClient } from "@tanstack/react-query"
import { renderPageContent } from "~/markdown"
export const getJsonFeed = async (domainOrSubdomain: string, path: string) => {
const queryClient = new QueryClient()
const site = await fetchGetSite(domainOrSubdomain, queryClient)
const pages = await fetchGetPagesBySite(
{
site: domainOrSubdomain,
type: "post",
visibility: PageVisibilityEnum.Published,
keepBody: true,
},
queryClient,
)
const link = getSiteLink({
subdomain: site.username || "",
})
return {
version: "https://jsonfeed.org/version/1",
title: site.name,
description: site.description,
icon: link + site.avatars?.[0],
home_page_url: link,
feed_url: `${link}${path}`,
items: pages.list?.map((page: any) => ({
id: page.id,
title: page.title,
content_html:
page.body?.content &&
renderPageContent(page.body?.content, true).contentHTML,
summary: page.summary?.content,
url: `${link}/${page.slug || page.id}`,
image: `${link}${page.cover}`,
date_published: page.date_published,
date_modified: page.date_updated,
tags: page.tags,
})),
}
}

View File

@ -55,7 +55,6 @@ const rehypePrism = rehypePrismGenerator(refractor)
export const renderPageContent = (
content: string,
html?: boolean,
enableToc?: boolean,
): Rendered => {
const env: MarkdownEnv = {
excerpt: "",

View File

@ -206,6 +206,7 @@ export async function getPagesBySite(
cursor?: string | null
tags?: string[]
useStat?: boolean
keepBody?: boolean
},
customUnidata?: Unidata,
) {
@ -297,7 +298,11 @@ export async function getPagesBySite(
await Promise.all(
pages?.list.map(async (page) => {
await expandPage(page, input.useStat)
delete page.body
if (!input.keepBody) {
delete page.body
}
return page
}),
)

View File

@ -1,57 +1,13 @@
import { GetServerSideProps } from "next"
import { fetchGetSite } from "~/queries/site.server"
import { fetchGetPagesBySite } from "~/queries/page.server"
import { PageVisibilityEnum } from "~/lib/types"
import { getSiteLink } from "~/lib/helpers"
import { QueryClient } from "@tanstack/react-query"
import { renderPageContent } from "~/markdown"
import { getJsonFeed } from "~/lib/json-feed"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const queryClient = new QueryClient()
ctx.res.setHeader("Content-Type", "application/feed+json; charset=utf-8")
ctx.res.setHeader("Access-Control-Allow-Methods", "GET")
ctx.res.setHeader("Access-Control-Allow-Origin", "*")
const domainOrSubdomain = ctx.params!.site as string
const site = await fetchGetSite(domainOrSubdomain, queryClient)
const pages = await fetchGetPagesBySite(
{
site: domainOrSubdomain,
type: "post",
visibility: PageVisibilityEnum.Published,
},
queryClient,
)
const link = getSiteLink({
subdomain: site.username || "",
})
ctx.res.write(
JSON.stringify({
version: "https://jsonfeed.org/version/1.1",
title: site.name,
description: site.description,
icon: link + site.avatars?.[0],
home_page_url: link,
feed_url: `${link}/feed`,
items: pages.list?.map((page: any) => ({
id: page.id,
title: page.title,
content_html:
page.body?.content &&
renderPageContent(page.body?.content, true).contentHTML,
summary: page.summary?.content,
url: `${link}/${page.slug || page.id}`,
image: `${link}${page.cover}`,
date_published: page.date_published,
date_modified: page.date_updated,
// authors: page.authors?.map((author: any) => ({
// name: author,
// })),
tags: page.tags,
})),
}),
)
ctx.res.write(JSON.stringify(await getJsonFeed(domainOrSubdomain, "/feed")))
ctx.res.end()
return {

View File

@ -0,0 +1,24 @@
import { GetServerSideProps } from "next"
// @ts-ignore
import jsonfeedToRSS from "jsonfeed-to-rss"
import { getJsonFeed } from "~/lib/json-feed"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
ctx.res.setHeader("Content-Type", "application/xml; charset=utf-8")
ctx.res.setHeader("Access-Control-Allow-Methods", "GET")
ctx.res.setHeader("Access-Control-Allow-Origin", "*")
const domainOrSubdomain = ctx.params!.site as string
ctx.res.write(
jsonfeedToRSS(await getJsonFeed(domainOrSubdomain, "/feed/xml")),
)
ctx.res.end()
return {
props: {},
}
}
const SiteFeed: React.FC = () => null
export default SiteFeed