feat: archive pages when >10 posts in a given section
This commit is contained in:
parent
920bb7bbeb
commit
d1b2db73bc
7 changed files with 218 additions and 19 deletions
28
apps/blog/src/pages/blog/index.astro
Normal file
28
apps/blog/src/pages/blog/index.astro
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import '../../styles/global.css';
|
||||
|
||||
const posts = await getCollection('posts');
|
||||
const sorted = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const y = String(date.getFullYear()).slice(-2);
|
||||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><title>blog - lewis m.w.</title></head>
|
||||
<body>
|
||||
<header>
|
||||
<pre><a href="/">lewis m.w.</a> <a href="mailto:lewis@wynne.rs">mail</a> <a href="https://github.com/llywelwyn">gh</a></pre>
|
||||
</header>
|
||||
|
||||
<details open>
|
||||
<summary>blog</summary>
|
||||
<pre set:html={sorted.map(post => `<span class="muted">${formatDate(post.data.date)}</span> <a href="/posts/${post.id}">${post.data.title}</a>`).join('\n')} />
|
||||
</details>
|
||||
</body>
|
||||
</html>
|
||||
49
apps/blog/src/pages/bookmarks/index.astro
Normal file
49
apps/blog/src/pages/bookmarks/index.astro
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import yaml from 'js-yaml';
|
||||
import bookmarksRaw from '../../data/bookmarks.yaml?raw';
|
||||
|
||||
interface Bookmark {
|
||||
date: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const bookmarks = (yaml.load(bookmarksRaw) as Bookmark[])
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const y = String(date.getFullYear()).slice(-2);
|
||||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
|
||||
function formatBookmarkDate(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
return formatDate(date);
|
||||
}
|
||||
|
||||
function extractDomain(url: string): string {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname.replace(/^www\./, '');
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><title>bookmarks - lewis m.w.</title></head>
|
||||
<body>
|
||||
<header>
|
||||
<pre><a href="/">lewis m.w.</a> <a href="mailto:lewis@wynne.rs">mail</a> <a href="https://github.com/llywelwyn">gh</a></pre>
|
||||
</header>
|
||||
|
||||
<details open>
|
||||
<summary>bookmarks</summary>
|
||||
<pre set:html={bookmarks.map(b => `<span class="muted">${formatBookmarkDate(b.date)}</span> <a href="${b.url}">${b.title}</a> <span class="muted">(${extractDomain(b.url)})</span>`).join('\n')} />
|
||||
</details>
|
||||
</body>
|
||||
</html>
|
||||
74
apps/blog/src/pages/guestbook/index.astro
Normal file
74
apps/blog/src/pages/guestbook/index.astro
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import { getApprovedEntries, type GuestbookEntry } from '../../lib/db';
|
||||
|
||||
let guestbookEntries: GuestbookEntry[] = [];
|
||||
try {
|
||||
guestbookEntries = await getApprovedEntries();
|
||||
} catch {
|
||||
// DB not available during dev without env vars
|
||||
}
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const y = String(date.getFullYear()).slice(-2);
|
||||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><title>guestbook - lewis m.w.</title></head>
|
||||
<body>
|
||||
<header>
|
||||
<pre><a href="/">lewis m.w.</a> <a href="mailto:lewis@wynne.rs">mail</a> <a href="https://github.com/llywelwyn">gh</a></pre>
|
||||
</header>
|
||||
|
||||
<details open>
|
||||
<summary>guestbook</summary>
|
||||
<div class="guestbook-entries">
|
||||
{guestbookEntries.map(e => (
|
||||
<div class="guestbook-entry">
|
||||
<span class="muted">{formatDate(e.createdAt)}</span>
|
||||
<span><b set:html={e.url ? `<a href="${e.url}">${e.name}</a>` : e.name} /> {e.message}</span>
|
||||
</div>
|
||||
))}
|
||||
<div class="guestbook-entry">
|
||||
<span></span>
|
||||
<span><a href="#" id="sign-guestbook">sign</a><span id="guestbook-status"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<script>
|
||||
document.getElementById('sign-guestbook')?.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const status = document.getElementById('guestbook-status')!;
|
||||
|
||||
const name = prompt('name:');
|
||||
if (!name) return;
|
||||
|
||||
const message = prompt('message:');
|
||||
if (!message) return;
|
||||
|
||||
const url = prompt('url (optional):');
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/guestbook', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, message, url: url || null }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
status.textContent = ' thanks! pending approval.';
|
||||
} else {
|
||||
status.textContent = ' error';
|
||||
}
|
||||
} catch {
|
||||
status.textContent = ' failed';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -74,30 +74,42 @@ function extractDomain(url: string): string {
|
|||
|
||||
<details open>
|
||||
<summary>blog</summary>
|
||||
<pre set:html={sorted.map(post => `<span class="muted">${formatDate(post.data.date)}</span> <a href="/posts/${post.id}">${post.data.title}</a>`).join('\n')} />
|
||||
<pre set:html={[
|
||||
...sorted.slice(0, 10).map(post => `<span class="muted">${formatDate(post.data.date)}</span> <a href="/posts/${post.id}">${post.data.title}</a>`),
|
||||
...(sorted.length > 10 ? [`<a href="/blog/">+${sorted.length - 10} more</a>`] : [])
|
||||
].join('\n')} />
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>txt</summary>
|
||||
<pre set:html={txtFiles.map(f => `<span class="muted">${formatDate(f.mtime)}</span> <a href="/txt/${f.name}">${f.name}</a>`).join('\n')} />
|
||||
<pre set:html={[
|
||||
...txtFiles.slice(0, 10).map(f => `<span class="muted">${formatDate(f.mtime)}</span> <a href="/txt/${f.name}">${f.name}</a>`),
|
||||
...(txtFiles.length > 10 ? [`<a href="/txt/">+${txtFiles.length - 10} more</a>`] : [])
|
||||
].join('\n')} />
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>bookmarks</summary>
|
||||
<pre set:html={bookmarks.map(b => `<span class="muted">${formatBookmarkDate(b.date)}</span> <a href="${b.url}">${b.title}</a> <span class="muted">(${extractDomain(b.url)})</span>`).join('\n')} />
|
||||
<pre set:html={[
|
||||
...bookmarks.slice(0, 10).map(b => `<span class="muted">${formatBookmarkDate(b.date)}</span> <a href="${b.url}">${b.title}</a> <span class="muted">(${extractDomain(b.url)})</span>`),
|
||||
...(bookmarks.length > 10 ? [`<a href="/bookmarks/">+${bookmarks.length - 10} more</a>`] : [])
|
||||
].join('\n')} />
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>guestbook</summary>
|
||||
<div class="guestbook-entries">
|
||||
{guestbookEntries.map(e => (
|
||||
{guestbookEntries.slice(0, 10).map(e => (
|
||||
<div class="guestbook-entry">
|
||||
<span class="date">{formatDate(e.createdAt)}</span>
|
||||
<span set:html={`${e.url ? `<a href="${e.url}">${e.name}</a>` : e.name}: ${e.message}`} />
|
||||
<span class="muted">{formatDate(e.createdAt)}</span>
|
||||
<span><b set:html={e.url ? `<a href="${e.url}">${e.name}</a>` : e.name} /> {e.message}</span>
|
||||
</div>
|
||||
))}
|
||||
<div class="guestbook-entry">
|
||||
<span>{guestbookEntries.length > 10 && <a href="/guestbook/">+{guestbookEntries.length - 10} more</a>}</span>
|
||||
<span><a href="#" id="sign-guestbook">sign</a><span id="guestbook-status"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="guestbook-sign"><a href="#" id="sign-guestbook">sign</a><span id="guestbook-status"></span></pre>
|
||||
</details>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,12 @@ const { Content } = await render(post);
|
|||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><title>{post.data.title}</title></head>
|
||||
<head><meta charset="UTF-8"><title>{post.data.title} - lewis m.w.</title></head>
|
||||
<body>
|
||||
<header>
|
||||
<pre><a href="/">lewis m.w.</a> <a href="mailto:lewis@wynne.rs">mail</a> <a href="https://github.com/llywelwyn">gh</a></pre>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<h1>{post.data.title}</h1>
|
||||
<Content />
|
||||
|
|
|
|||
42
apps/blog/src/pages/txt/index.astro
Normal file
42
apps/blog/src/pages/txt/index.astro
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
interface TxtFile {
|
||||
name: string;
|
||||
mtime: Date;
|
||||
}
|
||||
|
||||
const txtDir = path.join(process.cwd(), 'public/txt');
|
||||
const txtFiles: TxtFile[] = fs.existsSync(txtDir)
|
||||
? fs.readdirSync(txtDir)
|
||||
.filter(file => file.endsWith('.txt'))
|
||||
.map(name => {
|
||||
const stat = fs.statSync(path.join(txtDir, name));
|
||||
return { name, mtime: stat.mtime };
|
||||
})
|
||||
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
||||
: [];
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const y = String(date.getFullYear()).slice(-2);
|
||||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><title>txt - lewis m.w.</title></head>
|
||||
<body>
|
||||
<header>
|
||||
<pre><a href="/">lewis m.w.</a> <a href="mailto:lewis@wynne.rs">mail</a> <a href="https://github.com/llywelwyn">gh</a></pre>
|
||||
</header>
|
||||
|
||||
<details open>
|
||||
<summary>txt</summary>
|
||||
<pre set:html={txtFiles.map(f => `<span class="muted">${formatDate(f.mtime)}</span> <a href="/txt/${f.name}">${f.name}</a>`).join('\n')} />
|
||||
</details>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -49,20 +49,10 @@ details pre {
|
|||
|
||||
.guestbook-entries {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.guestbook-entry {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: 8ch 1fr;
|
||||
gap: 0 4ch;
|
||||
}
|
||||
|
||||
.guestbook-entry .date {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.guestbook-sign {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue