Format links page
Group links by date and style them like cards.
This commit is contained in:
parent
8a1313eb6e
commit
9f15da0835
2 changed files with 127 additions and 14 deletions
|
@ -12,6 +12,7 @@ interface LinkData {
|
||||||
description?: string,
|
description?: string,
|
||||||
read?: string,
|
read?: string,
|
||||||
author?: string,
|
author?: string,
|
||||||
|
quote?: string,
|
||||||
}
|
}
|
||||||
interface Data {
|
interface Data {
|
||||||
links: LinkData[];
|
links: LinkData[];
|
||||||
|
@ -23,13 +24,81 @@ interface Data {
|
||||||
function Link(props: { link: LinkData }) {
|
function Link(props: { link: LinkData }) {
|
||||||
const link = props.link;
|
const link = props.link;
|
||||||
return <>
|
return <>
|
||||||
<a href={link.url}>{link.title}</a>
|
<hgroup>
|
||||||
{" "}
|
<h4>
|
||||||
{link.tags.join(", ")}
|
<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")})`);
|
const data: Data = eval(`(${readFileSync("src/content/links.jsonc", "utf8")})`);
|
||||||
data.links.pop(); // Remove template at the end
|
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!";
|
const title = "Links!";
|
||||||
export const links: Page = {
|
export const links: Page = {
|
||||||
title,
|
title,
|
||||||
|
@ -37,8 +106,27 @@ export const links: Page = {
|
||||||
content: <BasePage title={title}>
|
content: <BasePage title={title}>
|
||||||
<main>
|
<main>
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
<ul>
|
<p>
|
||||||
{ data.links.map(link => <li><Link link={link} /></li>)}
|
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>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
|
@ -14,6 +14,10 @@ html {
|
||||||
text-size-adjust: none;
|
text-size-adjust: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:is(ul, ol)[role="list"] {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, button, input, label {
|
h1, h2, h3, h4, button, input, label {
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
@ -44,12 +48,12 @@ html {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
hgroup h1 {
|
hgroup :is(h1, h2, h3, h4) {
|
||||||
margin-bottom: 0.1em;
|
margin-block-start: 0;
|
||||||
|
margin-block-end: 0.1em;
|
||||||
}
|
}
|
||||||
hgroup p {
|
hgroup p {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
h1, h2, h3, h4 {
|
||||||
|
@ -57,11 +61,21 @@ h1, h2, h3, h4 {
|
||||||
margin-block-end: 0.5em;
|
margin-block-end: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(p, ol, ul) + :is(p, ol, ul) {
|
:is(hgroup, p, blockquote, ol, ul, li) + :is(hgroup, blockquote, p, ol, ul, li) {
|
||||||
padding-block-start: 1em;
|
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;
|
padding-inline-start: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,13 +116,24 @@ body {
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
margin-block: 1em;
|
margin-block: 1em;
|
||||||
}
|
}
|
||||||
.author h1 {
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
.author p {
|
.author p {
|
||||||
margin-block-end: 0;
|
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 */
|
||||||
.sandbox-inset-3d {
|
.sandbox-inset-3d {
|
||||||
contain: paint;
|
contain: paint;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue