feat: manpage, ?compact, and some changes to formatting
This commit is contained in:
parent
51e33844b1
commit
306e26da46
8 changed files with 120 additions and 21 deletions
|
|
@ -4,18 +4,50 @@ export default async function seed() {
|
|||
await db.insert(Guestbook).values([
|
||||
{
|
||||
id: 1,
|
||||
name: 'alice',
|
||||
message: 'love the site!',
|
||||
url: 'https://example.com',
|
||||
createdAt: new Date('2026-01-20'),
|
||||
name: 'lisek',
|
||||
message: ':)',
|
||||
url: null,
|
||||
createdAt: new Date('2026-03-23'),
|
||||
approved: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'bob',
|
||||
message: 'great blog posts',
|
||||
name: 'stripes',
|
||||
message: 'yay signing',
|
||||
url: null,
|
||||
createdAt: new Date('2026-01-18'),
|
||||
createdAt: new Date('2026-03-21'),
|
||||
approved: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Evan',
|
||||
message: 'Queue has four silent letters o_O',
|
||||
url: null,
|
||||
createdAt: new Date('2026-01-23'),
|
||||
approved: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'your good pal chev',
|
||||
message: 'howdy howdy',
|
||||
url: 'https://youtu.be/dQw4w9WgXcQ?si=lmJDP_U9yTySGD-_',
|
||||
createdAt: new Date('2025-11-19'),
|
||||
approved: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Farofa',
|
||||
message: 'Thinking on what to write holdon',
|
||||
url: null,
|
||||
createdAt: new Date('2025-11-03'),
|
||||
approved: true,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'luna',
|
||||
message: 'we love lewis from primal gaming',
|
||||
url: null,
|
||||
createdAt: new Date('2025-08-23'),
|
||||
approved: true,
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,8 @@
|
|||
pinned:
|
||||
- none
|
||||
descriptions:
|
||||
cv.txt: curriculum vitae
|
||||
now.txt: what i'm doing now
|
||||
man.txt: manual page
|
||||
changelog.txt: site changelog
|
||||
stats.txt: site statistics
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
location.replace('/admin');
|
||||
}
|
||||
|
||||
if (p.has('compact')) document.documentElement.dataset.compact = '';
|
||||
|
||||
var has = p.get('has');
|
||||
if (has) {
|
||||
document.documentElement.dataset.has = has;
|
||||
|
|
|
|||
46
www/public/man.txt
Normal file
46
www/public/man.txt
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
WYNNE.RS(7) Personal Sites Manual WYNNE.RS(7)
|
||||
|
||||
NAME
|
||||
wynne.rs — personal website of lewis m.w.
|
||||
|
||||
SYNOPSIS
|
||||
wynne.rs [?just=SECTION] [?has=TERM] [?do=ACTION] [?compact]
|
||||
|
||||
DESCRIPTION
|
||||
A personal website limited to some made-up, arbitrary design
|
||||
principles to keep things minimal. An experiment in doing as
|
||||
much as possible with as few distinct routes as I can. As it
|
||||
stands there is an index page with a list of all the content
|
||||
on the site, and a [slug]. Anything interesting is done with
|
||||
search parameters. Rather than a search page or an index for
|
||||
content categories, the queries below can be used. They tend
|
||||
to all modify data classes to show/hide content on the index.
|
||||
|
||||
OPTIONS
|
||||
?just=SECTION
|
||||
Display only the named section.
|
||||
|
||||
?has=TERM
|
||||
Filter entries to those containing TERM.
|
||||
|
||||
?do=random
|
||||
Redirect to a random entry.
|
||||
|
||||
?do=newest
|
||||
Redirect to the most recent entry.
|
||||
|
||||
?compact
|
||||
Hide section labels and dates.
|
||||
|
||||
PUBLIC FILES
|
||||
cv.txt Curriculum vitae.
|
||||
now.txt What I'm doing now.
|
||||
changelog.txt Site changelog. I forget to update this often.
|
||||
stats.txt Site statistics. Generated at build-time.
|
||||
man.txt This man page.
|
||||
|
||||
SEE ALSO
|
||||
source https://github.com/llywelwyn/wynne.rs
|
||||
|
||||
AUTHOR
|
||||
lewis m.w. <lewis@wynne.rs>
|
||||
|
|
@ -5,15 +5,6 @@ export function formatDate(date: Date): string {
|
|||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
|
||||
export function extractDomain(url: string): string {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname.replace(/^www\./, '');
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatListItem(
|
||||
date: Date,
|
||||
url: string,
|
||||
|
|
@ -23,7 +14,7 @@ export function formatListItem(
|
|||
const pinnedBadge = options?.pinned ? ' [pinned]' : '';
|
||||
const suffix = options?.suffix ? ` ${options.suffix}` : '';
|
||||
const prefix = options?.prefix ?? '';
|
||||
return `${prefix}<span class="muted">${formatDate(date)}</span> <a href="${url}">${title}</a>${pinnedBadge}${suffix}`;
|
||||
return `<span class="list-meta">${prefix}<span class="muted">${formatDate(date)}</span> </span><a href="${url}">${title}</a>${pinnedBadge}${suffix}`;
|
||||
}
|
||||
|
||||
interface Sortable {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ export interface TxtFile {
|
|||
name: string;
|
||||
date: Date;
|
||||
pinned: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface TxtConfig {
|
||||
pinned?: string[];
|
||||
descriptions?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function getTxtDir(): string {
|
||||
|
|
@ -32,12 +34,14 @@ export function getTxtFiles(): TxtFile[] {
|
|||
const config = loadTxtConfig();
|
||||
const pinnedSet = new Set(config.pinned || []);
|
||||
|
||||
const descriptions = config.descriptions || {};
|
||||
const files = fs.readdirSync(txtDir)
|
||||
.filter(file => file.endsWith('.txt'))
|
||||
.map(name => ({
|
||||
name,
|
||||
date: getGitLastModifiedDate(path.join(txtDir, name)),
|
||||
pinned: pinnedSet.has(name),
|
||||
description: descriptions[name],
|
||||
}));
|
||||
return sortByPinnedThenDate(files);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { getCollection } from 'astro:content';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
|
||||
import { formatDate, extractDomain, formatListItem } from '../lib/format';
|
||||
import { formatDate, formatListItem } from '../lib/format';
|
||||
import { organizePostsByCategory, getSlug, enrichPostsWithDates } from '../lib/md';
|
||||
import { getTxtFiles } from '../lib/txt';
|
||||
import { DEFAULT_CATEGORY, SECTIONS, SUBDOMAINS } from '../lib/consts';
|
||||
|
|
@ -16,6 +16,7 @@ const bookmarks = bookmarksCollection
|
|||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
const txtFiles = getTxtFiles();
|
||||
const txtMaxNameLen = Math.max(...txtFiles.map(f => f.name.replace(/\.txt$/, '').length));
|
||||
|
||||
const visibleLabels = [...sortedCategories.filter(c => c !== DEFAULT_CATEGORY), ...Object.values(SECTIONS)];
|
||||
const labelWidth = Math.max(...visibleLabels.map(l => l.length));
|
||||
|
|
@ -48,18 +49,23 @@ const urls = [
|
|||
})}
|
||||
|
||||
<section data-section={SECTIONS.plaintext}>
|
||||
<pre set:html={txtFiles.map((f, i) => formatListItem(f.date, `/${f.name}`, f.name, { pinned: f.pinned, prefix: i === 0 ? labelPrefix(SECTIONS.plaintext, `?just=${SECTIONS.plaintext}`) : blankPrefix })).join('\n')} />
|
||||
<pre set:html={txtFiles.map((f, i) => {
|
||||
const name = f.name.replace(/\.txt$/, '');
|
||||
const pad = ' '.repeat(txtMaxNameLen - name.length);
|
||||
const suffix = f.description ? `${pad} <span class="muted">(${f.description})</span>` : undefined;
|
||||
return formatListItem(f.date, `/${f.name}`, name, { pinned: f.pinned, suffix, prefix: i === 0 ? labelPrefix(SECTIONS.plaintext, `?just=${SECTIONS.plaintext}`) : blankPrefix });
|
||||
}).join('\n')} />
|
||||
</section>
|
||||
|
||||
<section data-section={SECTIONS.bookmarks}>
|
||||
<pre set:html={bookmarks.map((b, i) => formatListItem(b.data.date, b.data.url, b.data.title, { suffix: `<span class="muted">(${extractDomain(b.data.url)})</span>`, prefix: i === 0 ? labelPrefix(SECTIONS.bookmarks, `?just=${SECTIONS.bookmarks}`) : blankPrefix })).join('\n')} />
|
||||
<pre set:html={bookmarks.map((b, i) => formatListItem(b.data.date, b.data.url, b.data.title, { prefix: i === 0 ? labelPrefix(SECTIONS.bookmarks, `?just=${SECTIONS.bookmarks}`) : blankPrefix })).join('\n')} />
|
||||
</section>
|
||||
|
||||
<section data-section={SECTIONS.guestbook}>
|
||||
<pre class="guestbook-entries" set:html={guestbookEntries.map((e, i) => {
|
||||
const prefix = i === 0 ? labelPrefix(SECTIONS.guestbook, `?just=${SECTIONS.guestbook}`) : blankPrefix;
|
||||
const nameHtml = e.url ? `<a href="${e.url}"><b>${e.name}</b></a>` : `<b>${e.name}</b>`;
|
||||
return `<span class="guestbook-entry" style="padding-left: ${labelWidth + 12}ch; text-indent: -${labelWidth + 12}ch;">${prefix}<span class="muted">${formatDate(e.createdAt)}</span> ${nameHtml} ${e.message.replace(/\n/g, ' ')}</span>`;
|
||||
return `<span class="guestbook-entry" style="padding-left: ${labelWidth + 12}ch; text-indent: -${labelWidth + 12}ch;"><span class="list-meta">${prefix}<span class="muted">${formatDate(e.createdAt)}</span> </span>${nameHtml} ${e.message.replace(/\n/g, ' ')}</span>`;
|
||||
}).join('')} />
|
||||
<form id="guestbook-form" class="guestbook-form" style={`margin-left: ${labelWidth + 12}ch`}>
|
||||
<input type="text" name="name" placeholder="name" required maxlength="100" /><br />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
body {
|
||||
box-sizing: border-box;
|
||||
max-width: 38rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
|
|
@ -112,3 +113,14 @@ section pre {
|
|||
margin-top: 0.5rem;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
html[data-compact] .list-meta {
|
||||
display: none;
|
||||
}
|
||||
html[data-compact] .guestbook-entry {
|
||||
padding-left: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
}
|
||||
html[data-compact] .guestbook-form {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue