From ca34a11ad4ce57de21ab079a8949d7effbeccadc Mon Sep 17 00:00:00 2001 From: Cory Dransfeldt Date: Sat, 16 Nov 2024 22:03:37 -0800 Subject: [PATCH] feat: numerous other pages --- .env | 1 + _redirects | 10 +- astro.config.mjs | 2 +- package-lock.json | 76 ++++++++- package.json | 4 +- src/components/BlockRenderer.astro | 35 ---- src/components/Footer.astro | 2 +- src/components/Header.astro | 2 +- src/components/Metadata.astro | 155 ++++++++++++++++++ src/components/blocks/BlockRenderer.astro | 64 ++++++++ src/components/blocks/MastodonPost.astro | 7 - src/components/blocks/NowPlaying.astro | 2 +- src/components/blocks/YouTubePlayer.astro | 9 +- src/components/blocks/banners/Mastodon.astro | 15 ++ src/components/blocks/links/AddonLinks.astro | 10 ++ .../blocks/links/PopularPosts.astro | 21 +++ src/components/blocks/links/RecentLinks.astro | 26 +++ src/components/{ => home}/Intro.astro | 0 .../{ => home}/RecentActivity.astro | 0 src/components/{ => home}/RecentPosts.astro | 2 +- src/components/media/Grid.astro | 70 ++++++++ src/components/media/ProgressBar.astro | 9 + src/components/media/music/Chart.astro | 29 ++++ src/components/media/music/Recent.astro | 41 +++++ src/components/media/watching/Hero.astro | 18 ++ src/env.d.ts | 1 + src/layouts/Layout.astro | 6 +- src/pages/.well-known/webfinger.js | 46 ++++++ src/pages/[permalink].astro | 2 +- src/pages/feeds/json/all.json.js | 22 +++ src/pages/feeds/json/books.json.js | 22 +++ src/pages/feeds/json/links.json.js | 22 +++ src/pages/feeds/json/movies.json.js | 22 +++ src/pages/feeds/json/posts.json.js | 22 +++ src/pages/feeds/rss/all.xml.js | 22 +++ src/pages/feeds/rss/books.xml.js | 22 +++ src/pages/feeds/rss/links.xml.js | 22 +++ src/pages/feeds/rss/movies.xml.js | 22 +++ src/pages/feeds/rss/posts.xml.js | 22 +++ src/pages/feeds/{ => rss}/syndication.xml.js | 4 +- src/pages/humans.txt.js | 29 ++++ src/pages/index.astro | 10 +- src/pages/music/releases.ics.js | 24 +++ src/pages/posts/[...page].astro | 2 +- src/pages/posts/[year]/[title].astro | 23 ++- src/pages/robots.txt.js | 2 +- src/styles/components/banners.css | 4 + src/styles/components/mastodon-post.css | 22 --- src/styles/index.css | 1 - src/utils/albumReleasesCalendar.js | 38 +++++ src/utils/data/analytics.js | 34 ++++ src/utils/generateJsonFeed.js | 39 +++++ src/utils/generateRssFeed.js | 45 +++++ src/utils/getPopularPosts.js | 13 ++ 54 files changed, 1074 insertions(+), 101 deletions(-) delete mode 100644 src/components/BlockRenderer.astro create mode 100644 src/components/Metadata.astro create mode 100644 src/components/blocks/BlockRenderer.astro delete mode 100644 src/components/blocks/MastodonPost.astro create mode 100644 src/components/blocks/banners/Mastodon.astro create mode 100644 src/components/blocks/links/AddonLinks.astro create mode 100644 src/components/blocks/links/PopularPosts.astro create mode 100644 src/components/blocks/links/RecentLinks.astro rename src/components/{ => home}/Intro.astro (100%) rename src/components/{ => home}/RecentActivity.astro (100%) rename src/components/{ => home}/RecentPosts.astro (93%) create mode 100644 src/components/media/Grid.astro create mode 100644 src/components/media/ProgressBar.astro create mode 100644 src/components/media/music/Chart.astro create mode 100644 src/components/media/music/Recent.astro create mode 100644 src/components/media/watching/Hero.astro create mode 100644 src/env.d.ts create mode 100644 src/pages/.well-known/webfinger.js create mode 100644 src/pages/feeds/json/all.json.js create mode 100644 src/pages/feeds/json/books.json.js create mode 100644 src/pages/feeds/json/links.json.js create mode 100644 src/pages/feeds/json/movies.json.js create mode 100644 src/pages/feeds/json/posts.json.js create mode 100644 src/pages/feeds/rss/all.xml.js create mode 100644 src/pages/feeds/rss/books.xml.js create mode 100644 src/pages/feeds/rss/links.xml.js create mode 100644 src/pages/feeds/rss/movies.xml.js create mode 100644 src/pages/feeds/rss/posts.xml.js rename src/pages/feeds/{ => rss}/syndication.xml.js (93%) create mode 100644 src/pages/humans.txt.js create mode 100644 src/pages/music/releases.ics.js delete mode 100644 src/styles/components/mastodon-post.css create mode 100644 src/utils/albumReleasesCalendar.js create mode 100644 src/utils/data/analytics.js create mode 100644 src/utils/generateJsonFeed.js create mode 100644 src/utils/generateRssFeed.js create mode 100644 src/utils/getPopularPosts.js diff --git a/.env b/.env index 706bf39..a999c62 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ ACCOUNT_ID_PLEX= +API_KEY_PLAUSIBLE= SUPABASE_URL= SUPABASE_KEY= CF_ACCOUNT_ID= diff --git a/_redirects b/_redirects index 6b2361f..6e0cb07 100644 --- a/_redirects +++ b/_redirects @@ -5,11 +5,11 @@ # feeds /feed.xml /feeds/posts 301 /follow.xml /feeds/all 301 -/feeds/posts.xml /feeds/posts 301 -/feeds/links.xml /feeds/links 301 -/feeds/books.xml /feeds/books 301 -/feeds/movies.xml /feeds/movies 301 -/feeds/all.xml /feeds/all 301 +/feeds/posts /feeds/posts.xml 301 +/feeds/links /feeds/links.xml 301 +/feeds/books /feeds/books.xml 301 +/feeds/movies /feeds/movies.xml 301 +/feeds/all /feeds/all.xml 301 /feeds/posts/ /feeds/posts 301 /feeds/links/ /feeds/links 301 /feeds/books/ /feeds/books 301 diff --git a/astro.config.mjs b/astro.config.mjs index 1b19b5b..0e37b3b 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -15,10 +15,10 @@ export default defineConfig({ }, resolve: { alias: { - "@cdransf": "node_modules/@cdransf", "@components": "/src/components", "@data": "/src/utils/data", "@layouts": "/src/layouts", + "@npm": "/node_modules", "@scripts": "/src/scripts", "@styles": "/src/styles", "@utils": "/src/utils", diff --git a/package-lock.json b/package-lock.json index c9e75be..3ad6f3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,12 @@ "@astrojs/react": "^3.6.2", "@tabler/icons-react": "^3.19.0", "astro": "^4.16.13", - "luxon": "^3.5.0" + "luxon": "^3.5.0", + "youtube-video-element": "^1.1.6" }, "devDependencies": { "@supabase/supabase-js": "^2.45.4", + "ics": "^3.8.1", "markdown-it": "^14.1.0", "markdown-it-anchor": "^9.2.0", "markdown-it-footnote": "^4.0.0", @@ -3405,6 +3407,18 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "license": "BSD-2-Clause" }, + "node_modules/ics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz", + "integrity": "sha512-UqQlfkajfhrS4pUGQfGIJMYz/Jsl/ob3LqcfEhUmLbwumg+ZNkU0/6S734Vsjq3/FYNpEcZVKodLBoe+zBM69g==", + "dev": true, + "license": "ISC", + "dependencies": { + "nanoid": "^3.1.23", + "runes2": "^1.1.2", + "yup": "^1.2.0" + } + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -5108,6 +5122,13 @@ "node": ">=6" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "dev": true, + "license": "MIT" + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", @@ -5598,6 +5619,13 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==", + "dev": true, + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5877,6 +5905,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "dev": true, + "license": "MIT" + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -5911,6 +5946,13 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "dev": true, + "license": "MIT" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -6943,6 +6985,38 @@ "stacktracey": "^2.1.8" } }, + "node_modules/youtube-video-element": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/youtube-video-element/-/youtube-video-element-1.1.6.tgz", + "integrity": "sha512-EaHyEh68twtuWn6S7cCEghJkLfaOD82wmJhczeWSTxT71yOG6lL7EXu6EAHADj6wPQJ9+lZpaos3f/Bh8Lzvjg==", + "license": "MIT" + }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", diff --git a/package.json b/package.json index 2606701..4a4aa90 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,12 @@ "@astrojs/react": "^3.6.2", "@tabler/icons-react": "^3.19.0", "astro": "^4.16.13", - "luxon": "^3.5.0" + "luxon": "^3.5.0", + "youtube-video-element": "^1.1.6" }, "devDependencies": { "@supabase/supabase-js": "^2.45.4", + "ics": "^3.8.1", "markdown-it": "^14.1.0", "markdown-it-anchor": "^9.2.0", "markdown-it-footnote": "^4.0.0", diff --git a/src/components/BlockRenderer.astro b/src/components/BlockRenderer.astro deleted file mode 100644 index 17e391d..0000000 --- a/src/components/BlockRenderer.astro +++ /dev/null @@ -1,35 +0,0 @@ ---- -import AssociatedMedia from '@components/blocks//AssociatedMedia.astro'; -import GitHub from '@components/blocks/banners/GitHub.astro'; -import Hero from '@components/blocks//Hero.astro'; -import Modal from '@components/blocks//Modal.astro'; -import YouTubePlayer from '@components/blocks//YouTubePlayer.astro'; -import { md } from '@utils/helpers.js'; - -const { block } = Astro.props; -const htmlContent = block.type === 'markdown' ? md(block.text) : ''; ---- - -{block.type === 'youtube_player' && ( - -)} - -{block.type === 'hero' && ( - -)} - -{block.type === 'markdown' && ( -
-)} - -{block.type === 'modal' && ( - -)} - -{block.type === 'associated_media' && ( - -)} - -{block.type === 'github_banner' && ( - -)} \ No newline at end of file diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 13f4c49..78720e1 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -1,5 +1,5 @@ --- -import NavLink from './nav/NavLink.astro'; +import NavLink from '@components/nav/NavLink.astro'; const { nav, updated } = Astro.props; --- diff --git a/src/components/Header.astro b/src/components/Header.astro index 7ed6a85..a1c48f4 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -1,5 +1,5 @@ --- -import Menu from './nav/Menu.astro'; +import Menu from '@components/nav/Menu.astro'; const { nav, siteName, url } = Astro.props; const isHomePage = url === '/'; diff --git a/src/components/Metadata.astro b/src/components/Metadata.astro new file mode 100644 index 0000000..f93a169 --- /dev/null +++ b/src/components/Metadata.astro @@ -0,0 +1,155 @@ +--- +const { + schema, + page, + globals, + post, + title, + description, + artist, + movie, + movies, + show, + tv, + book, + books, + genre, + year, +} = Astro.props; + +const fullUrl = `${globals.url}${page.url}`; +let pageTitle = globals.site_name; +let pageDescription = globals.site_description; +let ogImage = `${globals.cdn_url}${globals.avatar}`; + +switch (schema) { + case "blog": + pageTitle = post?.title || pageTitle; + pageDescription = post?.description || pageDescription; + ogImage = `${globals.cdn_url}${post?.image}`; + break; + case "music": + case "music-index": + case "music-period": + pageTitle = schema === "music" ? `Music / ${page.title}` : "Music"; + if (schema === "music") { + ogImage = `${globals.cdn_url}${page?.image}`; + } else if (schema === "music-index") { + ogImage = `${globals.cdn_url}${movies?.week?.artists[0]?.grid?.image}`; + } else if (schema === "music-period") { + ogImage = `${globals.cdn_url}${page?.image}`; + } + break; + case "artist": + pageTitle = `Artists / ${artist?.name}`; + pageDescription = artist?.description || pageDescription; + ogImage = `${globals.cdn_url}${artist?.image}`; + break; + case "genre": + pageTitle = `Music / ${genre?.name}`; + pageDescription = genre?.description || pageDescription; + ogImage = `${globals.cdn_url}${genre?.artists[0]?.image}`; + break; + case "book": + pageTitle = `Books / ${book?.title} by ${book?.author}`; + pageDescription = book?.review || book?.description || pageDescription; + ogImage = `${globals.cdn_url}${book?.image}`; + break; + case "books": + pageTitle = "Books"; + const overviewBook = books?.all?.find((b) => b.status === "started"); + ogImage = `${globals.cdn_url}${overviewBook?.image}`; + break; + case "books-year": + const bookYear = year?.data?.find((b) => b.status === "finished"); + ogImage = `${globals.cdn_url}${bookYear?.image}`; + break; + case "movie": + pageTitle = `Movies / ${movie?.title}`; + if (movie?.rating) { + pageTitle += ` (${movie.rating})`; + } + pageDescription = movie?.review || movie?.description || pageDescription; + ogImage = `${globals.cdn_url}${movie?.backdrop}`; + break; + case "favorite-movies": + pageTitle = "Favorite movies"; + const favoriteMovie = movies?.favorites?.[0]; + ogImage = `${globals.cdn_url}${favoriteMovie?.backdrop}`; + break; + case "show": + pageTitle = `Show / ${show?.title}`; + pageDescription = show?.review || show?.description || pageDescription; + ogImage = `${globals.cdn_url}${show?.backdrop}`; + break; + case "favorite-shows": + pageTitle = "Favorite shows"; + const favoriteShow = tv?.favorites?.[0]; + ogImage = `${globals.cdn_url}${favoriteShow?.backdrop}`; + break; + case "watching": + pageTitle = "Watching"; + const overviewMovie = movies?.recentlyWatched?.[0]; + ogImage = `${globals.cdn_url}${overviewMovie?.backdrop}`; + break; + case "page": + pageTitle = page?.title || pageTitle; + pageDescription = page?.description || pageDescription; + break; + default: + break; +} + +if (title) pageTitle = title; +if (description) pageDescription = description; +if (pageTitle !== globals.site_name && schema !== "blog") pageTitle = `${pageTitle} / ${globals.site_name}`; + +const escapedPageDescription = + pageDescription?.replace( + /["'<>&]/g, + (c) => + ({ + '"': """, + "'": "'", + "<": "<", + ">": ">", + "&": "&", + })[c] + ) || ""; +--- + + + + +{pageTitle} + + + + + + + + + + + + + + + diff --git a/src/components/blocks/BlockRenderer.astro b/src/components/blocks/BlockRenderer.astro new file mode 100644 index 0000000..c182293 --- /dev/null +++ b/src/components/blocks/BlockRenderer.astro @@ -0,0 +1,64 @@ +--- +import { fetchAllPosts } from "@data/posts.js"; +import { fetchAnalyticsData } from "@data/analytics.js"; +import { fetchLinks } from "@data/links.js"; + +import AddonLinks from '@components/blocks/links/AddonLinks.astro'; +import AssociatedMedia from '@components/blocks//AssociatedMedia.astro'; +import GitHub from '@components/blocks/banners/GitHub.astro'; +import Hero from '@components/blocks//Hero.astro'; +import Modal from '@components/blocks//Modal.astro'; +import Npm from '@components/blocks/banners/Npm.astro'; +import Rss from '@components/blocks/banners/Rss.astro'; +import YouTubePlayer from '@components/blocks//YouTubePlayer.astro'; + +import { md } from '@utils/helpers.js'; +import { getPopularPosts } from '@utils/getPopularPosts.js'; + +const analytics = await fetchAnalyticsData(); +const links = await fetchLinks(); +const posts = await fetchAllPosts(); +const popularPosts = getPopularPosts(posts, analytics); + +const { block } = Astro.props; +--- + +{block.type === 'addon_links' && ( + +)} + +{block.type === 'associated_media' && ( + +)} + +{block.type === 'divider' && ( +
+)} + +{block.type === 'github_banner' && ( + +)} + +{block.type === 'hero' && ( + +)} + +{block.type === 'markdown' && ( +
+)} + +{block.type === 'npm_banner' && ( + +)} + +{block.type === 'modal' && ( + +)} + +{block.type === 'rss_banner' && ( + +)} + +{block.type === 'youtube_player' && ( + +)} \ No newline at end of file diff --git a/src/components/blocks/MastodonPost.astro b/src/components/blocks/MastodonPost.astro deleted file mode 100644 index 06baf3e..0000000 --- a/src/components/blocks/MastodonPost.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -const { post } = Astro.props; ---- - -
-

