feat: removed pins, and added right-aligned suffixes
This commit is contained in:
parent
c647fd62c3
commit
20811f107b
8 changed files with 51 additions and 25 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
title: hello
|
title: hello
|
||||||
date: 2023-02-26
|
date: 2023-02-26
|
||||||
pinned: true
|
|
||||||
---
|
---
|
||||||
|
|
||||||
i've always had some sort of homepage. it was originally on bebo, then that died and i didn't ever get into other social media, so i made a website
|
i've always had some sort of homepage. it was originally on bebo, then that died and i didn't ever get into other social media, so i made a website
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
pinned: []
|
|
||||||
descriptions:
|
descriptions:
|
||||||
cv.txt: curriculum vitae
|
cv.txt: curriculum vitae
|
||||||
now.txt: what i'm doing now
|
now.txt: what i'm doing now
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import { glob, file } from 'astro/loaders';
|
||||||
import { z } from 'astro/zod';
|
import { z } from 'astro/zod';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
const md = defineCollection({
|
const posts = defineCollection({
|
||||||
loader: glob({ pattern: '**/*.md', base: './content' }),
|
loader: glob({ pattern: '**/*.md', base: './content' }),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
date: z.coerce.date(),
|
date: z.coerce.date(),
|
||||||
updated: z.coerce.date().optional(),
|
updated: z.coerce.date().optional(),
|
||||||
pinned: z.boolean().optional(),
|
|
||||||
category: z.string().optional(),
|
category: z.string().optional(),
|
||||||
related: z.array(z.string()).optional(),
|
related: z.array(z.string()).optional(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -26,28 +26,45 @@ export function formatDate(date: Date): string {
|
||||||
return `${d}/${m}/${y}`;
|
return `${d}/${m}/${y}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function wordCount(markdown: string | undefined): string {
|
||||||
|
if (!markdown) return '';
|
||||||
|
const words = markdown
|
||||||
|
.replace(/^---[\s\S]*?---/m, '')
|
||||||
|
.replace(/^#+\s+.*$/gm, '')
|
||||||
|
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
||||||
|
.replace(/[*_~`]/g, '')
|
||||||
|
.replace(/:[a-z]+\[([^\]]*)\]/g, '$1')
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean).length;
|
||||||
|
if (words < 100) return `${words} words`;
|
||||||
|
const mins = Math.ceil(words / 200);
|
||||||
|
return `${mins} min`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractDomain(url: string): string {
|
||||||
|
try {
|
||||||
|
return new URL(url).hostname.replace(/^www\./, '');
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function formatListItem(
|
export function formatListItem(
|
||||||
date: Date,
|
date: Date,
|
||||||
url: string,
|
url: string,
|
||||||
title: string,
|
title: string,
|
||||||
options?: { pinned?: boolean; suffix?: string }
|
options?: { suffix?: string }
|
||||||
): string {
|
): string {
|
||||||
const pinnedBadge = options?.pinned ? ' [pinned]' : '';
|
const suffixHtml = options?.suffix ? `<span class="entry-suffix muted">${options.suffix}</span>` : '';
|
||||||
const suffix = options?.suffix ? ` ${options.suffix}` : '';
|
return `<span class="list-meta"><span class="muted">${formatDate(date)}</span></span><span class="entry-content"><a href="${url}" title="${title}">${title}</a>${suffixHtml}</span>`;
|
||||||
return `<span class="list-meta"><span class="muted">${formatDate(date)}</span></span><span class="entry-content"><a href="${url}" title="${title}">${title}</a>${pinnedBadge}${suffix}</span>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Sortable {
|
interface Sortable {
|
||||||
date: Date;
|
date: Date;
|
||||||
pinned?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortEntries<T>(items: T[], key?: (item: T) => Sortable): T[] {
|
export function sortEntries<T>(items: T[], key?: (item: T) => Sortable): T[] {
|
||||||
const get = key ?? (item => item as unknown as Sortable);
|
const get = key ?? (item => item as unknown as Sortable);
|
||||||
return items.slice().sort((a, b) => {
|
return items.slice().sort((a, b) => get(b).date.getTime() - get(a).date.getTime());
|
||||||
const ak = get(a), bk = get(b);
|
|
||||||
if (ak.pinned && !bk.pinned) return -1;
|
|
||||||
if (!ak.pinned && bk.pinned) return 1;
|
|
||||||
return bk.date.getTime() - ak.date.getTime();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import { sortEntries } from './format';
|
||||||
export interface TxtFile {
|
export interface TxtFile {
|
||||||
name: string;
|
name: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
pinned: boolean;
|
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TxtConfig {
|
export interface TxtConfig {
|
||||||
pinned?: string[];
|
|
||||||
descriptions?: Record<string, string>;
|
descriptions?: Record<string, string>;
|
||||||
dates?: Record<string, string>;
|
dates?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +30,6 @@ export function getTxtFiles(): TxtFile[] {
|
||||||
if (!fs.existsSync(txtDir)) return [];
|
if (!fs.existsSync(txtDir)) return [];
|
||||||
|
|
||||||
const config = loadTxtConfig();
|
const config = loadTxtConfig();
|
||||||
const pinnedSet = new Set(config.pinned || []);
|
|
||||||
const descriptions = config.descriptions || {};
|
const descriptions = config.descriptions || {};
|
||||||
const dates = config.dates || {};
|
const dates = config.dates || {};
|
||||||
|
|
||||||
|
|
@ -41,7 +38,6 @@ export function getTxtFiles(): TxtFile[] {
|
||||||
.map(name => ({
|
.map(name => ({
|
||||||
name,
|
name,
|
||||||
date: dates[name] ? new Date(dates[name]) : new Date(0),
|
date: dates[name] ? new Date(dates[name]) : new Date(0),
|
||||||
pinned: pinnedSet.has(name),
|
|
||||||
description: descriptions[name],
|
description: descriptions[name],
|
||||||
}));
|
}));
|
||||||
return sortEntries(files);
|
return sortEntries(files);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { getCollection, render } from 'astro:content';
|
import { getCollection, render } from 'astro:content';
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { formatDate, formatListItem, excerpt } from '../lib/format';
|
import { formatDate, formatListItem, excerpt, wordCount } from '../lib/format';
|
||||||
import { getSlug, resolveRelatedPosts, type Post } from '../lib/md';
|
import { getSlug, resolveRelatedPosts, type Post } from '../lib/md';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
|
|
@ -21,7 +21,7 @@ const description = excerpt((post as Post).body) || undefined;
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<p class="muted" style="margin-top: 0;">{formatDate(post.data.date)}{post.data.updated && ` (updated ${formatDate(post.data.updated)})`}</p>
|
<p class="muted" style="margin-top: 0;">{formatDate(post.data.date)}{post.data.updated && ` (updated ${formatDate(post.data.updated)})`} · {wordCount((post as Post).body)}{post.data.category && ` · ${post.data.category}`}</p>
|
||||||
<Content />
|
<Content />
|
||||||
</article>
|
</article>
|
||||||
{related.length > 0 && (
|
{related.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
|
import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
|
||||||
import { formatDate, formatListItem, escapeHtml } from '../lib/format';
|
import { formatDate, formatListItem, extractDomain, wordCount, escapeHtml } from '../lib/format';
|
||||||
import { organizePostsByCategory, getSlug } from '../lib/md';
|
import { organizePostsByCategory, getSlug } from '../lib/md';
|
||||||
import { getTxtFiles } from '../lib/txt';
|
import { getTxtFiles } from '../lib/txt';
|
||||||
import { DEFAULT_CATEGORY, SECTIONS, SUBDOMAINS } from '../lib/consts';
|
import { DEFAULT_CATEGORY, SECTIONS, SUBDOMAINS } from '../lib/consts';
|
||||||
|
|
@ -37,7 +37,7 @@ const urls = [
|
||||||
<section data-section={category}>
|
<section data-section={category}>
|
||||||
{!isDefault && <a class="section-label" href={`?just=${category}`}>{category}</a>}
|
{!isDefault && <a class="section-label" href={`?just=${category}`}>{category}</a>}
|
||||||
<div class="entry-list" set:html={categoryPosts.map(post =>
|
<div class="entry-list" set:html={categoryPosts.map(post =>
|
||||||
`<span class="entry">${formatListItem(post.data.date, `/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })}</span>`
|
`<span class="entry">${formatListItem(post.data.date, `/${getSlug(post.id)}`, post.data.title, { suffix: wordCount(post.body) })}</span>`
|
||||||
).join('')} />
|
).join('')} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
@ -47,14 +47,14 @@ const urls = [
|
||||||
<a class="section-label" href={`?just=${SECTIONS.plaintext}`}>{SECTIONS.plaintext}</a>
|
<a class="section-label" href={`?just=${SECTIONS.plaintext}`}>{SECTIONS.plaintext}</a>
|
||||||
<div class="entry-list" set:html={txtFiles.map(f => {
|
<div class="entry-list" set:html={txtFiles.map(f => {
|
||||||
const name = f.name.replace(/\.txt$/, '');
|
const name = f.name.replace(/\.txt$/, '');
|
||||||
return `<span class="entry">${formatListItem(f.date, `/${f.name}`, name, { pinned: f.pinned })}</span>`;
|
return `<span class="entry">${formatListItem(f.date, `/${f.name}`, name, { suffix: f.description })}</span>`;
|
||||||
}).join('')} />
|
}).join('')} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-section={SECTIONS.bookmarks}>
|
<section data-section={SECTIONS.bookmarks}>
|
||||||
<a class="section-label" href={`?just=${SECTIONS.bookmarks}`}>{SECTIONS.bookmarks}</a>
|
<a class="section-label" href={`?just=${SECTIONS.bookmarks}`}>{SECTIONS.bookmarks}</a>
|
||||||
<div class="entry-list" set:html={bookmarks.map(b =>
|
<div class="entry-list" set:html={bookmarks.map(b =>
|
||||||
`<span class="entry">${formatListItem(b.data.date, b.data.url, b.data.title)}</span>`
|
`<span class="entry">${formatListItem(b.data.date, b.data.url, b.data.title, { suffix: extractDomain(b.data.url) })}</span>`
|
||||||
).join('')} />
|
).join('')} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,11 +116,27 @@ section pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-content {
|
.entry-content {
|
||||||
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content > a {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entry-suffix {
|
||||||
|
flex: 1 10000 0%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: right;
|
||||||
|
padding-left: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
.guestbook-entries {
|
.guestbook-entries {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue