(items: T[]): T[] {
+ return items.slice().sort((a, b) => {
+ if (a.pinned && !b.pinned) return -1;
+ if (!a.pinned && b.pinned) return 1;
+ return b.date.getTime() - a.date.getTime();
+ });
+}
diff --git a/www/src/lib/posts.ts b/www/src/lib/md.ts
similarity index 95%
rename from www/src/lib/posts.ts
rename to www/src/lib/md.ts
index d3fc859..7026087 100644
--- a/www/src/lib/posts.ts
+++ b/www/src/lib/md.ts
@@ -7,7 +7,7 @@ export function getSlug(postId: string): string {
return parts[parts.length - 1];
}
-export function sortPosts(posts: Post[]): Post[] {
+function sortPosts(posts: Post[]): Post[] {
return posts.slice().sort((a, b) => {
if (a.data.pinned && !b.data.pinned) return -1;
if (!a.data.pinned && b.data.pinned) return 1;
diff --git a/www/src/lib/txt.ts b/www/src/lib/txt.ts
index 233d24a..802ba33 100644
--- a/www/src/lib/txt.ts
+++ b/www/src/lib/txt.ts
@@ -2,6 +2,7 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import yaml from 'js-yaml';
+import { sortByPinnedThenDate } from './format';
function getGitDate(filePath: string): Date {
try {
@@ -40,18 +41,14 @@ export function getTxtFiles(): TxtFile[] {
const config = loadTxtConfig();
const pinnedSet = new Set(config.pinned || []);
- return fs.readdirSync(txtDir)
+ const files = fs.readdirSync(txtDir)
.filter(file => file.endsWith('.txt'))
.map(name => ({
name,
date: getGitDate(path.join(txtDir, name)),
pinned: pinnedSet.has(name),
- }))
- .sort((a, b) => {
- if (a.pinned && !b.pinned) return -1;
- if (!a.pinned && b.pinned) return 1;
- return b.date.getTime() - a.date.getTime();
- });
+ }));
+ return sortByPinnedThenDate(files);
}
export function getTxtFileNames(): string[] {
diff --git a/www/src/pages/admin.astro b/www/src/pages/admin.astro
index d634e79..f540c6c 100644
--- a/www/src/pages/admin.astro
+++ b/www/src/pages/admin.astro
@@ -1,26 +1,14 @@
---
export const prerender = false;
-import { getSession } from 'auth-astro/server';
import { getPendingEntries, type GuestbookEntry } from '../lib/db';
-import { isAdmin } from '../lib/auth';
+import { requireAdminSession } from '../lib/auth';
import Layout from '../layouts/Layout.astro';
import { formatDate } from '../lib/format';
-let session;
-try {
- session = await getSession(Astro.request);
-} catch {
- return new Response('Auth not configured', { status: 500 });
-}
-
-if (!session) {
- return Astro.redirect('/api/auth/signin');
-}
-
-if (!isAdmin(session.user?.id)) {
- return new Response('Forbidden', { status: 403 });
-}
+const { session, error } = await requireAdminSession(Astro.request);
+if (error) return error;
+if (!session) return Astro.redirect('/api/auth/signin');
let entries: GuestbookEntry[] = [];
try {
diff --git a/www/src/pages/bookmarks/index.astro b/www/src/pages/bookmarks/index.astro
index 4c37d9f..73f8438 100644
--- a/www/src/pages/bookmarks/index.astro
+++ b/www/src/pages/bookmarks/index.astro
@@ -1,7 +1,7 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
-import { formatDate, extractDomain } from '../../lib/format';
+import { formatListItem, extractDomain } from '../../lib/format';
const bookmarksCollection = await getCollection('bookmarks');
const bookmarks = bookmarksCollection
@@ -11,6 +11,6 @@ const bookmarks = bookmarksCollection
bookmarks
- `${formatDate(b.data.date)} ${b.data.title} (${extractDomain(b.data.url)})`).join('\n')} />
+ formatListItem(b.data.date, b.data.url, b.data.title, { suffix: `(${extractDomain(b.data.url)})` })).join('\n')} />
diff --git a/www/src/pages/draft/[slug].astro b/www/src/pages/draft/[slug].astro
index 78c919f..62302af 100644
--- a/www/src/pages/draft/[slug].astro
+++ b/www/src/pages/draft/[slug].astro
@@ -1,27 +1,15 @@
---
export const prerender = false;
-import { getSession } from 'auth-astro/server';
import { getCollection, render } from 'astro:content';
-import { isAdmin } from '../../lib/auth';
+import { requireAdminSession } from '../../lib/auth';
import Layout from '../../layouts/Layout.astro';
import { formatDate } from '../../lib/format';
-import { getSlug } from '../../lib/posts';
+import { getSlug } from '../../lib/md';
-let session;
-try {
- session = await getSession(Astro.request);
-} catch {
- return new Response('Auth not configured', { status: 500 });
-}
-
-if (!session) {
- return Astro.redirect('/api/auth/signin');
-}
-
-if (!isAdmin(session.user?.id)) {
- return new Response('Forbidden', { status: 403 });
-}
+const { session, error } = await requireAdminSession(Astro.request);
+if (error) return error;
+if (!session) return Astro.redirect('/api/auth/signin');
const slug = Astro.params.slug;
const posts = await getCollection('md', ({ data }) => data.draft === true);
diff --git a/www/src/pages/draft/index.astro b/www/src/pages/draft/index.astro
index 4fb6cf6..eb8ca14 100644
--- a/www/src/pages/draft/index.astro
+++ b/www/src/pages/draft/index.astro
@@ -1,27 +1,15 @@
---
export const prerender = false;
-import { getSession } from 'auth-astro/server';
import { getCollection } from 'astro:content';
-import { isAdmin } from '../../lib/auth';
+import { requireAdminSession } from '../../lib/auth';
import Layout from '../../layouts/Layout.astro';
-import { formatDate } from '../../lib/format';
-import { organizePostsByCategory, getSlug } from '../../lib/posts';
+import { formatListItem } from '../../lib/format';
+import { organizePostsByCategory, getSlug } from '../../lib/md';
-let session;
-try {
- session = await getSession(Astro.request);
-} catch {
- return new Response('Auth not configured', { status: 500 });
-}
-
-if (!session) {
- return Astro.redirect('/api/auth/signin');
-}
-
-if (!isAdmin(session.user?.id)) {
- return new Response('Forbidden', { status: 403 });
-}
+const { session, error } = await requireAdminSession(Astro.request);
+if (error) return error;
+if (!session) return Astro.redirect('/api/auth/signin');
const posts = await getCollection('md', ({ data }) => data.draft === true);
const { grouped, categories: sortedCategories } = organizePostsByCategory(posts);
@@ -36,7 +24,7 @@ const { grouped, categories: sortedCategories } = organizePostsByCategory(posts)
sortedCategories.map(category => (
{category}
- `${formatDate(post.data.date)} ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`).join('\n')} />
+ formatListItem(post.data.date, `/draft/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
))
)}
diff --git a/www/src/pages/feed.xml.ts b/www/src/pages/feed.xml.ts
index 606e013..aa58a25 100644
--- a/www/src/pages/feed.xml.ts
+++ b/www/src/pages/feed.xml.ts
@@ -1,7 +1,7 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
-import { getSlug } from '../lib/posts';
+import { getSlug } from '../lib/md';
import { getTxtFiles } from '../lib/txt';
export async function GET(context: APIContext) {
diff --git a/www/src/pages/guestbook/index.astro b/www/src/pages/guestbook/index.astro
index 5e33851..92ecdbf 100644
--- a/www/src/pages/guestbook/index.astro
+++ b/www/src/pages/guestbook/index.astro
@@ -29,33 +29,7 @@ try {
diff --git a/www/src/pages/index.astro b/www/src/pages/index.astro
index 03fc3de..2ae6848 100644
--- a/www/src/pages/index.astro
+++ b/www/src/pages/index.astro
@@ -2,8 +2,8 @@
import { getCollection } from 'astro:content';
import Layout from '../layouts/Layout.astro';
import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
-import { formatDate, extractDomain } from '../lib/format';
-import { organizePostsByCategory, getSlug } from '../lib/posts';
+import { formatDate, extractDomain, formatListItem } from '../lib/format';
+import { organizePostsByCategory, getSlug } from '../lib/md';
import { getTxtFiles } from '../lib/txt';
const posts = await getCollection('md', ({ data }) => data.draft !== true);
@@ -30,7 +30,7 @@ try {
{category}
`${formatDate(post.data.date)} ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`),
+ ...categoryPosts.slice(0, 10).map(post => formatListItem(post.data.date, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })),
...(categoryPosts.length > 10 ? [`+${categoryPosts.length - 10} more`] : [])
].join('\n')} />
@@ -40,7 +40,7 @@ try {
txt
`${formatDate(f.date)} ${f.name}${f.pinned ? ' [pinned]' : ''}`),
+ ...txtFiles.slice(0, 10).map(f => formatListItem(f.date, `/txt/${f.name}`, f.name, { pinned: f.pinned })),
...(txtFiles.length > 10 ? [`+${txtFiles.length - 10} more`] : [])
].join('\n')} />
@@ -48,7 +48,7 @@ try {
bookmarks
`${formatDate(b.data.date)} ${b.data.title} (${extractDomain(b.data.url)})`),
+ ...bookmarks.slice(0, 10).map(b => formatListItem(b.data.date, b.data.url, b.data.title, { suffix: `(${extractDomain(b.data.url)})` })),
...(bookmarks.length > 10 ? [`+${bookmarks.length - 10} more`] : [])
].join('\n')} />
@@ -70,33 +70,7 @@ try {
diff --git a/www/src/pages/md/[slug].astro b/www/src/pages/md/[slug].astro
index 615a11d..b91ceb4 100644
--- a/www/src/pages/md/[slug].astro
+++ b/www/src/pages/md/[slug].astro
@@ -2,7 +2,7 @@
import { getCollection, render } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import { formatDate } from '../../lib/format';
-import { getSlug } from '../../lib/posts';
+import { getSlug } from '../../lib/md';
export async function getStaticPaths() {
const posts = await getCollection('md', ({ data }) => data.draft !== true);
diff --git a/www/src/pages/md/index.astro b/www/src/pages/md/index.astro
index 9382df8..7e58533 100644
--- a/www/src/pages/md/index.astro
+++ b/www/src/pages/md/index.astro
@@ -1,8 +1,8 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
-import { formatDate } from '../../lib/format';
-import { organizePostsByCategory, getSlug } from '../../lib/posts';
+import { formatListItem } from '../../lib/format';
+import { organizePostsByCategory, getSlug } from '../../lib/md';
const posts = await getCollection('md', ({ data }) => data.draft !== true);
const { grouped, categories: sortedCategories } = organizePostsByCategory(posts);
@@ -12,7 +12,7 @@ const { grouped, categories: sortedCategories } = organizePostsByCategory(posts)
{sortedCategories.map(category => (
{category}
- `${formatDate(post.data.date)} ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`).join('\n')} />
+ formatListItem(post.data.date, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
))}
diff --git a/www/src/pages/random.ts b/www/src/pages/random.ts
index 24b036b..5effc90 100644
--- a/www/src/pages/random.ts
+++ b/www/src/pages/random.ts
@@ -1,6 +1,6 @@
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
-import { getSlug } from '../lib/posts';
+import { getSlug } from '../lib/md';
import { getTxtFileNames } from '../lib/txt';
export const prerender = false;
diff --git a/www/src/pages/sitemap.txt.ts b/www/src/pages/sitemap.txt.ts
index c0857ba..ac8cfa8 100644
--- a/www/src/pages/sitemap.txt.ts
+++ b/www/src/pages/sitemap.txt.ts
@@ -1,6 +1,6 @@
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
-import { getSlug } from '../lib/posts';
+import { getSlug } from '../lib/md';
import { getTxtFileNames } from '../lib/txt';
const SUBDOMAINS = [
diff --git a/www/src/pages/txt/index.astro b/www/src/pages/txt/index.astro
index 42d655c..fd98e29 100644
--- a/www/src/pages/txt/index.astro
+++ b/www/src/pages/txt/index.astro
@@ -1,6 +1,6 @@
---
import Layout from '../../layouts/Layout.astro';
-import { formatDate } from '../../lib/format';
+import { formatListItem } from '../../lib/format';
import { getTxtFiles } from '../../lib/txt';
const txtFiles = getTxtFiles();
@@ -9,6 +9,6 @@ const txtFiles = getTxtFiles();
txt
- `${formatDate(f.date)} ${f.name}${f.pinned ? ' [pinned]' : ''}`).join('\n')} />
+ formatListItem(f.date, `/txt/${f.name}`, f.name, { pinned: f.pinned })).join('\n')} />
diff --git a/www/src/scripts/guestbook-sign.ts b/www/src/scripts/guestbook-sign.ts
new file mode 100644
index 0000000..b9cdf47
--- /dev/null
+++ b/www/src/scripts/guestbook-sign.ts
@@ -0,0 +1,30 @@
+export function initGuestbookSign() {
+ 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';
+ }
+ });
+}