feat: newspaper columnar layout - currently in newspaper order, though maybe left-right would be better

This commit is contained in:
Lewis Wynne 2026-03-27 17:34:48 +00:00
parent 880b92900f
commit d65342fd73
4 changed files with 49 additions and 26 deletions

View file

@ -31,11 +31,10 @@
document.documentElement.dataset.has = has; document.documentElement.dataset.has = has;
has = has.toLowerCase(); has = has.toLowerCase();
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('section[data-section] pre').forEach(function(pre) { document.querySelectorAll('section[data-section] .entry').forEach(function(entry) {
var lines = pre.innerHTML.split('\n'); if (entry.textContent.toLowerCase().indexOf(has) === -1) {
pre.innerHTML = lines.filter(function(line) { entry.style.display = 'none';
return !line.trim() || line.toLowerCase().indexOf(has) !== -1; }
}).join('\n');
}); });
document.querySelectorAll('.guestbook-entry').forEach(function(entry) { document.querySelectorAll('.guestbook-entry').forEach(function(entry) {
if (entry.textContent.toLowerCase().indexOf(has) === -1) { if (entry.textContent.toLowerCase().indexOf(has) === -1) {

View file

@ -35,7 +35,7 @@ export function formatListItem(
const pinnedBadge = options?.pinned ? ' [pinned]' : ''; const pinnedBadge = options?.pinned ? ' [pinned]' : '';
const suffix = options?.suffix ? ` ${options.suffix}` : ''; const suffix = options?.suffix ? ` ${options.suffix}` : '';
const prefix = options?.prefix ?? ''; const prefix = options?.prefix ?? '';
return `<span class="list-meta">${prefix}<span class="muted">${formatDate(date)}</span> </span><a href="${url}">${title}</a>${pinnedBadge}${suffix}`; return `<span class="list-meta">${prefix}<span class="muted">${formatDate(date)}</span></span><span class="entry-content"><a href="${url}">${title}</a>${pinnedBadge}${suffix}</span>`;
} }
interface Sortable { interface Sortable {

View file

@ -16,13 +16,6 @@ const bookmarks = bookmarksCollection
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); .sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const txtFiles = getTxtFiles(); 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));
const labelPrefix = (label: string, href: string) =>
`${' '.repeat(labelWidth - label.length)}<a class="section-label" href="${href}">${label}</a> `;
const blankPrefix = ' '.repeat(labelWidth + 2);
let guestbookEntries: GuestbookEntry[] = []; let guestbookEntries: GuestbookEntry[] = [];
try { try {
@ -43,33 +36,38 @@ const urls = [
const isDefault = category === DEFAULT_CATEGORY; const isDefault = category === DEFAULT_CATEGORY;
return ( return (
<section data-section={category}> <section data-section={category}>
<pre set:html={categoryPosts.map((post, i) => formatListItem(post.dates.created, `/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned, prefix: (!isDefault && i === 0) ? labelPrefix(category, `?just=${category}`) : blankPrefix })).join('\n')} /> {!isDefault && <a class="section-label" href={`?just=${category}`}>{category}</a>}
<div class="entry-list" set:html={categoryPosts.map(post =>
`<span class="entry">${formatListItem(post.dates.created, `/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })}</span>`
).join('')} />
</section> </section>
); );
})} })}
<section data-section={SECTIONS.plaintext}> <section data-section={SECTIONS.plaintext}>
<pre set:html={txtFiles.map((f, i) => { <a class="section-label" href={`?just=${SECTIONS.plaintext}`}>{SECTIONS.plaintext}</a>
<div class="entry-list" set:html={txtFiles.map(f => {
const name = f.name.replace(/\.txt$/, ''); const name = f.name.replace(/\.txt$/, '');
const pad = ' '.repeat(txtMaxNameLen - name.length); return `<span class="entry">${formatListItem(f.date, `/${f.name}`, name, { pinned: f.pinned })}</span>`;
const suffix = f.description ? `${pad} <span class="muted">(${f.description})</span>` : undefined; }).join('')} />
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>
<section data-section={SECTIONS.bookmarks}> <section data-section={SECTIONS.bookmarks}>
<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')} /> <a class="section-label" href={`?just=${SECTIONS.bookmarks}`}>{SECTIONS.bookmarks}</a>
<div class="entry-list" set:html={bookmarks.map(b =>
`<span class="entry">${formatListItem(b.data.date, b.data.url, b.data.title)}</span>`
).join('')} />
</section> </section>
<section data-section={SECTIONS.guestbook}> <section data-section={SECTIONS.guestbook}>
<div class="guestbook-entries" style={`--meta-width: ${labelWidth + 12}ch`} set:html={guestbookEntries.map((e, i) => { <a class="section-label" href={`?just=${SECTIONS.guestbook}`}>{SECTIONS.guestbook}</a>
const prefix = i === 0 ? labelPrefix(SECTIONS.guestbook, `?just=${SECTIONS.guestbook}`) : blankPrefix; <div class="guestbook-entries" set:html={guestbookEntries.map(e => {
const safeName = escapeHtml(e.name); const safeName = escapeHtml(e.name);
const safeMessage = escapeHtml(e.message.replace(/\n/g, ' ')); const safeMessage = escapeHtml(e.message.replace(/\n/g, ' '));
const nameHtml = e.url ? `<a href="${escapeHtml(e.url)}"><b>${safeName}</b></a>` : `<b>${safeName}</b>`; const nameHtml = e.url ? `<a href="${escapeHtml(e.url)}"><b>${safeName}</b></a>` : `<b>${safeName}</b>`;
return `<span class="guestbook-entry"><span class="list-meta">${prefix}<span class="muted">${formatDate(e.createdAt)}</span> </span><span>${nameHtml} ${safeMessage}</span></span>`; return `<span class="guestbook-entry"><span class="list-meta"><span class="muted">${formatDate(e.createdAt)}</span> </span><span>${nameHtml} ${safeMessage}</span></span>`;
}).join('')} /></div> }).join('')} /></div>
<form id="guestbook-form" class="guestbook-form" style={`margin-left: ${labelWidth + 12}ch`}> <form id="guestbook-form" class="guestbook-form">
<label class="sr-only" for="gb-name">name</label> <label class="sr-only" for="gb-name">name</label>
<input id="gb-name" type="text" name="name" placeholder="name" required maxlength="100" /><br /> <input id="gb-name" type="text" name="name" placeholder="name" required maxlength="100" /><br />
<label class="sr-only" for="gb-message">message</label> <label class="sr-only" for="gb-message">message</label>

View file

@ -1,6 +1,6 @@
body { body {
box-sizing: border-box; box-sizing: border-box;
max-width: 38rem; max-width: 48rem;
margin: 0 auto; margin: 0 auto;
padding: 1rem; padding: 1rem;
text-align: justify; text-align: justify;
@ -102,6 +102,25 @@ section pre {
margin: 0; margin: 0;
} }
.entry-list {
columns: 2 24ch;
column-gap: 3ch;
font-family: monospace;
margin: 0;
}
.entry {
display: grid;
grid-template-columns: 10ch 1fr;
break-inside: avoid;
}
.entry-content {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.guestbook-entries { .guestbook-entries {
font-family: monospace; font-family: monospace;
white-space: pre; white-space: pre;
@ -109,7 +128,7 @@ section pre {
.guestbook-entry { .guestbook-entry {
display: grid; display: grid;
grid-template-columns: var(--meta-width) 1fr; grid-template-columns: 10ch 1fr;
} }
.guestbook-entry > span:last-child { .guestbook-entry > span:last-child {
@ -118,12 +137,19 @@ section pre {
.guestbook-form { .guestbook-form {
margin-top: 0.5rem; margin-top: 0.5rem;
margin-left: 10ch;
font-family: monospace; font-family: monospace;
} }
html[data-compact] .list-meta { html[data-compact] .list-meta {
display: none; display: none;
} }
html[data-compact] .entry {
display: block;
}
html[data-compact] .entry-list {
columns: 1;
}
html[data-compact] .guestbook-entry { html[data-compact] .guestbook-entry {
display: block; display: block;
} }