chore: myriad fixes + artist pages
This commit is contained in:
parent
aec8471b06
commit
ff00020b70
53 changed files with 753 additions and 486 deletions
|
@ -1,36 +1,36 @@
|
||||||
---
|
---
|
||||||
import NavLink from '@components/nav/NavLink.astro';
|
import NavLink from "@components/nav/NavLink.astro";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { updated } = Astro.props;
|
const { updated } = Astro.props;
|
||||||
const { nav } = await fetchGlobalData(Astro);
|
const { nav } = await fetchGlobalData(Astro);
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer style={updated ? undefined : 'margin-top: var(--spacing-3xl)'}>
|
<footer style={updated ? undefined : "margin-top: var(--spacing-3xl)"}>
|
||||||
{updated && (
|
{
|
||||||
<p class="updated">
|
updated && (
|
||||||
<em>This page was last updated on {new Date(updated).toLocaleDateString()}</em>
|
<p class="updated">
|
||||||
</p>
|
<em>
|
||||||
)}
|
This page was last updated on {new Date(updated).toLocaleDateString()}
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
<nav aria-label="Social icons" class="social">
|
<nav aria-label="Social icons" class="social">
|
||||||
{nav.footer_icons.map(link => (
|
{
|
||||||
<NavLink
|
nav.footer_icons.map((link) => (
|
||||||
url={link.permalink}
|
<NavLink url={link.permalink} title={link.title} icon={link.icon} />
|
||||||
title={link.title}
|
))
|
||||||
icon={link.icon}
|
}
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</nav>
|
</nav>
|
||||||
<nav aria-label="Secondary site navigation" class="sub-pages">
|
<nav aria-label="Secondary site navigation" class="sub-pages">
|
||||||
{nav.footer_text.map((link, index) => (
|
{
|
||||||
<>
|
nav.footer_text.map((link, index) => (
|
||||||
<NavLink
|
<>
|
||||||
url={link.permalink}
|
<NavLink url={link.permalink} title={link.title} icon={link.icon} />
|
||||||
title={link.title}
|
{index < nav.footer_text.length - 1 && <span>/</span>}
|
||||||
icon={link.icon}
|
</>
|
||||||
/>
|
))
|
||||||
{index < nav.footer_text.length - 1 && <span>/</span>}
|
}
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</nav>
|
</nav>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
---
|
---
|
||||||
import Menu from '@components/nav/Menu.astro';
|
import Menu from "@components/nav/Menu.astro";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { siteName, url } = Astro.props;
|
const { siteName, url } = Astro.props;
|
||||||
const { nav } = await fetchGlobalData(Astro);
|
const { nav } = await fetchGlobalData(Astro);
|
||||||
const isHomePage = url === '/';
|
const isHomePage = url === "/";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="main-title">
|
<section class="main-title">
|
||||||
<h1>
|
<h1>
|
||||||
{isHomePage ? (
|
{
|
||||||
siteName
|
isHomePage ? (
|
||||||
) : (
|
siteName
|
||||||
<a href="/" tabindex="0">{siteName}</a>
|
) : (
|
||||||
)}
|
<a href="/" tabindex="0">
|
||||||
|
{siteName}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
</h1>
|
</h1>
|
||||||
<Menu nav={nav} />
|
<Menu nav={nav} />
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -15,29 +15,29 @@ import {
|
||||||
IconCoffee,
|
IconCoffee,
|
||||||
IconDeviceWatch,
|
IconDeviceWatch,
|
||||||
IconHeartHandshake,
|
IconHeartHandshake,
|
||||||
} from '@tabler/icons-react';
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
const { icon } = Astro.props;
|
const { icon } = Astro.props;
|
||||||
|
|
||||||
const iconComponents = {
|
const iconComponents = {
|
||||||
article: IconArticle,
|
article: IconArticle,
|
||||||
headphones: IconHeadphones,
|
headphones: IconHeadphones,
|
||||||
'device-tv-old': IconDeviceTvOld,
|
"device-tv-old": IconDeviceTvOld,
|
||||||
books: IconBooks,
|
books: IconBooks,
|
||||||
link: IconLink,
|
link: IconLink,
|
||||||
'info-circle': IconInfoCircle,
|
"info-circle": IconInfoCircle,
|
||||||
search: IconSearch,
|
search: IconSearch,
|
||||||
rss: IconRss,
|
rss: IconRss,
|
||||||
'brand-mastodon': IconBrandMastodon,
|
"brand-mastodon": IconBrandMastodon,
|
||||||
mail: IconMail,
|
mail: IconMail,
|
||||||
'brand-github': IconBrandGithub,
|
"brand-github": IconBrandGithub,
|
||||||
'brand-npm': IconBrandNpm,
|
"brand-npm": IconBrandNpm,
|
||||||
coffee: IconCoffee,
|
coffee: IconCoffee,
|
||||||
'device-watch': IconDeviceWatch,
|
"device-watch": IconDeviceWatch,
|
||||||
'heart-handshake': IconHeartHandshake,
|
"heart-handshake": IconHeartHandshake,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SelectedIcon = iconComponents[icon?.toLowerCase()] || null;
|
const SelectedIcon = iconComponents[icon?.toLowerCase()] || null;
|
||||||
---
|
---
|
||||||
|
|
||||||
{SelectedIcon ? <SelectedIcon size={24} /> : null}
|
{SelectedIcon ? <SelectedIcon size={24} /> : null}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import { escapeHtml } from "@utils/helpers/general.js";
|
import { escapeHtml } from "@utils/helpers/general.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
schema = "page",
|
schema = "page",
|
||||||
|
@ -107,7 +107,8 @@ if (titleOverride) pageTitle = titleOverride;
|
||||||
if (descriptionOverride) pageDescription = descriptionOverride;
|
if (descriptionOverride) pageDescription = descriptionOverride;
|
||||||
if (ogImageOverride) ogImage = ogImageOverride;
|
if (ogImageOverride) ogImage = ogImageOverride;
|
||||||
|
|
||||||
if (pageTitle !== globals.site_name && schema !== "blog") pageTitle = `${pageTitle} / ${globals.site_name}`;
|
if (pageTitle !== globals.site_name && schema !== "blog")
|
||||||
|
pageTitle = `${pageTitle} / ${globals.site_name}`;
|
||||||
|
|
||||||
const escapedPageDescription = escapeHtml(pageDescription);
|
const escapedPageDescription = escapeHtml(pageDescription);
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
---
|
---
|
||||||
|
import IconMapper from "@components/IconMapper.astro";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
artists = [],
|
artists = [],
|
||||||
books = [],
|
books = [],
|
||||||
|
|
|
@ -3,17 +3,17 @@ import { fetchAllPosts } from "@data/posts.js";
|
||||||
import { fetchAnalyticsData } from "@data/analytics.js";
|
import { fetchAnalyticsData } from "@data/analytics.js";
|
||||||
import { fetchLinks } from "@data/links.js";
|
import { fetchLinks } from "@data/links.js";
|
||||||
|
|
||||||
import AddonLinks from '@components/blocks/links/AddonLinks.astro';
|
import AddonLinks from "@components/blocks/links/AddonLinks.astro";
|
||||||
import AssociatedMedia from '@components/blocks//AssociatedMedia.astro';
|
import AssociatedMedia from "@components/blocks//AssociatedMedia.astro";
|
||||||
import GitHub from '@components/blocks/banners/GitHub.astro';
|
import GitHub from "@components/blocks/banners/GitHub.astro";
|
||||||
import Hero from '@components/blocks//Hero.astro';
|
import Hero from "@components/blocks//Hero.astro";
|
||||||
import Modal from '@components/blocks//Modal.astro';
|
import Modal from "@components/blocks//Modal.astro";
|
||||||
import Npm from '@components/blocks/banners/Npm.astro';
|
import Npm from "@components/blocks/banners/Npm.astro";
|
||||||
import Rss from '@components/blocks/banners/Rss.astro';
|
import Rss from "@components/blocks/banners/Rss.astro";
|
||||||
import YouTubePlayer from '@components/blocks//YouTubePlayer.astro';
|
import YouTubePlayer from "@components/blocks//YouTubePlayer.astro";
|
||||||
|
|
||||||
import { md } from '@utils/helpers/general.js';
|
import { md } from "@utils/helpers/general.js";
|
||||||
import { getPopularPosts } from '@utils/getPopularPosts.js';
|
import { getPopularPosts } from "@utils/getPopularPosts.js";
|
||||||
|
|
||||||
const analytics = await fetchAnalyticsData();
|
const analytics = await fetchAnalyticsData();
|
||||||
const links = await fetchLinks();
|
const links = await fetchLinks();
|
||||||
|
@ -22,42 +22,26 @@ const popularPosts = getPopularPosts(posts, analytics);
|
||||||
const { block } = Astro.props;
|
const { block } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{block.type === 'addon_links' && (
|
{
|
||||||
<AddonLinks popularPosts={popularPosts} links={links} />
|
block.type === "addon_links" && (
|
||||||
)}
|
<AddonLinks popularPosts={popularPosts} links={links} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{block.type === 'associated_media' && (
|
{block.type === "associated_media" && <AssociatedMedia media={block.media} />}
|
||||||
<AssociatedMedia media={block.media} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'divider' && (
|
{block.type === "divider" && <div set:html={md(block.markup)} />}
|
||||||
<div set:html={md(block.markup)}></div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'github_banner' && (
|
{block.type === "github_banner" && <GitHub url={block.url} />}
|
||||||
<GitHub url={block.url} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'hero' && (
|
{block.type === "hero" && <Hero image={block.image} alt={block.alt} />}
|
||||||
<Hero image={block.image} alt={block.alt} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'markdown' && (
|
{block.type === "markdown" && <div set:html={md(block.text)} />}
|
||||||
<div set:html={md(block.text)}></div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'npm_banner' && (
|
{block.type === "npm_banner" && <Npm url={block.url} command={block.command} />}
|
||||||
<Npm url={block.url} command={block.command} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'modal' && (
|
{block.type === "modal" && <Modal content={block.content} />}
|
||||||
<Modal content={block.content} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'rss_banner' && (
|
{block.type === "rss_banner" && <Rss url={block.url} text={block.text} />}
|
||||||
<Rss url={block.url} text={block.text} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{block.type === 'youtube_player' && (
|
{block.type === "youtube_player" && <YouTubePlayer url={block.url} />}
|
||||||
<YouTubePlayer url={block.url} />
|
|
||||||
)}
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import { fetchGlobals } from "@utils/data/globals.js";
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { image, alt } = Astro.props;
|
const { image, alt } = Astro.props;
|
||||||
const globals = await fetchGlobals(Astro);
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { fetchNowPlaying } from '@utils/data/nowPlaying.js';
|
import { fetchNowPlaying } from "@utils/data/nowPlaying.js";
|
||||||
|
|
||||||
const isProduction = import.meta.env.MODE === 'production';
|
const isProduction = import.meta.env.MODE === "production";
|
||||||
const nowPlayingData = await fetchNowPlaying();
|
const nowPlayingData = await fetchNowPlaying();
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ const nowPlayingData = await fetchNowPlaying();
|
||||||
<p>{nowPlayingData.content}</p>
|
<p>{nowPlayingData.content}</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
{isProduction && (<script type="module">
|
{isProduction && (<script type="module" is:inline>
|
||||||
async function updateNowPlaying() {
|
async function updateNowPlaying() {
|
||||||
const response = await fetch('/api/now-playing');
|
const response = await fetch("/api/now-playing");
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
document.getElementById('now-playing-content').innerHTML = data.content;
|
document.getElementById("now-playing-content").innerHTML = data.content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import YoutubeVideo from '@npm/youtube-video-element/dist/react.js';
|
import YoutubeVideo from "@npm/youtube-video-element/dist/react.js";
|
||||||
|
|
||||||
const { url } = Astro.props;
|
const { url } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<YoutubeVideo src={url} controls />
|
<YoutubeVideo src={url} controls />
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
---
|
||||||
import { IconAlertCircle } from '@tabler/icons-react';
|
import { IconAlertCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { text } = Astro.props
|
const { text } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="banner error">
|
<div class="banner error">
|
||||||
<p><IconAlertCircle size={24} />{ text }</p>
|
<p><IconAlertCircle size={24} />{text}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
---
|
---
|
||||||
import { IconBrandGithub } from '@tabler/icons-react';
|
import { IconBrandGithub } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { url } = Astro.props;
|
const { url } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="banner github">
|
<div class="banner github">
|
||||||
<p><IconBrandGithub size={24} /> Take a look at <a href={url}>the GitHub repository for this project</a>. (Give it a star if you feel like it.)</p>
|
<p>
|
||||||
</div>
|
<IconBrandGithub size={24} /> Take a look at <a href={url}
|
||||||
|
>the GitHub repository for this project</a
|
||||||
|
>. (Give it a star if you feel like it.)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { IconBrandNpm } from '@tabler/icons-react';
|
import { IconBrandNpm } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { url, command } = Astro.props;
|
const { url, command } = Astro.props;
|
||||||
---
|
---
|
||||||
|
@ -7,6 +7,7 @@ const { url, command } = Astro.props;
|
||||||
<div class="banner npm">
|
<div class="banner npm">
|
||||||
<p>
|
<p>
|
||||||
<IconBrandNpm size={24} />
|
<IconBrandNpm size={24} />
|
||||||
<a href={url}>You can take a look at this package on NPM</a> or install it by running <code>{command}</code>.
|
<a href={url}>You can take a look at this package on NPM</a> or install it by
|
||||||
|
running <code>{command}</code>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
---
|
---
|
||||||
import { IconClockX } from '@tabler/icons-react';
|
import { IconClockX } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { isOldPost } = Astro.props;
|
const { isOldPost } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{isOldPost && (
|
{
|
||||||
<div class="banner old-post">
|
isOldPost && (
|
||||||
<p>
|
<div class="banner old-post">
|
||||||
<IconClockX size={24} />
|
<p>
|
||||||
This post is over 3 years old. I've probably changed my mind since it was written and it <em>could</em> be out of date.
|
<IconClockX size={24} />
|
||||||
</p>
|
This post is over 3 years old. I've probably changed my mind since it
|
||||||
</div>
|
was written and it <em>could</em> be out of date.
|
||||||
)}
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { IconRss } from '@tabler/icons-react';
|
import { IconRss } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { url, text } = Astro.props;
|
const { url, text } = Astro.props;
|
||||||
---
|
---
|
||||||
|
@ -9,4 +9,4 @@ const { url, text } = Astro.props;
|
||||||
<IconRss size={24} />
|
<IconRss size={24} />
|
||||||
<a href={url}>{text}</a>.
|
<a href={url}>{text}</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { IconAlertTriangle } from '@tabler/icons-react';
|
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||||
|
|
||||||
const { text } = Astro.props;
|
const { text } = Astro.props;
|
||||||
---
|
---
|
||||||
|
@ -9,4 +9,4 @@ const { text } = Astro.props;
|
||||||
<IconAlertTriangle size={24} />
|
<IconAlertTriangle size={24} />
|
||||||
{text}
|
{text}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
---
|
---
|
||||||
const { popularPosts } = Astro.props;
|
const { popularPosts } = Astro.props;
|
||||||
import { IconChartBarPopular } from '@tabler/icons-react';
|
import { IconChartBarPopular } from "@tabler/icons-react";
|
||||||
---
|
---
|
||||||
{popularPosts && popularPosts.length > 0 && (
|
|
||||||
<article>
|
{
|
||||||
<h3>
|
popularPosts && popularPosts.length > 0 && (
|
||||||
<a class="article" href="/posts">
|
<article>
|
||||||
<IconChartBarPopular size={24} />
|
<h3>
|
||||||
Popular posts
|
<a class="article" href="/posts">
|
||||||
</a>
|
<IconChartBarPopular size={24} />
|
||||||
</h3>
|
Popular posts
|
||||||
<ol type="1">
|
</a>
|
||||||
{popularPosts.slice(0, 5).map((post) => (
|
</h3>
|
||||||
<li>
|
<ol type="1">
|
||||||
<a href={post.url}>{post.title}</a>
|
{popularPosts.slice(0, 5).map((post) => (
|
||||||
</li>
|
<li>
|
||||||
))}
|
<a href={post.url}>{post.title}</a>
|
||||||
</ol>
|
</li>
|
||||||
</article>
|
))}
|
||||||
)}
|
</ol>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
---
|
---
|
||||||
const { links } = Astro.props;
|
const { links } = Astro.props;
|
||||||
import { IconLink } from '@tabler/icons-react';
|
import { IconLink } from "@tabler/icons-react";
|
||||||
---
|
---
|
||||||
{links && links.length > 0 && (
|
|
||||||
<article>
|
{
|
||||||
<h3>
|
links && links.length > 0 && (
|
||||||
<a class="link" href="/links">
|
<article>
|
||||||
<IconLink size={24} />
|
<h3>
|
||||||
Recent links
|
<a class="link" href="/links">
|
||||||
</a>
|
<IconLink size={24} />
|
||||||
</h3>
|
Recent links
|
||||||
<ul>
|
</a>
|
||||||
{links.slice(0, 5).map((link) => (
|
</h3>
|
||||||
<li>
|
<ul>
|
||||||
<a href={link.link} title={link.title}>
|
{links.slice(0, 5).map((link) => (
|
||||||
{link.title}
|
<li>
|
||||||
</a>
|
<a href={link.link} title={link.title}>
|
||||||
{link.author && (
|
{link.title}
|
||||||
<> via <a href={link.author.url}>{link.author.name}</a></>
|
</a>
|
||||||
)}
|
{link.author && (
|
||||||
</li>
|
<>
|
||||||
))}
|
{" "}
|
||||||
</ul>
|
via <a href={link.author.url}>{link.author.name}</a>
|
||||||
</article>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
---
|
||||||
import { fetchMusicData } from '@utils/data/music.js';
|
import { fetchMusicData } from "@utils/data/music.js";
|
||||||
import { fetchShows } from '@utils/data/tv.js';
|
import { fetchShows } from "@utils/data/tv.js";
|
||||||
import { fetchMovies } from '@utils/data/movies.js';
|
import { fetchMovies } from "@utils/data/movies.js";
|
||||||
import { fetchBooks } from '@utils/data/books.js';
|
import { fetchBooks } from "@utils/data/books.js";
|
||||||
import { fetchLinks } from '@utils/data/links.js';
|
import { fetchLinks } from "@utils/data/links.js";
|
||||||
import { IconActivity } from '@tabler/icons-react';
|
import { IconActivity } from "@tabler/icons-react";
|
||||||
|
|
||||||
import Rss from '@components/blocks/banners/Rss.astro';
|
import Rss from "@components/blocks/banners/Rss.astro";
|
||||||
|
|
||||||
const music = await fetchMusicData();
|
const music = await fetchMusicData();
|
||||||
const tv = await fetchShows();
|
const tv = await fetchShows();
|
||||||
|
@ -27,17 +27,42 @@ const link = links[0];
|
||||||
Recent activity
|
Recent activity
|
||||||
</h2>
|
</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><span class="music">Top track this week:</span> <a href={track.artist_url}>{track.track_name} by {track.artist_name}</a></li>
|
<li>
|
||||||
<li><span class="tv">Last episode watched:</span> <strong class="highlight-text">{show.formatted_episode}</strong> of <a href={show.url}>{show.title}</a></li>
|
<span class="music">Top track this week:</span>
|
||||||
<li><span class="movies">Last movie watched:</span> <a href={movie.url}>{movie.title}</a>{movie.rating ? ` (${movie.rating})` : ''}</li>
|
<a href={track.artist_url}>{track.track_name} by {track.artist_name}</a>
|
||||||
<li><span class="books">Last book finished:</span> <a href={book.url}>{book.title}</a> by {book.author}{book.rating ? ` (${book.rating})` : ''}</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="tv">Last episode watched:</span>
|
||||||
|
<strong class="highlight-text">{show.formatted_episode}</strong> of <a
|
||||||
|
href={show.url}>{show.title}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="movies">Last movie watched:</span>
|
||||||
|
<a href={movie.url}>{movie.title}</a>{
|
||||||
|
movie.rating ? ` (${movie.rating})` : ""
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="books">Last book finished:</span>
|
||||||
|
<a href={book.url}>{book.title}</a> by {book.author}{
|
||||||
|
book.rating ? ` (${book.rating})` : ""
|
||||||
|
}
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="link">Last link shared:</span>
|
<span class="link">Last link shared:</span>
|
||||||
<a href={link.link}>{link.title}</a>
|
<a href={link.link}>{link.title}</a>
|
||||||
{link.author && (
|
{
|
||||||
<span> via <a href={link.author.url}>{link.author.name}</a></span>
|
link.author && (
|
||||||
)}
|
<span>
|
||||||
|
{" "}
|
||||||
|
via <a href={link.author.url}>{link.author.name}</a>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<Rss url="/feeds" text="Subscribe to my movies, books, links or activity feed(s)" />
|
<Rss
|
||||||
</article>
|
url="/feeds"
|
||||||
|
text="Subscribe to my movies, books, links or activity feed(s)"
|
||||||
|
/>
|
||||||
|
</article>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { IconClock, IconStar, IconArrowRight } from '@tabler/icons-react';
|
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/general.js';
|
import { md } from "@utils/helpers/general.js";
|
||||||
|
|
||||||
const posts = await fetchAllPosts();
|
const posts = await fetchAllPosts();
|
||||||
---
|
---
|
||||||
|
@ -10,24 +10,26 @@ const posts = await fetchAllPosts();
|
||||||
<IconClock size={24} />
|
<IconClock size={24} />
|
||||||
Recent posts
|
Recent posts
|
||||||
</h2>
|
</h2>
|
||||||
{posts.slice(0, 5).map(post => (
|
{
|
||||||
<article key={post.url}>
|
posts.slice(0, 5).map((post) => (
|
||||||
<div class="post-meta">
|
<article key={post.url}>
|
||||||
{post.featured && <IconStar size={16} />}
|
<div class="post-meta">
|
||||||
<time datetime={post.date}>
|
{post.featured && <IconStar size={16} />}
|
||||||
{new Date(post.date).toLocaleDateString('en-US', {
|
<time datetime={post.date}>
|
||||||
year: 'numeric',
|
{new Date(post.date).toLocaleDateString("en-US", {
|
||||||
month: 'long',
|
year: "numeric",
|
||||||
day: 'numeric',
|
month: "long",
|
||||||
})}
|
day: "numeric",
|
||||||
</time>
|
})}
|
||||||
</div>
|
</time>
|
||||||
<h3>
|
</div>
|
||||||
<a href={post.url}>{post.title}</a>
|
<h3>
|
||||||
</h3>
|
<a href={post.url}>{post.title}</a>
|
||||||
<p set:html={md(post.description)}></p>
|
</h3>
|
||||||
</article>
|
<p set:html={md(post.description)} />
|
||||||
))}
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
<a class="icon-link" href="/posts">
|
<a class="icon-link" href="/posts">
|
||||||
View all posts <IconArrowRight size={16} />
|
View all posts <IconArrowRight size={16} />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import Paginator from '@components/nav/Paginator.astro';
|
import Paginator from "@components/nav/Paginator.astro";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { data, count, shape, pagination, loading = "lazy" } = Astro.props;
|
const { data, count, shape, pagination, loading = "lazy" } = Astro.props;
|
||||||
const { globals } = await fetchGlobalData(Astro);
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
|
@ -9,24 +9,24 @@ const hidePagination = pageCount <= 1;
|
||||||
|
|
||||||
function getImageAttributes(item, shape) {
|
function getImageAttributes(item, shape) {
|
||||||
let imageUrl = item.grid.image;
|
let imageUrl = item.grid.image;
|
||||||
let imageClass = '';
|
let imageClass = "";
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let height = 0;
|
let height = 0;
|
||||||
|
|
||||||
switch (shape) {
|
switch (shape) {
|
||||||
case 'poster':
|
case "poster":
|
||||||
imageUrl = item.grid.backdrop;
|
imageUrl = item.grid.backdrop;
|
||||||
imageClass = 'banner';
|
imageClass = "banner";
|
||||||
width = 256;
|
width = 256;
|
||||||
height = 170;
|
height = 170;
|
||||||
break;
|
break;
|
||||||
case 'square':
|
case "square":
|
||||||
imageClass = 'square';
|
imageClass = "square";
|
||||||
width = 200;
|
width = 200;
|
||||||
height = 200;
|
height = 200;
|
||||||
break;
|
break;
|
||||||
case 'vertical':
|
case "vertical":
|
||||||
imageClass = 'vertical';
|
imageClass = "vertical";
|
||||||
width = 200;
|
width = 200;
|
||||||
height = 307;
|
height = 307;
|
||||||
break;
|
break;
|
||||||
|
@ -37,36 +37,39 @@ function getImageAttributes(item, shape) {
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class={`media-grid ${shape}`}>
|
<div class={`media-grid ${shape}`}>
|
||||||
{data.slice(0, count).map((item) => {
|
{
|
||||||
const alt = item.grid.alt?.replace(/['"]/g, '');
|
data.slice(0, count).map((item) => {
|
||||||
const { imageUrl, imageClass, width, height } = getImageAttributes(item, shape);
|
const alt = item.grid.alt?.replace(/['"]/g, "");
|
||||||
|
const { imageUrl, imageClass, width, height } = getImageAttributes(
|
||||||
|
item,
|
||||||
|
shape
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={item.grid.url} title={alt}>
|
<a href={item.grid.url} title={alt}>
|
||||||
<div class="item media-overlay">
|
<div class="item media-overlay">
|
||||||
<div class="meta-text">
|
<div class="meta-text">
|
||||||
<div class="header">{item.grid.title}</div>
|
<div class="header">{item.grid.title}</div>
|
||||||
<div class="subheader">{item.grid.subtext}</div>
|
<div class="subheader">{item.grid.subtext}</div>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
srcSet={`
|
srcSet={`
|
||||||
${globals.cdn_url}${imageUrl}?class=${imageClass}sm&type=webp ${width}w,
|
${globals.cdn_url}${imageUrl}?class=${imageClass}sm&type=webp ${width}w,
|
||||||
${globals.cdn_url}${imageUrl}?class=${imageClass}md&type=webp ${width * 2}w
|
${globals.cdn_url}${imageUrl}?class=${imageClass}md&type=webp ${width * 2}w
|
||||||
`}
|
`}
|
||||||
sizes={`(max-width: 450px) ${width}px, ${width * 2}px`}
|
sizes={`(max-width: 450px) ${width}px, ${width * 2}px`}
|
||||||
src={`${globals.cdn_url}${imageUrl}?class=${imageClass}sm&type=webp`}
|
src={`${globals.cdn_url}${imageUrl}?class=${imageClass}sm&type=webp`}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
decoding="async"
|
decoding="async"
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!hidePagination && (
|
{!hidePagination && <Paginator pagination={pagination} />}
|
||||||
<Paginator pagination={pagination} />
|
|
||||||
)}
|
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
---
|
---
|
||||||
import ProgressBar from '@components/media/ProgressBar.astro';
|
import ProgressBar from "@components/media/ProgressBar.astro";
|
||||||
|
|
||||||
const { data, count } = Astro.props;
|
const { data, count } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="music-chart">
|
<div class="music-chart">
|
||||||
<ol type="1">
|
<ol type="1">
|
||||||
{data.slice(0, count).map((item) => {
|
{
|
||||||
const percentage = `${item.chart.percentage}%`;
|
data.slice(0, count).map((item) => {
|
||||||
const playsLabel = item.chart.plays === 1 ? 'play' : 'plays';
|
const percentage = `${item.chart.percentage}%`;
|
||||||
|
const playsLabel = item.chart.plays === 1 ? "play" : "plays";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li value={item.chart.rank}>
|
<li value={item.chart.rank}>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<a class="title" href={item.chart.url}>{item.chart.title}</a>
|
<a class="title" href={item.chart.url}>
|
||||||
<span class="subtext">{item.chart.artist}</span>
|
{item.chart.title}
|
||||||
<span class="subtext">
|
</a>
|
||||||
{item.chart.plays} {playsLabel}
|
<span class="subtext">{item.chart.artist}</span>
|
||||||
</span>
|
<span class="subtext">
|
||||||
|
{item.chart.plays} {playsLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar percentage={percentage} />
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar percentage={percentage} />
|
</li>
|
||||||
</div>
|
);
|
||||||
</li>
|
})
|
||||||
);
|
}
|
||||||
})}
|
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
---
|
---
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { data } = Astro.props;
|
const { data } = Astro.props;
|
||||||
const { globals } = await fetchGlobalData(Astro);
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="music-chart">
|
<div class="music-chart">
|
||||||
{data.slice(0, 10).map((item) => (
|
{
|
||||||
<div class="item">
|
data.slice(0, 10).map((item) => (
|
||||||
<div class="meta">
|
<div class="item">
|
||||||
<a href={item.chart.url}>
|
<div class="meta">
|
||||||
<img
|
<a href={item.chart.url}>
|
||||||
srcSet={`
|
<img
|
||||||
|
srcSet={`
|
||||||
${globals.cdn_url}${item.chart.image}?class=w50&type=webp 50w,
|
${globals.cdn_url}${item.chart.image}?class=w50&type=webp 50w,
|
||||||
${globals.cdn_url}${item.chart.image}?class=w100&type=webp 100w
|
${globals.cdn_url}${item.chart.image}?class=w100&type=webp 100w
|
||||||
`}
|
`}
|
||||||
sizes="(max-width: 450px) 50px, 100px"
|
sizes="(max-width: 450px) 50px, 100px"
|
||||||
src={`${globals.cdn_url}${item.chart.image}?class=w50&type=webp`}
|
src={`${globals.cdn_url}${item.chart.image}?class=w50&type=webp`}
|
||||||
alt={item.chart.alt.replace(/['"]/g, '')}
|
alt={item.chart.alt.replace(/['"]/g, "")}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
width="64"
|
width="64"
|
||||||
height="64"
|
height="64"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div class="meta-text">
|
<div class="meta-text">
|
||||||
<a class="title" href={item.chart.url}>{item.chart.title}</a>
|
<a class="title" href={item.chart.url}>
|
||||||
<span class="subtext">{item.chart.subtext}</span>
|
{item.chart.title}
|
||||||
|
</a>
|
||||||
|
<span class="subtext">{item.chart.subtext}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<time dateTime={item.chart.played_at}>
|
||||||
|
{new Date(item.chart.played_at).toLocaleString("en-US", {
|
||||||
|
timeZone: "America/Los_Angeles",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
hour12: true,
|
||||||
|
})}
|
||||||
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<time dateTime={item.chart.played_at}>
|
))
|
||||||
{new Date(item.chart.played_at).toLocaleString("en-US", {
|
}
|
||||||
timeZone: "America/Los_Angeles",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
hour12: true,
|
|
||||||
})}
|
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
import Hero from "@components/blocks/Hero.astro";
|
import Hero from "@components/blocks/Hero.astro";
|
||||||
|
|
||||||
const { movie } = Astro.props;
|
const { movie } = Astro.props;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import { IconMenu2, IconX } from '@tabler/icons-react';
|
import { IconMenu2, IconX } from "@tabler/icons-react";
|
||||||
import NavLink from './NavLink.astro';
|
import NavLink from "./NavLink.astro";
|
||||||
|
|
||||||
const { nav } = Astro.props;
|
const { nav } = Astro.props;
|
||||||
---
|
---
|
||||||
|
@ -16,15 +16,17 @@ const { nav } = Astro.props;
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<ul class="menu-primary" aria-label="Primary site navigation" id="primary-navigation">
|
<ul
|
||||||
{nav.primary.map((link) => (
|
class="menu-primary"
|
||||||
<li>
|
aria-label="Primary site navigation"
|
||||||
<NavLink
|
id="primary-navigation"
|
||||||
url={link.permalink}
|
>
|
||||||
title={link.title}
|
{
|
||||||
icon={link.icon}
|
nav.primary.map((link) => (
|
||||||
/>
|
<li>
|
||||||
</li>
|
<NavLink url={link.permalink} title={link.title} icon={link.icon} />
|
||||||
))}
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
---
|
---
|
||||||
import IconMapper from '@components/IconMapper.astro';
|
import IconMapper from "@components/IconMapper.astro";
|
||||||
|
|
||||||
const { url, title, icon } = Astro.props;
|
const { url, title, icon } = Astro.props;
|
||||||
const isHttp = url.startsWith('http');
|
const isHttp = url.startsWith("http");
|
||||||
const isActive = Astro.url.pathname === url;
|
const isActive = Astro.url.pathname === url;
|
||||||
---
|
---
|
||||||
|
|
||||||
{isActive ? (
|
{
|
||||||
<span class={`active icon ${icon?.toLowerCase()}`} aria-current="page">
|
isActive ? (
|
||||||
<IconMapper icon={icon} />
|
<span class={`active icon ${icon?.toLowerCase()}`} aria-current="page">
|
||||||
<span>{title}</span>
|
<IconMapper icon={icon} />
|
||||||
</span>
|
<span>{title}</span>
|
||||||
) : (
|
</span>
|
||||||
<a
|
) : (
|
||||||
class={`icon ${icon}`}
|
<a
|
||||||
href={url}
|
class={`icon ${icon}`}
|
||||||
rel={isHttp ? 'me' : undefined}
|
href={url}
|
||||||
aria-label={title}
|
rel={isHttp ? "me" : undefined}
|
||||||
>
|
aria-label={title}
|
||||||
<IconMapper icon={icon} />
|
>
|
||||||
<span>{title}</span>
|
<IconMapper icon={icon} />
|
||||||
</a>
|
<span>{title}</span>
|
||||||
)}
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
10
src/components/utils/ToggleContent.astro
Normal file
10
src/components/utils/ToggleContent.astro
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
import { md } from "@utils/helpers/general.js";
|
||||||
|
|
||||||
|
const { content } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div data-toggle-content class="text-toggle-hidden">
|
||||||
|
<div set:html={md(content)} />
|
||||||
|
</div>
|
||||||
|
<button data-toggle-button>Show more</button>
|
|
@ -3,7 +3,7 @@ import "@styles/index.css";
|
||||||
import Header from "@components/Header.astro";
|
import Header from "@components/Header.astro";
|
||||||
import Footer from "@components/Footer.astro";
|
import Footer from "@components/Footer.astro";
|
||||||
import Metadata from "@components/Metadata.astro";
|
import Metadata from "@components/Metadata.astro";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
schema = "page",
|
schema = "page",
|
||||||
|
@ -61,7 +61,11 @@ const isProduction = import.meta.env.MODE === "production";
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<noscript>
|
<noscript>
|
||||||
<style>.client-side {display:none}</style>
|
<style>
|
||||||
|
.client-side {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</noscript>
|
</noscript>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -74,5 +78,6 @@ const isProduction = import.meta.env.MODE === "production";
|
||||||
</main>
|
</main>
|
||||||
<Footer updated={updated} />
|
<Footer updated={updated} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import Layout from '@layouts/Layout.astro';
|
import Layout from "@layouts/Layout.astro";
|
||||||
import BlockRenderer from '@components/blocks/BlockRenderer.astro';
|
import BlockRenderer from "@components/blocks/BlockRenderer.astro";
|
||||||
import { fetchPages } from '@utils/data/pages';
|
import { fetchPages } from "@utils/data/pages.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
|
@ -26,7 +26,5 @@ const currentUrl = Astro.url.pathname;
|
||||||
updated={page.updated}
|
updated={page.updated}
|
||||||
currentUrl={currentUrl}
|
currentUrl={currentUrl}
|
||||||
>
|
>
|
||||||
{page.blocks.map((block) => (
|
{page.blocks.map((block) => <BlockRenderer block={block} />)}
|
||||||
<BlockRenderer block={block} />
|
|
||||||
))}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,21 +1,28 @@
|
||||||
---
|
---
|
||||||
import Layout from '@layouts/Layout.astro';
|
import Layout from "@layouts/Layout.astro";
|
||||||
import { IconRss, IconJson, IconMailPlus, IconBrandMastodon } from "@tabler/icons-react";
|
import {
|
||||||
import { fetchBlogroll } from '@utils/data/blogroll.js';
|
IconRss,
|
||||||
|
IconJson,
|
||||||
|
IconMailPlus,
|
||||||
|
IconBrandMastodon,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { fetchBlogroll } from "@utils/data/blogroll.js";
|
||||||
|
|
||||||
const blogroll = await fetchBlogroll();
|
const blogroll = await fetchBlogroll();
|
||||||
const currentUrl = Astro.url.pathname;
|
const currentUrl = Astro.url.pathname;
|
||||||
const title = "Blogroll";
|
const title = "Blogroll";
|
||||||
const description = "These are awesome blogs that I enjoy and you may enjoy too.";
|
const description =
|
||||||
|
"These are awesome blogs that I enjoy and you may enjoy too.";
|
||||||
---
|
---
|
||||||
<Layout
|
|
||||||
pageTitle={title}
|
<Layout pageTitle={title} description={description} currentUrl={currentUrl}>
|
||||||
description={description}
|
|
||||||
currentUrl={currentUrl}
|
|
||||||
>
|
|
||||||
<h2 class="page-title">{title}</h2>
|
<h2 class="page-title">{title}</h2>
|
||||||
<p>
|
<p>
|
||||||
You can <a href="/blogroll.opml" class="plausible-event-name=Blogroll+OPML+download">download an OPML file</a> containing all of these feeds and import them into your RSS reader.
|
You can <a
|
||||||
|
href="/blogroll.opml"
|
||||||
|
class="plausible-event-name=Blogroll+OPML+download"
|
||||||
|
>download an OPML file</a
|
||||||
|
> containing all of these feeds and import them into your RSS reader.
|
||||||
</p>
|
</p>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -26,39 +33,60 @@ const description = "These are awesome blogs that I enjoy and you may enjoy too.
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{blogroll.map((blog) => (
|
{
|
||||||
<tr>
|
blogroll.map((blog) => (
|
||||||
<td>{blog.name}</td>
|
<tr>
|
||||||
<td>
|
<td>{blog.name}</td>
|
||||||
<a href={blog.url}>{blog.url.replace("https://", "")}</a>
|
<td>
|
||||||
</td>
|
<a href={blog.url}>{blog.url.replace("https://", "")}</a>
|
||||||
<td class="blog-roll-icons">
|
</td>
|
||||||
{blog.rss_feed && (
|
<td class="blog-roll-icons">
|
||||||
<a class="rss" href={blog.rss_feed} aria-label={`RSS feed for ${blog.name}`}>
|
{blog.rss_feed && (
|
||||||
<IconRss size={16} />
|
<a
|
||||||
</a>
|
class="rss"
|
||||||
)}
|
href={blog.rss_feed}
|
||||||
{blog.json_feed && (
|
aria-label={`RSS feed for ${blog.name}`}
|
||||||
<a class="json" href={blog.json_feed} aria-label={`JSON feed for ${blog.name}`}>
|
>
|
||||||
<IconJson size={16} />
|
<IconRss size={16} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{blog.newsletter && (
|
{blog.json_feed && (
|
||||||
<a class="mail-plus" href={blog.newsletter} aria-label={`Subscribe to ${blog.name}'s newsletter`}>
|
<a
|
||||||
<IconMailPlus size={16} />
|
class="json"
|
||||||
</a>
|
href={blog.json_feed}
|
||||||
)}
|
aria-label={`JSON feed for ${blog.name}`}
|
||||||
{blog.mastodon && (
|
>
|
||||||
<a class="brand-mastodon" href={blog.mastodon} aria-label={`Follow ${blog.name} on Mastodon`}>
|
<IconJson size={16} />
|
||||||
<IconBrandMastodon size={16} />
|
</a>
|
||||||
</a>
|
)}
|
||||||
)}
|
{blog.newsletter && (
|
||||||
</td>
|
<a
|
||||||
</tr>
|
class="mail-plus"
|
||||||
))}
|
href={blog.newsletter}
|
||||||
|
aria-label={`Subscribe to ${blog.name}'s newsletter`}
|
||||||
|
>
|
||||||
|
<IconMailPlus size={16} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{blog.mastodon && (
|
||||||
|
<a
|
||||||
|
class="brand-mastodon"
|
||||||
|
href={blog.mastodon}
|
||||||
|
aria-label={`Follow ${blog.name} on Mastodon`}
|
||||||
|
>
|
||||||
|
<IconBrandMastodon size={16} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
Head on over to <a href="https://blogroll.org">blogroll.org</a> to find more blogs to follow or search for feeds using <a href="https://feedle.world">feedle</a>.
|
Head on over to <a href="https://blogroll.org">blogroll.org</a> to find more
|
||||||
|
blogs to follow or search for feeds using <a href="https://feedle.world"
|
||||||
|
>feedle</a
|
||||||
|
>.
|
||||||
</p>
|
</p>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { fetchBlogroll } from "@utils/data/blogroll";
|
import { fetchBlogroll } from "@utils/data/blogroll.js";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import AssociatedMedia from "@components/blocks/AssociatedMedia.astro";
|
||||||
import ProgressBar from "@components/media/ProgressBar.astro";
|
import ProgressBar from "@components/media/ProgressBar.astro";
|
||||||
import { IconArrowLeft, IconHeart, IconNeedle } from "@tabler/icons-react";
|
import { IconArrowLeft, IconHeart, IconNeedle } from "@tabler/icons-react";
|
||||||
import { fetchBookByUrl } from "@utils/data/dynamic/bookByUrl.js";
|
import { fetchBookByUrl } from "@utils/data/dynamic/bookByUrl.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { isbn } = Astro.params;
|
const { isbn } = Astro.params;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Layout from "@layouts/Layout.astro";
|
||||||
import Rss from "@components/blocks/banners/Rss.astro";
|
import Rss from "@components/blocks/banners/Rss.astro";
|
||||||
import ProgressBar from "@components/media/ProgressBar.astro";
|
import ProgressBar from "@components/media/ProgressBar.astro";
|
||||||
import { fetchBooks } from "@utils/data/books.js";
|
import { fetchBooks } from "@utils/data/books.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
import { md, htmlTruncate } from "@utils/helpers/general.js";
|
import { md, htmlTruncate } from "@utils/helpers/general.js";
|
||||||
|
|
||||||
const books = await fetchBooks();
|
const books = await fetchBooks();
|
||||||
|
@ -30,7 +30,6 @@ const bookYearLinks = (years) =>
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
globals={globals}
|
|
||||||
pageTitle={title}
|
pageTitle={title}
|
||||||
description={description}
|
description={description}
|
||||||
updated={updated}
|
updated={updated}
|
||||||
|
@ -43,7 +42,7 @@ const bookYearLinks = (years) =>
|
||||||
>{currentBookCount} books</strong
|
>{currentBookCount} books</strong
|
||||||
> this year.
|
> this year.
|
||||||
</p>
|
</p>
|
||||||
<p set:html={bookYearLinks(books.years)}></p>
|
<p set:html={bookYearLinks(books.years)} />
|
||||||
<Rss
|
<Rss
|
||||||
url="/feeds/books"
|
url="/feeds/books"
|
||||||
text="Subscribe to my books feed or follow along on this page"
|
text="Subscribe to my books feed or follow along on this page"
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import Grid from "@components/media/Grid.astro";
|
import Grid from "@components/media/Grid.astro";
|
||||||
import { IconArrowLeft } from "@tabler/icons-react";
|
import { IconArrowLeft } from "@tabler/icons-react";
|
||||||
import { filterBooksByStatus, findFavoriteBooks, mediaLinks } from "@utils/helpers/media.js";
|
import {
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
filterBooksByStatus,
|
||||||
|
findFavoriteBooks,
|
||||||
|
mediaLinks,
|
||||||
|
} from "@utils/helpers/media.js";
|
||||||
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
import { fetchBooks } from "@utils/data/books.js";
|
import { fetchBooks } from "@utils/data/books.js";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
@ -26,21 +30,30 @@ const description = isCurrentYear
|
||||||
const intro = isCurrentYear
|
const intro = isCurrentYear
|
||||||
? `
|
? `
|
||||||
I've finished <strong class="highlight-text">${bookData.length} books</strong> this year.
|
I've finished <strong class="highlight-text">${bookData.length} books</strong> this year.
|
||||||
${favoriteBooks ? ` Among my favorites are ${favoriteBooks}.` : ''}
|
${favoriteBooks ? ` Among my favorites are ${favoriteBooks}.` : ""}
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
I finished <strong class="highlight-text">${bookData.length} books</strong> in
|
I finished <strong class="highlight-text">${bookData.length} books</strong> in
|
||||||
<strong class="highlight-text">${year}</strong>.
|
<strong class="highlight-text">${year}</strong>.
|
||||||
${favoriteBooks ? ` Among my favorites were ${favoriteBooks}.` : ''}
|
${favoriteBooks ? ` Among my favorites were ${favoriteBooks}.` : ""}
|
||||||
`;
|
`;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout globals={globals} pageTitle={pageTitle} description={description} schema="books-year">
|
<Layout
|
||||||
|
pageTitle={pageTitle}
|
||||||
|
description={description}
|
||||||
|
schema="books-year"
|
||||||
|
>
|
||||||
<a href="/books" class="back-link">
|
<a href="/books" class="back-link">
|
||||||
<IconArrowLeft size={18} /> Back to books
|
<IconArrowLeft size={18} /> Back to books
|
||||||
</a>
|
</a>
|
||||||
<h2 class="page-title">{year} / Books</h2>
|
<h2 class="page-title">{year} / Books</h2>
|
||||||
<div set:html={intro}></div>
|
<div set:html={intro} />
|
||||||
<hr />
|
<hr />
|
||||||
<Grid globals={globals} data={bookData} shape="vertical" count={200} loading="eager" />
|
<Grid
|
||||||
|
data={bookData}
|
||||||
|
shape="vertical"
|
||||||
|
count={200}
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { generateJsonFeed } from '@utils/generateJsonFeed';
|
import { generateJsonFeed } from "@utils/generateJsonFeed.js";
|
||||||
import { fetchGlobals } from '@utils/data/globals';
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchActivity } from '@utils/data/activity';
|
import { fetchActivity } from "@utils/data/activity.js";
|
||||||
import fs from 'fs/promises';
|
import fs from "fs/promises";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const globals = await fetchGlobals();
|
const globals = await fetchGlobals();
|
||||||
|
@ -15,7 +15,7 @@ export async function getStaticPaths() {
|
||||||
data: activity,
|
data: activity,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filePath = path.resolve('public/feeds/all.json');
|
const filePath = path.resolve("public/feeds/all.json");
|
||||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||||
await fs.writeFile(filePath, feed);
|
await fs.writeFile(filePath, feed);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateRssFeed } from "@utils/generateRssFeed";
|
import { generateRssFeed } from "@utils/generateRssFeed";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchActivity } from "@utils/data/activity";
|
import { fetchActivity } from "@utils/data/activity.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateJsonFeed } from "@utils/generateJsonFeed";
|
import { generateJsonFeed } from "@utils/generateJsonFeed.js";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchBooks } from "@utils/data/books";
|
import { fetchBooks } from "@utils/data/books.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateRssFeed } from "@utils/generateRssFeed";
|
import { generateRssFeed } from "@utils/generateRssFeed";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchBooks } from "@utils/data/books";
|
import { fetchBooks } from "@utils/data/books.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateJsonFeed } from "@utils/generateJsonFeed";
|
import { generateJsonFeed } from "@utils/generateJsonFeed.js";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchLinks } from "@utils/data/links";
|
import { fetchLinks } from "@utils/data/links.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { generateRssFeed } from "@utils/generateRssFeed";
|
import { generateRssFeed } from "@utils/generateRssFeed";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchLinks } from "@utils/data/links";
|
import { fetchLinks } from "@utils/data/links";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { generateJsonFeed } from '@utils/generateJsonFeed';
|
import { generateJsonFeed } from '@utils/generateJsonFeed.js';
|
||||||
import { fetchGlobals } from '@utils/data/globals';
|
import { fetchGlobals } from '@utils/data/globals.js';
|
||||||
import { fetchMovies } from '@utils/data/movies';
|
import { fetchMovies } from '@utils/data/movies';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateRssFeed } from "@utils/generateRssFeed";
|
import { generateRssFeed } from "@utils/generateRssFeed";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchMovies } from "@utils/data/movies";
|
import { fetchMovies } from "@utils/data/movies.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateJsonFeed } from "@utils/generateJsonFeed";
|
import { generateJsonFeed } from "@utils/generateJsonFeed.js";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchAllPosts } from "@utils/data/posts";
|
import { fetchAllPosts } from "@utils/data/posts.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateRssFeed } from "@utils/generateRssFeed";
|
import { generateRssFeed } from "@utils/generateRssFeed";
|
||||||
import { fetchGlobals } from "@utils/data/globals";
|
import { fetchGlobals } from "@utils/data/globals.js";
|
||||||
import { fetchAllPosts } from "@utils/data/posts";
|
import { fetchAllPosts } from "@utils/data/posts.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { fetchGlobals } from '@utils/data/globals';
|
import { fetchGlobals } from '@utils/data/globals.js';
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
---
|
---
|
||||||
import Layout from '@layouts/Layout.astro';
|
import Layout from "@layouts/Layout.astro";
|
||||||
import Intro from '@components/home/Intro.astro';
|
import Intro from "@components/home/Intro.astro";
|
||||||
import RecentActivity from '@components/home/RecentActivity.astro';
|
import RecentActivity from "@components/home/RecentActivity.astro";
|
||||||
import RecentPosts from '@components/home/RecentPosts.astro';
|
import RecentPosts from "@components/home/RecentPosts.astro";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { globals } = await fetchGlobalData(Astro);
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
const schema = 'blog';
|
const schema = "blog";
|
||||||
const pageTitle = globals.site_name;
|
const pageTitle = globals.site_name;
|
||||||
const description = 'This is a blog post description';
|
const description = "This is a blog post description";
|
||||||
const ogImage = globals.cdn_url + globals.avatar;
|
const ogImage = globals.cdn_url + globals.avatar;
|
||||||
const fullUrl = globals.url + '/blog/my-post';
|
const fullUrl = globals.url + "/blog/my-post";
|
||||||
const themeColor = globals.theme_color;
|
const themeColor = globals.theme_color;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
pageTitle={pageTitle}
|
pageTitle={pageTitle}
|
||||||
description={description}
|
description={description}
|
||||||
|
@ -24,4 +25,4 @@ const themeColor = globals.theme_color;
|
||||||
<Intro intro={globals.intro} />
|
<Intro intro={globals.intro} />
|
||||||
<RecentActivity />
|
<RecentActivity />
|
||||||
<RecentPosts />
|
<RecentPosts />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -3,16 +3,20 @@ import Layout from "@layouts/Layout.astro";
|
||||||
import Paginator from "@components/nav/Paginator.astro";
|
import Paginator from "@components/nav/Paginator.astro";
|
||||||
import RssBanner from "@components/blocks/banners/Rss.astro";
|
import RssBanner from "@components/blocks/banners/Rss.astro";
|
||||||
import { fetchLinks } from "@utils/data/links.js";
|
import { fetchLinks } from "@utils/data/links.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
|
||||||
const { globals } = await fetchGlobalData(Astro);
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
const links = await fetchLinks();
|
const links = await fetchLinks();
|
||||||
const title = "Links";
|
const title = "Links";
|
||||||
const description = "These are links I've liked or otherwise found interesting. They're all added manually, after having been read and, I suppose, properly considered.";
|
const description =
|
||||||
|
"These are links I've liked or otherwise found interesting. They're all added manually, after having been read and, I suppose, properly considered.";
|
||||||
const pageSize = 30;
|
const pageSize = 30;
|
||||||
const currentPage = parseInt(Astro.url.searchParams.get("page") || "1", 10);
|
const currentPage = parseInt(Astro.url.searchParams.get("page") || "1", 10);
|
||||||
const totalPages = Math.ceil(links.length / pageSize);
|
const totalPages = Math.ceil(links.length / pageSize);
|
||||||
const paginatedLinks = links.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
const paginatedLinks = links.slice(
|
||||||
|
(currentPage - 1) * pageSize,
|
||||||
|
currentPage * pageSize
|
||||||
|
);
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
currentPage,
|
currentPage,
|
||||||
|
@ -29,37 +33,40 @@ const pagination = {
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
globals={globals}
|
|
||||||
pageTitle={title}
|
pageTitle={title}
|
||||||
description={description}
|
description={description}
|
||||||
currentUrl={Astro.url.pathname}
|
currentUrl={Astro.url.pathname}
|
||||||
>
|
>
|
||||||
{currentPage === 1 && (
|
{
|
||||||
<>
|
currentPage === 1 && (
|
||||||
<h2 class="page-title">{title}</h2>
|
<>
|
||||||
<p>{description}</p>
|
<h2 class="page-title">{title}</h2>
|
||||||
<RssBanner
|
<p>{description}</p>
|
||||||
url="/feeds/links"
|
<RssBanner
|
||||||
text="Subscribe to my links feed or follow along on this page"
|
url="/feeds/links"
|
||||||
/>
|
text="Subscribe to my links feed or follow along on this page"
|
||||||
<hr />
|
/>
|
||||||
</>
|
<hr />
|
||||||
)}
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<div class="link-grid">
|
<div class="link-grid">
|
||||||
{paginatedLinks.map((link) => (
|
{
|
||||||
<div class="link-box">
|
paginatedLinks.map((link) => (
|
||||||
<a href={link.link} title={link.title}>
|
<div class="link-box">
|
||||||
<strong>{link.title}</strong>
|
<a href={link.link} title={link.title}>
|
||||||
</a>
|
<strong>{link.title}</strong>
|
||||||
{link.author && (
|
</a>
|
||||||
<>
|
{link.author && (
|
||||||
{" via "}
|
<>
|
||||||
<a href={link.author.url}>{link.author.name}</a>
|
{" via "}
|
||||||
</>
|
<a href={link.author.url}>{link.author.name}</a>
|
||||||
)}
|
</>
|
||||||
</div>
|
)}
|
||||||
))}
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Paginator pagination={pagination} />
|
<Paginator pagination={pagination} />
|
||||||
|
|
133
src/pages/music/artists/[slug].astro
Normal file
133
src/pages/music/artists/[slug].astro
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
---
|
||||||
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import ToggleContent from "@components/utils/ToggleContent.astro";
|
||||||
|
import AssociatedMedia from "@components/blocks/AssociatedMedia.astro";
|
||||||
|
import { IconArrowLeft, IconHeart, IconNeedle, IconMapPin, IconDeviceSpeaker } from "@tabler/icons-react";
|
||||||
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
|
import { fetchArtistByUrl } from "@utils/data/dynamic/artistByUrl.js";
|
||||||
|
|
||||||
|
const { globals } = await fetchGlobalData(Astro);
|
||||||
|
const artist = await fetchArtistByUrl(Astro.url.pathname);
|
||||||
|
|
||||||
|
if (!artist) return Astro.redirect("/404", 404);
|
||||||
|
|
||||||
|
const pageTitle = `${artist.name} / Music`;
|
||||||
|
const description = artist.description || `Learn more about ${artist.name}`;
|
||||||
|
const alt = `${artist.name} / ${artist.country}`;
|
||||||
|
const playLabel = artist.total_plays === 1 ? "play" : "plays";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout pageTitle={pageTitle} description={description} schema="artist">
|
||||||
|
<a href="/music" class="back-link">
|
||||||
|
<IconArrowLeft size={18} /> Back to music
|
||||||
|
</a>
|
||||||
|
<article class="artist-focus">
|
||||||
|
<div class="artist-display">
|
||||||
|
<img
|
||||||
|
srcset={`
|
||||||
|
${globals.cdn_url}${artist.image}?class=w200&type=webp 200w,
|
||||||
|
${globals.cdn_url}${artist.image}?class=w600&type=webp 400w,
|
||||||
|
${globals.cdn_url}${artist.image}?class=w800&type=webp 800w
|
||||||
|
`}
|
||||||
|
sizes="(max-width: 450px) 200px,
|
||||||
|
(max-width: 850px) 400px,
|
||||||
|
800px"
|
||||||
|
src={`${globals.cdn_url}${artist.image}?class=w200&type=webp`}
|
||||||
|
alt={alt}
|
||||||
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
/>
|
||||||
|
<div class="media-meta">
|
||||||
|
<span class="title"><strong>{artist.name}</strong></span>
|
||||||
|
<span class="sub-meta country">
|
||||||
|
<IconMapPin size={18} /> {artist.country}
|
||||||
|
</span>
|
||||||
|
{artist.favorite && (
|
||||||
|
<span class="sub-meta favorite">
|
||||||
|
<IconHeart size={18} /> This is one of my favorite artists!
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{artist.tattoo && (
|
||||||
|
<span class="sub-meta tattoo">
|
||||||
|
<IconNeedle size={18} /> I have a tattoo inspired by this artist!
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{artist.total_plays > 0 && (
|
||||||
|
<span class="sub-meta">
|
||||||
|
<strong class="highlight-text">{artist.total_plays} {playLabel}</strong>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span class="sub-meta">
|
||||||
|
<a href={artist.genre.url} title={`Learn more about ${artist.genre.name}`}>
|
||||||
|
{artist.genre.name}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AssociatedMedia
|
||||||
|
artists={artist.related_artists}
|
||||||
|
books={artist.books}
|
||||||
|
genres={artist.genres}
|
||||||
|
movies={artist.movies}
|
||||||
|
posts={artist.posts}
|
||||||
|
shows={artist.shows}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{artist.description && (
|
||||||
|
<>
|
||||||
|
<h2>Overview</h2>
|
||||||
|
<ToggleContent content={artist.description} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{artist.concerts && (
|
||||||
|
<>
|
||||||
|
<p id="concerts" class="concerts">
|
||||||
|
<IconDeviceSpeaker size={18} /> I've seen this artist live!
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{artist.concerts.map((concert) => {
|
||||||
|
const venue = concert.venue_latitude && concert.venue_longitude
|
||||||
|
? `<a href="https://www.openstreetmap.org/?mlat=${concert.venue_latitude}&mlon=${concert.venue_longitude}#map=18/${concert.venue_latitude}/${concert.venue_longitude}">${concert.venue_name_short}</a>`
|
||||||
|
: concert.venue_name_short;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
On <strong class="highlight-text">{concert.date.toLocaleString(DateTime.DATE_MED)}</strong>
|
||||||
|
{venue && <> at {venue}</>}
|
||||||
|
{concert.notes && (
|
||||||
|
<span> — {concert.notes}</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{artist.albums && (
|
||||||
|
<>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Album</th>
|
||||||
|
<th>Plays</th>
|
||||||
|
<th>Year</th>
|
||||||
|
</tr>
|
||||||
|
{artist.albums.map((album) => (
|
||||||
|
<tr>
|
||||||
|
<td>{album.name}</td>
|
||||||
|
<td>{album.total_plays}</td>
|
||||||
|
<td>{album.release_year}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
<em>These are the albums by this artist that are in my collection, not necessarily a comprehensive discography.</em>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
</Layout>
|
|
@ -1,23 +1,24 @@
|
||||||
import { albumReleasesCalendar } from '@utils/albumReleasesCalendar';
|
import { albumReleasesCalendar } from "@utils/albumReleasesCalendar.js";
|
||||||
import { fetchAlbumReleases } from '@utils/data/albumReleases';
|
import { fetchAlbumReleases } from "@utils/data/albumReleases.js";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const { all: albumReleases } = await fetchAlbumReleases();
|
const { all: albumReleases } = await fetchAlbumReleases();
|
||||||
const icsContent = await albumReleasesCalendar(albumReleases);
|
const icsContent = await albumReleasesCalendar(albumReleases);
|
||||||
|
|
||||||
if (!icsContent) return new Response('Error generating ICS file', { status: 500 });
|
if (!icsContent)
|
||||||
|
return new Response("Error generating ICS file", { status: 500 });
|
||||||
|
|
||||||
return new Response(icsContent, {
|
return new Response(icsContent, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/calendar',
|
"Content-Type": "text/calendar",
|
||||||
'Content-Disposition': 'attachment; filename="releases.ics"',
|
"Content-Disposition": 'attachment; filename="releases.ics"',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating album releases ICS file:', error);
|
console.error("Error generating album releases ICS file:", error);
|
||||||
return new Response('Error generating album releases ICS file', {
|
return new Response("Error generating album releases ICS file", {
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from "astro:content";
|
||||||
import { IconStar } from '@tabler/icons-react';
|
import { IconStar } from "@tabler/icons-react";
|
||||||
import { fetchAllPosts } from "@data/posts.js";
|
import { fetchAllPosts } from "@data/posts.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import Paginator from '@components/nav/Paginator.astro';
|
import Paginator from "@components/nav/Paginator.astro";
|
||||||
import { md } from '@utils/helpers/general.js';
|
import { md } from "@utils/helpers/general.js";
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
const posts = await fetchAllPosts();
|
const posts = await fetchAllPosts();
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
|
@ -35,25 +35,23 @@ const pagination = {
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout pageTitle="All posts" currentUrl={currentUrl}>
|
||||||
globals={globals}
|
{
|
||||||
pageTitle="All posts"
|
paginatedPosts.map((post) => (
|
||||||
currentUrl={currentUrl}
|
<article>
|
||||||
>
|
<div class="post-meta">
|
||||||
{paginatedPosts.map((post) => (
|
{post.featured && <IconStar size={16} />}
|
||||||
<article>
|
<time datetime={post.date}>
|
||||||
<div class="post-meta">
|
{DateTime.fromISO(post.date).toLocaleString(DateTime.DATE_FULL)}
|
||||||
{post.featured && <IconStar size={16} />}
|
</time>
|
||||||
<time datetime={post.date}>
|
</div>
|
||||||
{DateTime.fromISO(post.date).toLocaleString(DateTime.DATE_FULL)}
|
<h3>
|
||||||
</time>
|
<a href={post.url}>{post.title}</a>
|
||||||
</div>
|
</h3>
|
||||||
<h3>
|
<p set:html={md(post.description)} />
|
||||||
<a href={post.url}>{post.title}</a>
|
</article>
|
||||||
</h3>
|
))
|
||||||
<p set:html={md(post.description)}></p>
|
}
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Paginator pagination={pagination} />
|
<Paginator pagination={pagination} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -3,16 +3,16 @@ import { IconStar } from "@tabler/icons-react";
|
||||||
import { fetchAllPosts } from "@data/posts.js";
|
import { fetchAllPosts } from "@data/posts.js";
|
||||||
import { fetchAnalyticsData } from "@data/analytics.js";
|
import { fetchAnalyticsData } from "@data/analytics.js";
|
||||||
import { fetchLinks } from "@data/links.js";
|
import { fetchLinks } from "@data/links.js";
|
||||||
import { fetchGlobalData } from '@utils/data/global/index.js';
|
import { fetchGlobalData } from "@utils/data/global/index.js";
|
||||||
import { md } from '@utils/helpers/general.js';
|
import { md } from "@utils/helpers/general.js";
|
||||||
import { getPopularPosts } from '@utils/getPopularPosts.js';
|
import { getPopularPosts } from "@utils/getPopularPosts.js";
|
||||||
|
|
||||||
const analytics = await fetchAnalyticsData();
|
const analytics = await fetchAnalyticsData();
|
||||||
const links = await fetchLinks();
|
const links = await fetchLinks();
|
||||||
const posts = await fetchAllPosts();
|
const posts = await fetchAllPosts();
|
||||||
const popularPosts = getPopularPosts(posts, analytics);
|
const popularPosts = getPopularPosts(posts, analytics);
|
||||||
|
|
||||||
import AddonLinks from '@components/blocks/links/AddonLinks.astro';
|
import AddonLinks from "@components/blocks/links/AddonLinks.astro";
|
||||||
import AssociatedMedia from "@components/blocks/AssociatedMedia.astro";
|
import AssociatedMedia from "@components/blocks/AssociatedMedia.astro";
|
||||||
import BlockRenderer from "@components/blocks/BlockRenderer.astro";
|
import BlockRenderer from "@components/blocks/BlockRenderer.astro";
|
||||||
import Coffee from "@components/blocks/banners/Coffee.astro";
|
import Coffee from "@components/blocks/banners/Coffee.astro";
|
||||||
|
|
25
src/utils/data/dynamic/artistByUrl.js
Normal file
25
src/utils/data/dynamic/artistByUrl.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||||
|
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||||
|
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||||
|
|
||||||
|
const artistCache = {};
|
||||||
|
|
||||||
|
export async function fetchArtistByUrl(url) {
|
||||||
|
if (import.meta.env.MODE === "development" && artistCache[url]) return artistCache[url];
|
||||||
|
|
||||||
|
const { data: artist, error } = await supabase
|
||||||
|
.from("optimized_artists")
|
||||||
|
.select("*")
|
||||||
|
.eq("url", url)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error fetching artist:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (import.meta.env.MODE === "development") artistCache[url] = artist[0];
|
||||||
|
|
||||||
|
return artist[0];
|
||||||
|
}
|
|
@ -3,11 +3,10 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||||
|
|
||||||
const bookCache = {};
|
const bookCache = {};
|
||||||
|
|
||||||
export async function fetchBookByUrl(url) {
|
export async function fetchBookByUrl(url) {
|
||||||
if (bookCache[url]) return bookCache[url];
|
if (import.meta.env.MODE === "development" && bookCache[url]) return bookCache[url];
|
||||||
|
|
||||||
const { data: book, error } = await supabase
|
const { data: book, error } = await supabase
|
||||||
.from("optimized_books")
|
.from("optimized_books")
|
||||||
|
@ -15,12 +14,11 @@ export async function fetchBookByUrl(url) {
|
||||||
.eq("url", url)
|
.eq("url", url)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (error || !book) {
|
if (error || !book || book.length === 0) {
|
||||||
console.error(`Error fetching book with URL ${url}:`, error);
|
console.error(`Error fetching book with URL ${url}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (import.meta.env.MODE === "development") bookCache[url] = book[0];
|
||||||
bookCache[url] = book[0];
|
|
||||||
|
|
||||||
return book[0];
|
return book[0];
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue