Format links page

Group links by date and style them like cards.
This commit is contained in:
Hornwitser 2025-02-16 23:54:10 +01:00
parent 8a1313eb6e
commit 9f15da0835
2 changed files with 127 additions and 14 deletions

View file

@ -12,6 +12,7 @@ interface LinkData {
description?: string,
read?: string,
author?: string,
quote?: string,
}
interface Data {
links: LinkData[];
@ -23,13 +24,81 @@ interface Data {
function Link(props: { link: LinkData }) {
const link = props.link;
return <>
<a href={link.url}>{link.title}</a>
{" "}
{link.tags.join(", ")}
<hgroup>
<h4>
<a href={link.url}>{link.title}</a>
{link.author ? <span class="no-break"> {link.author}</span> : null}
</h4>
<p>
{link.tags.join(", ")}
{link.read ? <>
, read: <time>{link.read}</time>
</> : null}
</p>
</hgroup>
{link.quote ? <blockquote>
{link.quote}
</blockquote> : null}
{link.description ? <p>
{link.description}
</p> : null}
{link.altUrls ? <p>
Also available at {link.altUrls.map(url => <a href={url}>{url}</a>)}.
</p> : null}
{link.related ? <p>
Related <a href={link.related}>{link.related}</a>.
</p> : null}
{link.via ? <p>
Via <a href={link.via}>{link.via}</a>.
</p> : null}
</>
}
const data: Data = eval(`(${readFileSync("src/content/links.jsonc", "utf8")})`);
data.links.pop(); // Remove template at the end
function compare(a: string, b: string) {
return Number(a > b) - Number(b > a);
}
function* groupBy<T, K>(items: Iterable<T>, keyFn: (item: T) => K) {
let oldKey: K = Symbol() as any;
let group: T[] | undefined = undefined;
for (const item of items) {
let newKey = keyFn(item);
if (!Object.is(newKey, oldKey)) {
if (group) {
yield [oldKey, group] as [K, T[]];
}
group = [];
oldKey = newKey;
}
group!.push(item);
}
if (group) {
yield [oldKey, group] as [K, T[]];
}
}
const byDate = data.links.filter(link => link.read).sort((a, b) => -compare(a.read!, b.read!));
const byMonth = groupBy(byDate, link => link.read!.slice(0, 7));
const byYearMonth = [...groupBy(byMonth, month => month[0].slice(0, 4))];
const other = data.links.filter(link => !link.read);
const monthNames = [
"", // padding to make mapping start at 1
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const title = "Links!";
export const links: Page = {
title,
@ -37,8 +106,27 @@ export const links: Page = {
content: <BasePage title={title}>
<main>
<h1>{title}</h1>
<ul>
{ data.links.map(link => <li><Link link={link} /></li>)}
<p>
Here be interesting things I've read, watched, listened to or otherwise found useful as resources over the years. These are ordered by when I read them as that seems most practical to me.
</p>
{byYearMonth.map(([year, months]) => <section>
<h2>{year}</h2>
{months.map(([yearMonth, links]) => <section>
<h3>{monthNames[Number.parseInt(yearMonth.slice(5))]}</h3>
<ul role="list">
{links.map(link => <li class="link">
<Link link={link} />
</li>)}
</ul>
</section>)}
</section>)}
<h2>
Resources and other things
</h2>
<ul role="list">
{other.map(link => <li class="link">
<Link link={link} />
</li>)}
</ul>
</main>
</BasePage>

View file

@ -14,6 +14,10 @@ html {
text-size-adjust: none;
}
:is(ul, ol)[role="list"] {
list-style: none;
}
h1, h2, h3, h4, button, input, label {
line-height: 1.1;
}
@ -44,12 +48,12 @@ html {
scrollbar-gutter: stable;
}
hgroup h1 {
margin-bottom: 0.1em;
hgroup :is(h1, h2, h3, h4) {
margin-block-start: 0;
margin-block-end: 0.1em;
}
hgroup p {
font-style: italic;
margin-bottom: 1em;
}
h1, h2, h3, h4 {
@ -57,11 +61,21 @@ h1, h2, h3, h4 {
margin-block-end: 0.5em;
}
:is(p, ol, ul) + :is(p, ol, ul) {
padding-block-start: 1em;
:is(hgroup, p, blockquote, ol, ul, li) + :is(hgroup, blockquote, p, ol, ul, li) {
margin-block-start: var(--block-space, 1em);
}
ol, ul {
blockquote {
padding-inline: 1.25em;
}
blockquote::before {
content: "“";
}
blockquote::after {
content: "”";
}
:is(ol, ul):not([role]) {
padding-inline-start: 1.25em;
}
@ -102,13 +116,24 @@ body {
gap: 1em;
margin-block: 1em;
}
.author h1 {
margin-block-start: 0;
}
.author p {
margin-block-end: 0;
}
/* links */
.no-break {
display: inline-block;
max-width: 100%;
}
.link {
background-color: color-mix(in oklab, Canvas 90%, white);
padding: 0.5em;
border-radius: 0.5em;
}
.link>* {
--block-space: 0.5em;
}
/* sandbox */
.sandbox-inset-3d {
contain: paint;