{post.content}

-
\ No newline at end of file diff --git a/src/components/blocks/NowPlaying.astro b/src/components/blocks/NowPlaying.astro index cd629af..cfcaf08 100644 --- a/src/components/blocks/NowPlaying.astro +++ b/src/components/blocks/NowPlaying.astro @@ -1,5 +1,5 @@ --- -import { fetchNowPlaying } from '../../utils/data/nowPlaying.js'; +import { fetchNowPlaying } from '@utils/data/nowPlaying.js'; const isProduction = import.meta.env.MODE === 'production'; const nowPlayingData = await fetchNowPlaying(); diff --git a/src/components/blocks/YouTubePlayer.astro b/src/components/blocks/YouTubePlayer.astro index 98339c1..1040ecd 100644 --- a/src/components/blocks/YouTubePlayer.astro +++ b/src/components/blocks/YouTubePlayer.astro @@ -1,10 +1,7 @@ --- +import YoutubeVideo from '@npm/youtube-video-element/dist/react.js'; + const { url } = Astro.props; --- - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/blocks/banners/Mastodon.astro b/src/components/blocks/banners/Mastodon.astro new file mode 100644 index 0000000..275a536 --- /dev/null +++ b/src/components/blocks/banners/Mastodon.astro @@ -0,0 +1,15 @@ +--- +import { IconBrandMastodon } from "@tabler/icons-react"; + +const { url } = Astro.props; +--- + diff --git a/src/components/blocks/links/AddonLinks.astro b/src/components/blocks/links/AddonLinks.astro new file mode 100644 index 0000000..cdd04d4 --- /dev/null +++ b/src/components/blocks/links/AddonLinks.astro @@ -0,0 +1,10 @@ +--- +import PopularPosts from './PopularPosts.astro'; +import RecentLinks from './RecentLinks.astro'; + +const { popularPosts, links } = Astro.props; +--- + \ No newline at end of file diff --git a/src/components/blocks/links/PopularPosts.astro b/src/components/blocks/links/PopularPosts.astro new file mode 100644 index 0000000..e52f5bc --- /dev/null +++ b/src/components/blocks/links/PopularPosts.astro @@ -0,0 +1,21 @@ +--- +const { popularPosts } = Astro.props; +import { IconChartBarPopular } from '@tabler/icons-react'; +--- +{popularPosts && popularPosts.length > 0 && ( + +)} diff --git a/src/components/blocks/links/RecentLinks.astro b/src/components/blocks/links/RecentLinks.astro new file mode 100644 index 0000000..71930cf --- /dev/null +++ b/src/components/blocks/links/RecentLinks.astro @@ -0,0 +1,26 @@ +--- +const { links } = Astro.props; +import { IconLink } from '@tabler/icons-react'; +--- +{links && links.length > 0 && ( + +)} diff --git a/src/components/Intro.astro b/src/components/home/Intro.astro similarity index 100% rename from src/components/Intro.astro rename to src/components/home/Intro.astro diff --git a/src/components/RecentActivity.astro b/src/components/home/RecentActivity.astro similarity index 100% rename from src/components/RecentActivity.astro rename to src/components/home/RecentActivity.astro diff --git a/src/components/RecentPosts.astro b/src/components/home/RecentPosts.astro similarity index 93% rename from src/components/RecentPosts.astro rename to src/components/home/RecentPosts.astro index 5b19e0e..a222d3d 100644 --- a/src/components/RecentPosts.astro +++ b/src/components/home/RecentPosts.astro @@ -1,6 +1,6 @@ --- import { IconClock, IconStar, IconArrowRight } from '@tabler/icons-react'; -import { fetchAllPosts } from '../utils/data/posts.js'; +import { fetchAllPosts } from '@utils/data/posts.js'; import { md } from '@utils/helpers.js'; const posts = await fetchAllPosts(); diff --git a/src/components/media/Grid.astro b/src/components/media/Grid.astro new file mode 100644 index 0000000..9292eda --- /dev/null +++ b/src/components/media/Grid.astro @@ -0,0 +1,70 @@ +--- +import Paginator from '@components/nav/Paginator.astro'; + +const { data, globals, count, shape, pagination, loading = "lazy" } = Astro.props; +const pageCount = pagination?.pages?.length || 0; +const hidePagination = pageCount <= 1; + +function getImageAttributes(item, shape) { + let imageUrl = item.grid.image; + let imageClass = ''; + let width = 0; + let height = 0; + + switch (shape) { + case 'poster': + imageUrl = item.grid.backdrop; + imageClass = 'banner'; + width = 256; + height = 170; + break; + case 'square': + imageClass = 'square'; + width = 200; + height = 200; + break; + case 'vertical': + imageClass = 'vertical'; + width = 200; + height = 307; + break; + } + + return { imageUrl, imageClass, width, height }; +} +--- + +
+ {data.slice(0, count).map((item) => { + const alt = item.grid.alt?.replace(/['"]/g, ''); + const { imageUrl, imageClass, width, height } = getImageAttributes(item, shape); + + return ( + +
+
+
{item.grid.title}
+
{item.grid.subtext}
+
+ {alt} +
+
+ ); + })} +
+ +{!hidePagination && ( + +)} diff --git a/src/components/media/ProgressBar.astro b/src/components/media/ProgressBar.astro new file mode 100644 index 0000000..1c9799e --- /dev/null +++ b/src/components/media/ProgressBar.astro @@ -0,0 +1,9 @@ +--- +const { percentage } = Astro.props; +--- + +{percentage && ( +
+
+
+)} \ No newline at end of file diff --git a/src/components/media/music/Chart.astro b/src/components/media/music/Chart.astro new file mode 100644 index 0000000..b4670f3 --- /dev/null +++ b/src/components/media/music/Chart.astro @@ -0,0 +1,29 @@ +--- +import ProgressBar from '@components/media/ProgressBar.astro'; + +const { data, count } = Astro.props; +--- + +
+
    + {data.slice(0, count).map((item) => { + const percentage = `${item.chart.percentage}%`; + const playsLabel = item.chart.plays === 1 ? 'play' : 'plays'; + + return ( +
  1. +
    +
    + {item.chart.title} + {item.chart.artist} + + {item.chart.plays} {playsLabel} + +
    + +
    +
  2. + ); + })} +
+
\ No newline at end of file diff --git a/src/components/media/music/Recent.astro b/src/components/media/music/Recent.astro new file mode 100644 index 0000000..9921178 --- /dev/null +++ b/src/components/media/music/Recent.astro @@ -0,0 +1,41 @@ +--- +const { data, globals } = Astro.props; +--- + +
+ {data.slice(0, 10).map((item) => ( +
+
+ + {item.chart.alt.replace(/['"]/g, + +
+ {item.chart.title} + {item.chart.subtext} +
+
+ +
+ ))} +
diff --git a/src/components/media/watching/Hero.astro b/src/components/media/watching/Hero.astro new file mode 100644 index 0000000..078ef55 --- /dev/null +++ b/src/components/media/watching/Hero.astro @@ -0,0 +1,18 @@ +--- +import Hero from "@components/blocks/Hero.astro"; + +const { movie, globals } = Astro.props; +--- + + +
+
+
{movie.title}
+
+ {movie.rating && {movie.rating} } + ({movie.year}) +
+
+ +
+
diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..9bc5cb4 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index d7fe958..cbb9e18 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,8 +1,8 @@ --- import "@styles/index.css"; -import Header from "../components/Header.astro"; -import Footer from "../components/Footer.astro"; -import { fetchNavigation } from "../utils/data/nav.js"; +import Header from "@components/Header.astro"; +import Footer from "@components/Footer.astro"; +import { fetchNavigation } from "@utils/data/nav.js"; const currentUrl = Astro.url.pathname; const nav = await fetchNavigation(); diff --git a/src/pages/.well-known/webfinger.js b/src/pages/.well-known/webfinger.js new file mode 100644 index 0000000..cddfeea --- /dev/null +++ b/src/pages/.well-known/webfinger.js @@ -0,0 +1,46 @@ +import { fetchGlobals } from "@utils/data/globals"; + +export async function GET() { + try { + const globals = await fetchGlobals(); + + const webfingerResponse = { + subject: `acct:${globals.webfinger_username}@${globals.webfinger_hostname}`, + aliases: [ + `https://${globals.webfinger_hostname}/@${globals.webfinger_username}`, + `https://${globals.webfinger_hostname}/users/${globals.webfinger_username}`, + ], + links: [ + { + rel: "http://webfinger.net/rel/profile-page", + type: "text/html", + href: `https://${globals.webfinger_hostname}/@${globals.webfinger_username}`, + }, + { + rel: "self", + type: "application/activity+json", + href: `https://${globals.webfinger_hostname}/users/${globals.webfinger_username}`, + }, + { + rel: "http://ostatus.org/schema/1.0/subscribe", + template: `https://${globals.webfinger_hostname}/authorize_interaction?uri={uri}`, + }, + { + rel: "http://webfinger.net/rel/avatar", + type: "image/png", + href: `${globals.cdn_url}${globals.avatar}?class=squarebase`, + }, + ], + }; + + return new Response(JSON.stringify(webfingerResponse), { + status: 200, + headers: { + "Content-Type": "application/jrd+json", + }, + }); + } catch (error) { + console.error("Error generating WebFinger response:", error); + return new Response("Error generating WebFinger response", { status: 500 }); + } +} diff --git a/src/pages/[permalink].astro b/src/pages/[permalink].astro index 2378f13..06faa28 100644 --- a/src/pages/[permalink].astro +++ b/src/pages/[permalink].astro @@ -1,6 +1,6 @@ --- import Layout from '@layouts/Layout.astro'; -import BlockRenderer from '@components/BlockRenderer.astro'; +import BlockRenderer from '@components/blocks/BlockRenderer.astro'; import { fetchGlobals } from '@utils/data/globals.js'; import { fetchPages } from '@utils/data/pages'; diff --git a/src/pages/feeds/json/all.json.js b/src/pages/feeds/json/all.json.js new file mode 100644 index 0000000..6cc1a4d --- /dev/null +++ b/src/pages/feeds/json/all.json.js @@ -0,0 +1,22 @@ +import { generateJsonFeed } from '@utils/generateJsonFeed'; +import { fetchGlobals } from '@utils/data/globals'; +import { fetchActivity } from '@utils/data/activity'; + +export async function GET() { + const globals = await fetchGlobals(); + const activity = await fetchActivity(); + + const feed = generateJsonFeed({ + permalink: "/feeds/all.json", + title: "All activity / Cory Dransfeldt", + globals, + data: activity, + }); + + return new Response(feed, { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/src/pages/feeds/json/books.json.js b/src/pages/feeds/json/books.json.js new file mode 100644 index 0000000..54e2a61 --- /dev/null +++ b/src/pages/feeds/json/books.json.js @@ -0,0 +1,22 @@ +import { generateJsonFeed } from '@utils/generateJsonFeed'; +import { fetchGlobals } from '@utils/data/globals'; +import { fetchBooks } from '@utils/data/books'; + +export async function GET() { + const globals = await fetchGlobals(); + const books = await fetchBooks(); + + const feed = generateJsonFeed({ + permalink: "/feeds/books.json", + title: "Books / Cory Dransfeldt", + globals, + data: books.feed, + }); + + return new Response(feed, { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/src/pages/feeds/json/links.json.js b/src/pages/feeds/json/links.json.js new file mode 100644 index 0000000..98fdd5f --- /dev/null +++ b/src/pages/feeds/json/links.json.js @@ -0,0 +1,22 @@ +import { generateJsonFeed } from '@utils/generateJsonFeed'; +import { fetchGlobals } from '@utils/data/globals'; +import { fetchLinks } from '@utils/data/links'; + +export async function GET() { + const globals = await fetchGlobals(); + const links = await fetchLinks(); + + const feed = generateJsonFeed({ + permalink: "/feeds/links.json", + title: "Links / Cory Dransfeldt", + globals, + data: links, + }); + + return new Response(feed, { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/src/pages/feeds/json/movies.json.js b/src/pages/feeds/json/movies.json.js new file mode 100644 index 0000000..852eeea --- /dev/null +++ b/src/pages/feeds/json/movies.json.js @@ -0,0 +1,22 @@ +import { generateJsonFeed } from '@utils/generateJsonFeed'; +import { fetchGlobals } from '@utils/data/globals'; +import { fetchMovies } from '@utils/data/movies'; + +export async function GET() { + const globals = await fetchGlobals(); + const movies = await fetchMovies(); + + const feed = generateJsonFeed({ + permalink: "/feeds/movies.json", + title: "Movies / Cory Dransfeldt", + globals, + data: movies.feed, + }); + + return new Response(feed, { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/src/pages/feeds/json/posts.json.js b/src/pages/feeds/json/posts.json.js new file mode 100644 index 0000000..ea6e978 --- /dev/null +++ b/src/pages/feeds/json/posts.json.js @@ -0,0 +1,22 @@ +import { generateJsonFeed } from '@utils/generateJsonFeed'; +import { fetchGlobals } from '@utils/data/globals'; +import { fetchAllPosts } from '@utils/data/posts'; + +export async function GET() { + const globals = await fetchGlobals(); + const posts = await fetchAllPosts(); + + const feed = generateJsonFeed({ + permalink: "/feeds/posts.json", + title: "Posts / Cory Dransfeldt", + globals, + data: posts, + }); + + return new Response(feed, { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/src/pages/feeds/rss/all.xml.js b/src/pages/feeds/rss/all.xml.js new file mode 100644 index 0000000..77bac08 --- /dev/null +++ b/src/pages/feeds/rss/all.xml.js @@ -0,0 +1,22 @@ +import { generateRssFeed } from "@utils/generateRssFeed"; +import { fetchGlobals } from "@utils/data/globals"; +import { fetchActivity } from "@utils/data/activity"; + +export async function GET() { + const globals = await fetchGlobals(); + const activity = await fetchActivity(); + + const rss = generateRssFeed({ + permalink: "/feeds/all.xml", + title: "All activity / Cory Dransfeldt", + globals, + data: activity, + }); + + return new Response(rss, { + status: 200, + headers: { + "Content-Type": "application/rss+xml", + }, + }); +} diff --git a/src/pages/feeds/rss/books.xml.js b/src/pages/feeds/rss/books.xml.js new file mode 100644 index 0000000..187c346 --- /dev/null +++ b/src/pages/feeds/rss/books.xml.js @@ -0,0 +1,22 @@ +import { generateRssFeed } from "@utils/generateRssFeed"; +import { fetchGlobals } from "@utils/data/globals"; +import { fetchBooks } from '@utils/data/books'; + +export async function GET() { + const globals = await fetchGlobals(); + const books = await fetchBooks(); + + const rss = generateRssFeed({ + permalink: "/feeds/books.xml", + title: "Books / Cory Dransfeldt", + globals, + data: books.feed, + }); + + return new Response(rss, { + status: 200, + headers: { + "Content-Type": "application/rss+xml", + }, + }); +} diff --git a/src/pages/feeds/rss/links.xml.js b/src/pages/feeds/rss/links.xml.js new file mode 100644 index 0000000..e2219cd --- /dev/null +++ b/src/pages/feeds/rss/links.xml.js @@ -0,0 +1,22 @@ +import { generateRssFeed } from "@utils/generateRssFeed"; +import { fetchGlobals } from "@utils/data/globals"; +import { fetchLinks } from '@utils/data/links'; + +export async function GET() { + const globals = await fetchGlobals(); + const links = await fetchLinks(); + + const rss = generateRssFeed({ + permalink: "/feeds/links.xml", + title: "Links / Cory Dransfeldt", + globals, + data: links, + }); + + return new Response(rss, { + status: 200, + headers: { + "Content-Type": "application/rss+xml", + }, + }); +} diff --git a/src/pages/feeds/rss/movies.xml.js b/src/pages/feeds/rss/movies.xml.js new file mode 100644 index 0000000..78a314d --- /dev/null +++ b/src/pages/feeds/rss/movies.xml.js @@ -0,0 +1,22 @@ +import { generateRssFeed } from "@utils/generateRssFeed"; +import { fetchGlobals } from "@utils/data/globals"; +import { fetchMovies } from '@utils/data/movies'; + +export async function GET() { + const globals = await fetchGlobals(); + const movies = await fetchMovies(); + + const rss = generateRssFeed({ + permalink: "/feeds/movies.xml", + title: "Movies / Cory Dransfeldt", + globals, + data: movies.feed, + }); + + return new Response(rss, { + status: 200, + headers: { + "Content-Type": "application/rss+xml", + }, + }); +} diff --git a/src/pages/feeds/rss/posts.xml.js b/src/pages/feeds/rss/posts.xml.js new file mode 100644 index 0000000..53f9890 --- /dev/null +++ b/src/pages/feeds/rss/posts.xml.js @@ -0,0 +1,22 @@ +import { generateRssFeed } from "@utils/generateRssFeed"; +import { fetchGlobals } from "@utils/data/globals"; +import { fetchAllPosts } from '@utils/data/posts'; + +export async function GET() { + const globals = await fetchGlobals(); + const posts = await fetchAllPosts(); + + const rss = generateRssFeed({ + permalink: "/feeds/posts.xml", + title: "Posts / Cory Dransfeldt", + globals, + data: posts, + }); + + return new Response(rss, { + status: 200, + headers: { + "Content-Type": "application/rss+xml", + }, + }); +} diff --git a/src/pages/feeds/syndication.xml.js b/src/pages/feeds/rss/syndication.xml.js similarity index 93% rename from src/pages/feeds/syndication.xml.js rename to src/pages/feeds/rss/syndication.xml.js index 2d704bc..b75af08 100644 --- a/src/pages/feeds/syndication.xml.js +++ b/src/pages/feeds/rss/syndication.xml.js @@ -1,5 +1,5 @@ -import fetchSyndication from '../../utils/data/syndication.js'; -import { fetchGlobals } from '../../utils/data/globals.js'; +import fetchSyndication from '@utils/data/syndication.js'; +import { fetchGlobals } from '@utils/data/globals.js'; export async function GET() { const globals = await fetchGlobals(); diff --git a/src/pages/humans.txt.js b/src/pages/humans.txt.js new file mode 100644 index 0000000..abe30cf --- /dev/null +++ b/src/pages/humans.txt.js @@ -0,0 +1,29 @@ +import { fetchGlobals } from '@utils/data/globals'; + +export async function GET() { + try { + const globals = await fetchGlobals(); + + const humansTxt = ` +## team + +${globals.site_name} +${globals.url} +${globals.mastodon} + +## colophon + +${globals.url}/colophon + `.trim(); + + return new Response(humansTxt, { + status: 200, + headers: { + 'Content-Type': 'text/plain', + }, + }); + } catch (error) { + console.error('Error generating humans.txt:', error); + return new Response('Error generating humans.txt', { status: 500 }); + } +} diff --git a/src/pages/index.astro b/src/pages/index.astro index d8a2a25..51d8289 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,9 +1,9 @@ --- -import Layout from '../layouts/Layout.astro'; -import Intro from '../components/Intro.astro'; -import { fetchGlobals } from '../utils/data/globals'; -import RecentActivity from '../components/RecentActivity.astro'; -import RecentPosts from '../components/RecentPosts.astro'; +import { fetchGlobals } from '@utils/data/globals'; +import Layout from '@layouts/Layout.astro'; +import Intro from '@components/home/Intro.astro'; +import RecentActivity from '@components/home/RecentActivity.astro'; +import RecentPosts from '@components/home/RecentPosts.astro'; const globals = await fetchGlobals(); const schema = 'blog'; diff --git a/src/pages/music/releases.ics.js b/src/pages/music/releases.ics.js new file mode 100644 index 0000000..3b2e2b0 --- /dev/null +++ b/src/pages/music/releases.ics.js @@ -0,0 +1,24 @@ +import { albumReleasesCalendar } from '@utils/albumReleasesCalendar'; +import { fetchAlbumReleases } from '@utils/data/albumReleases'; + +export async function GET() { + try { + const { all: albumReleases } = await fetchAlbumReleases(); + const icsContent = await albumReleasesCalendar(albumReleases); + + if (!icsContent) return new Response('Error generating ICS file', { status: 500 }); + + return new Response(icsContent, { + status: 200, + headers: { + 'Content-Type': 'text/calendar', + 'Content-Disposition': 'attachment; filename="releases.ics"', + }, + }); + } catch (error) { + console.error('Error generating album releases ICS file:', error); + return new Response('Error generating album releases ICS file', { + status: 500, + }); + } +} diff --git a/src/pages/posts/[...page].astro b/src/pages/posts/[...page].astro index 395d6e1..93404b2 100644 --- a/src/pages/posts/[...page].astro +++ b/src/pages/posts/[...page].astro @@ -55,5 +55,5 @@ const pagination = { ))} - + \ No newline at end of file diff --git a/src/pages/posts/[year]/[title].astro b/src/pages/posts/[year]/[title].astro index 78a0c56..1a298f2 100644 --- a/src/pages/posts/[year]/[title].astro +++ b/src/pages/posts/[year]/[title].astro @@ -1,14 +1,26 @@ --- import { IconStar } from "@tabler/icons-react"; + import { fetchAllPosts } from "@data/posts.js"; +import { fetchAnalyticsData } from "@data/analytics.js"; import { fetchGlobals } from "@data/globals.js"; +import { fetchLinks } from "@data/links.js"; + import { md } from '@utils/helpers.js'; -import OldPost from "@components/blocks/banners/OldPost.astro"; -import BlockRenderer from "@components/BlockRenderer.astro"; +import { getPopularPosts } from '@utils/getPopularPosts.js'; + +const analytics = await fetchAnalyticsData(); +const links = await fetchLinks(); +const posts = await fetchAllPosts(); +const popularPosts = getPopularPosts(posts, analytics); + +import AddonLinks from '@components/blocks/links/AddonLinks.astro'; import AssociatedMedia from "@components/blocks/AssociatedMedia.astro"; -import MastodonPost from "@components/blocks/MastodonPost.astro"; -import Layout from "@layouts/Layout.astro"; +import BlockRenderer from "@components/blocks/BlockRenderer.astro"; import Coffee from "@components/blocks/banners/Coffee.astro"; +import Layout from "@layouts/Layout.astro"; +import Mastodon from "@components/blocks/banners/Mastodon.astro"; +import OldPost from "@components/blocks/banners/OldPost.astro"; export const prerender = true; @@ -87,7 +99,7 @@ const htmlContent = md(post.content); post.blocks && post.blocks.map((block) => ) } - + {post.mastodon_url && } + diff --git a/src/pages/robots.txt.js b/src/pages/robots.txt.js index fbc6036..6c26b13 100644 --- a/src/pages/robots.txt.js +++ b/src/pages/robots.txt.js @@ -1,4 +1,4 @@ -import { fetchAllRobots } from '../utils//data/robots.js'; +import { fetchAllRobots } from '@utils//data/robots.js'; export async function GET() { try { diff --git a/src/styles/components/banners.css b/src/styles/components/banners.css index 117ae9b..9a2cca4 100644 --- a/src/styles/components/banners.css +++ b/src/styles/components/banners.css @@ -24,6 +24,7 @@ &.coffee, &.error, &.github, + &.mastodon, &.npm, &.old-post, &.rss, @@ -37,6 +38,9 @@ &.github { --banner-accent-color: var(--brand-github); } + &.mastodon { + --banner-accent-color: var(--brand-mastodon); + } &.npm { --banner-accent-color: var(--brand-npm); } diff --git a/src/styles/components/mastodon-post.css b/src/styles/components/mastodon-post.css deleted file mode 100644 index d6f794f..0000000 --- a/src/styles/components/mastodon-post.css +++ /dev/null @@ -1,22 +0,0 @@ -mastodon-post { - width: 100%; - - .mastodon-post-wrapper { - & dl, - & dt { - display: flex; - } - - & dl { - align-items: center; - - & dd { - margin-left: var(--spacing-xs); - - &:not(:last-child) { - margin-right: var(--spacing-lg); - } - } - } - } -} diff --git a/src/styles/index.css b/src/styles/index.css index 38205f9..8bee591 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -29,7 +29,6 @@ @import url("./components/banners.css") layer(components); @import url("./components/buttons.css") layer(components); @import url("./components/forms.css") layer(components); -@import url("./components/mastodon-post.css") layer(components); @import url("./components/media-grid.css") layer(components); @import url("./components/menu.css") layer(components); @import url("./components/modal.css") layer(components); diff --git a/src/utils/albumReleasesCalendar.js b/src/utils/albumReleasesCalendar.js new file mode 100644 index 0000000..b67ec7c --- /dev/null +++ b/src/utils/albumReleasesCalendar.js @@ -0,0 +1,38 @@ +import { DateTime } from "luxon"; +import { createEvents } from "ics"; + +export async function albumReleasesCalendar(albumReleases) { + if (!albumReleases || albumReleases.length === 0) return ""; + + const events = albumReleases + .map((album) => { + const date = DateTime.fromISO(album["release_date"]); + if (!date.isValid) return null; + + return { + start: [date.year, date.month, date.day], + startInputType: "local", + startOutputType: "local", + title: `Release: ${album["artist"]["name"]} - ${album["title"]}`, + description: `Check out this new album release: ${album["url"]}. Read more about ${album["artist"]["name"]} at https://coryd.dev${album["artist"]["url"]}`, + url: album["url"], + uid: `${date.toFormat("yyyyMMdd")}-${album["artist"]["name"]}-${album["title"]}@coryd.dev`, + timestamp: DateTime.now().toUTC().toFormat("yyyyMMdd'T'HHmmss'Z'"), + }; + }) + .filter((event) => event !== null); + + const { error, value } = createEvents(events, { + calName: "Album releases calendar / coryd.dev", + }); + + if (error) { + console.error("Error creating events: ", error); + events.forEach((event, index) => { + console.error(`Event ${index}:`, event); + }); + return ""; + } + + return value; +} diff --git a/src/utils/data/analytics.js b/src/utils/data/analytics.js new file mode 100644 index 0000000..0f17e30 --- /dev/null +++ b/src/utils/data/analytics.js @@ -0,0 +1,34 @@ +let cachedPages = null; + +export async function fetchAnalyticsData() { + if (import.meta.env.MODE === "development" && cachedPages) return cachedPages; + + const API_KEY_PLAUSIBLE = import.meta.env.API_KEY_PLAUSIBLE; + const url = + "https://plausible.io/api/v1/stats/breakdown?site_id=coryd.dev&period=6mo&property=event:page&limit=30"; + + try { + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${API_KEY_PLAUSIBLE}`, + }, + }); + + if (!res.ok) { + console.error(`Error fetching Plausible data: ${res.statusText}`); + return []; + } + + const pages = await res.json(); + const filteredPages = pages["results"].filter((p) => + p["page"].includes("posts") + ); + + if (import.meta.env.MODE === "development") cachedPages = filteredPages; + + return filteredPages; + } catch (error) { + console.error("Error fetching Plausible data:", error); + return []; + } +} diff --git a/src/utils/generateJsonFeed.js b/src/utils/generateJsonFeed.js new file mode 100644 index 0000000..7c58c3a --- /dev/null +++ b/src/utils/generateJsonFeed.js @@ -0,0 +1,39 @@ +export function generateJsonFeed({ + permalink, + title, + globals, + data, +}) { + const feed = { + version: "https://jsonfeed.org/version/1", + title, + home_page_url: globals.url, + feed_url: `${globals.url}${permalink}`, + description: globals.site_description, + icon: `${globals.cdn_url}${globals.avatar}?class=w200`, + author: { + name: globals.site_name, + url: globals.url, + avatar: `${globals.cdn_url}${globals.avatar}?class=w200`, + }, + items: data.slice(0, 20).map((entry) => { + const text = entry.feed.description + ?.replace(/(<([^>]+)>)/gi, "") + ?.trim() + ?.replace(/\s+/g, " ") + ?.slice(0, 200); + + return { + id: entry.feed.url, + url: entry.feed.url, + title: entry.feed.title, + content_html: text, + content_text: text, + summary: text, + date_published: new Date(entry.feed.date).toISOString(), + }; + }), + }; + + return JSON.stringify(feed, null, 2); +} diff --git a/src/utils/generateRssFeed.js b/src/utils/generateRssFeed.js new file mode 100644 index 0000000..f0c347f --- /dev/null +++ b/src/utils/generateRssFeed.js @@ -0,0 +1,45 @@ +import { DateTime } from "luxon"; + +export function generateRssFeed({ permalink, title, globals, data }) { + const rssItems = data.slice(0, 20).map((entry) => { + const entryFeed = entry.feed; + const rating = entry.rating; + const entryTitle = `${entryFeed.title}${ + entryFeed.artist?.name ? ` via ${entryFeed.artist.name}` : "" + }${rating ? ` (${rating})` : ""}`; + + return ` + + <![CDATA[${entryTitle}]]> + ${entryFeed.url} + ${DateTime.fromISO(entryFeed.date).toRFC2822()} + ${entryFeed.url} + ${ + entryFeed.image + ? `` + : "" + } + + `; + }); + + return ` + + + + + <![CDATA[${title}]]> + + ${globals.url}${permalink} + ${DateTime.now().toUTC().toRFC2822()} + + <![CDATA[${title}]]> + ${globals.url}${permalink} + ${globals.cdn_url}${globals.avatar}?class=w200 + 144 + 144 + + ${rssItems.join("\n")} + +`; +} diff --git a/src/utils/getPopularPosts.js b/src/utils/getPopularPosts.js new file mode 100644 index 0000000..7e33805 --- /dev/null +++ b/src/utils/getPopularPosts.js @@ -0,0 +1,13 @@ +export function getPopularPosts(posts, analytics) { + const filteredPosts = posts.filter((post) => + analytics.some((p) => p.page.includes(post.url)) + ); + + const sortedPosts = filteredPosts.sort((a, b) => { + const visitors = (page) => + analytics.find((p) => p.page.includes(page.url))?.visitors || 0; + return visitors(b) - visitors(a); + }); + + return sortedPosts; +}