library sorting

This commit is contained in:
Levi Neuwirth 2026-04-11 15:10:48 -04:00
parent 61297a924e
commit e5ed6a3bb4
3 changed files with 124 additions and 9 deletions

View File

@ -404,7 +404,9 @@ rules = do
let ts = fromMaybe [] (lookupStringList "tags" meta)
return $ any (\t -> t == p || (p ++ "/") `isPrefixOf` t) ts
portalList name p = listField name essayCtx $ do
itemCtx = dateField "date-iso" "%Y-%m-%d" <> essayCtx
portalList name p = listField name itemCtx $ do
essays <- loadAll (allEssays .&&. hasNoVersion)
posts <- loadAll ("content/blog/*.md" .&&. hasNoVersion)
fiction <- loadAll ("content/fiction/*.md" .&&. hasNoVersion)

View File

@ -5,9 +5,54 @@
font-size: var(--text-size-small);
color: var(--text-muted);
margin-top: 0.25rem;
margin-bottom: 1.25rem;
}
/* ============================================================
SORT CONTROLS
============================================================ */
.library-controls {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 2.5rem;
}
.library-controls-label {
font-family: var(--font-sans);
font-size: 0.75rem;
color: var(--text-faint);
}
.library-controls-options {
display: flex;
gap: 0.3rem;
}
.library-sort-btn {
font-family: var(--font-sans);
font-size: 0.75rem;
color: var(--text-muted);
background: none;
border: 1px solid var(--border);
border-radius: 2px;
padding: 0.15em 0.55em;
cursor: pointer;
transition: border-color 0.1s, color 0.1s;
}
.library-sort-btn:hover {
border-color: var(--border-muted);
color: var(--text);
}
.library-sort-btn.is-active {
border-color: var(--text-muted);
color: var(--text);
font-weight: 600;
}
/* ============================================================
PORTAL SECTIONS
============================================================ */

View File

@ -2,11 +2,20 @@
<h1 class="page-title">Library</h1>
<p class="library-intro">Everything on this site, organized by portal.</p>
<div class="library-controls">
<span class="library-controls-label">Sort by</span>
<div class="library-controls-options" role="group" aria-label="Sort order">
<button class="library-sort-btn" data-sort="date">date</button>
<button class="library-sort-btn" data-sort="title">title</button>
<button class="library-sort-btn" data-sort="score">epistemic effort</button>
</div>
</div>
$if(research-entries)$
<section class="library-section">
<h2 id="research"><a href="/research/">Research</a></h2>
<ul class="library-list">$for(research-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -20,7 +29,7 @@ $if(nonfiction-entries)$
<section class="library-section">
<h2 id="nonfiction"><a href="/nonfiction/">Nonfiction</a></h2>
<ul class="library-list">$for(nonfiction-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -34,7 +43,7 @@ $if(fiction-entries)$
<section class="library-section">
<h2 id="fiction"><a href="/fiction/">Fiction</a></h2>
<ul class="library-list">$for(fiction-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -48,7 +57,7 @@ $if(poetry-entries)$
<section class="library-section">
<h2 id="poetry"><a href="/poetry/">Poetry</a></h2>
<ul class="library-list">$for(poetry-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -62,7 +71,7 @@ $if(music-entries)$
<section class="library-section">
<h2 id="music"><a href="/music/">Music</a></h2>
<ul class="library-list">$for(music-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -76,7 +85,7 @@ $if(ai-entries)$
<section class="library-section">
<h2 id="ai"><a href="/ai/">AI</a></h2>
<ul class="library-list">$for(ai-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -90,7 +99,7 @@ $if(tech-entries)$
<section class="library-section">
<h2 id="tech"><a href="/tech/">Tech</a></h2>
<ul class="library-list">$for(tech-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -104,7 +113,7 @@ $if(miscellany-entries)$
<section class="library-section">
<h2 id="miscellany"><a href="/miscellany/">Miscellany</a></h2>
<ul class="library-list">$for(miscellany-entries)$
<li class="library-entry">
<li class="library-entry" data-date="$date-iso$"$if(overall-score)$ data-score="$overall-score$"$endif$>
<div class="library-entry-header">
<a class="library-entry-title" href="$url$">$title$</a>
<span class="library-entry-date">$date-created$</span>
@ -115,3 +124,62 @@ $if(miscellany-entries)$
$endif$
</div>
<script>
(function () {
var STORAGE_KEY = 'library-sort';
var DEFAULT = 'date';
var MODES = { date: 1, title: 1, score: 1 };
function titleOf(entry) {
var el = entry.querySelector('.library-entry-title');
return el ? el.textContent.trim().toLowerCase() : '';
}
function compare(a, b, mode) {
if (mode === 'title') {
return titleOf(a).localeCompare(titleOf(b));
}
if (mode === 'score') {
// Entries without a score sink to the bottom; ties broken by date desc.
var hasA = a.dataset.score !== undefined;
var hasB = b.dataset.score !== undefined;
if (hasA && !hasB) return -1;
if (!hasA && hasB) return 1;
if (hasA && hasB) {
var diff = Number(b.dataset.score) - Number(a.dataset.score);
if (diff !== 0) return diff;
}
// fall through to date-desc tiebreak
}
// date desc (ISO strings sort lexicographically)
var da = a.dataset.date || '';
var db = b.dataset.date || '';
if (da < db) return 1;
if (da > db) return -1;
return 0;
}
function applySort(mode) {
if (!MODES[mode]) mode = DEFAULT;
document.querySelectorAll('.library-list').forEach(function (list) {
var entries = Array.prototype.slice.call(list.querySelectorAll('.library-entry'));
entries.sort(function (a, b) { return compare(a, b, mode); });
entries.forEach(function (el) { list.appendChild(el); });
});
document.querySelectorAll('.library-sort-btn').forEach(function (btn) {
btn.classList.toggle('is-active', btn.dataset.sort === mode);
});
try { localStorage.setItem(STORAGE_KEY, mode); } catch (e) {}
}
document.addEventListener('DOMContentLoaded', function () {
var saved;
try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {}
applySort(saved || DEFAULT);
document.querySelectorAll('.library-sort-btn').forEach(function (btn) {
btn.addEventListener('click', function () { applySort(btn.dataset.sort); });
});
});
}());
</script>