This commit is contained in:
@@ -63,6 +63,10 @@ making changes; it is the authoritative operations and deployment runbook.
|
||||
mustard labels and are excluded from discovery.
|
||||
- Keep project devlog entries in the homepage Recent Activity feed alongside
|
||||
projects, blog entries, and articles, ordered by publication date.
|
||||
- Keep Recent Activity batching as a progressive enhancement: render the full
|
||||
semantic feed, show six items initially with JavaScript, reveal six more per
|
||||
activation, announce the visible count, and leave all items available when
|
||||
JavaScript is disabled.
|
||||
- When `README.md` changes, keep the README-derived example blog entry aligned
|
||||
with the runbook content it demonstrates.
|
||||
|
||||
|
||||
@@ -199,7 +199,10 @@ The public routes are `/projects/thinkloom/`,
|
||||
native Lektor records and approved images. This site owns their models,
|
||||
templates, layout, repository-information card, and import policy. Imported
|
||||
devlog entries also join the homepage Recent Activity feed, ordered with
|
||||
projects, articles, and blog entries by publication date.
|
||||
projects, articles, and blog entries by publication date. The feed initially
|
||||
shows six items and reveals up to six more per button activation. This is a
|
||||
progressive enhancement implemented by `assets/static/activity.js`; without
|
||||
JavaScript, the complete semantic feed remains visible.
|
||||
|
||||
`configs/project-sources.ini` is the allowlist. Each source declares a stable
|
||||
project ID, credential-free HTTPS Git URL, public web/API URL, and branch. Add
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
(() => {
|
||||
for (const feed of document.querySelectorAll("[data-activity-feed]")) {
|
||||
const items = [...feed.querySelectorAll("[data-activity-item]")];
|
||||
const batchSize = Number.parseInt(feed.dataset.batchSize || "6", 10);
|
||||
const controls = feed.parentElement.querySelector("[data-activity-controls]");
|
||||
const status = controls?.querySelector("[data-activity-status]");
|
||||
const button = controls?.querySelector("[data-activity-more]");
|
||||
|
||||
if (!controls || !status || !button || items.length <= batchSize) continue;
|
||||
|
||||
let visibleCount = Math.min(batchSize, items.length);
|
||||
|
||||
const render = () => {
|
||||
for (const [index, item] of items.entries()) {
|
||||
item.hidden = index >= visibleCount;
|
||||
}
|
||||
|
||||
const remaining = items.length - visibleCount;
|
||||
status.textContent = `Showing ${visibleCount} of ${items.length} recent activity items.`;
|
||||
button.disabled = remaining === 0;
|
||||
button.textContent = remaining
|
||||
? `Show ${Math.min(batchSize, remaining)} more`
|
||||
: "All activity shown";
|
||||
controls.hidden = false;
|
||||
};
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
visibleCount = Math.min(visibleCount + batchSize, items.length);
|
||||
render();
|
||||
});
|
||||
|
||||
render();
|
||||
}
|
||||
})();
|
||||
@@ -57,6 +57,23 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.timeline-entry h2, .entry-card h2 { margin: 10px 0 5px; font-size: 21px; line-height: 1.3; }
|
||||
.timeline-entry p, .entry-card p { max-width: 680px; margin: 0 0 10px; color: var(--muted); }
|
||||
.read-more { color: var(--magenta); font-size: 10px; }
|
||||
.activity-controls { display: flex; justify-content: space-between; align-items: center; gap: 18px; padding: 24px 0 0 37px; }
|
||||
.activity-controls[hidden] { display: none; }
|
||||
.activity-status { margin: 0; color: var(--muted); font: 500 9px var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.activity-more-button {
|
||||
border: 1px solid var(--rule);
|
||||
background: transparent;
|
||||
padding: 7px 10px;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
font: 500 9px var(--mono);
|
||||
letter-spacing: .08em;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.activity-more-button:hover,
|
||||
.activity-more-button:focus-visible { border-color: var(--teal); color: var(--teal); }
|
||||
.activity-more-button:disabled { cursor: default; opacity: .55; }
|
||||
|
||||
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
|
||||
.entry-card { min-height: 245px; padding: 28px; background: var(--bg); }
|
||||
@@ -286,6 +303,7 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.card-grid { grid-template-columns: 1fr; }
|
||||
.tag-directory { grid-template-columns: 1fr; }
|
||||
.tag-filter { padding-block: 18px; }
|
||||
.activity-controls { align-items: flex-start; padding-left: 30px; }
|
||||
.tag-directory-card { min-height: 0; }
|
||||
.project-hero { padding-block: 44px; }
|
||||
.project-hero-inner,
|
||||
|
||||
+2
-1
@@ -9,8 +9,9 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&family=Playfair+Display:ital,wght@0,600;0,700;1,500;1,600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" href="{{ '/static/favicon.svg'|url }}" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=6">
|
||||
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=7">
|
||||
<script src="{{ '/static/tags.js'|url }}?v=1" defer></script>
|
||||
<script src="{{ '/static/activity.js'|url }}?v=1" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
|
||||
+6
-2
@@ -12,7 +12,7 @@
|
||||
<span>Recent Activity</span>
|
||||
<span>Sorted by date</span>
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<div class="timeline" id="recent-activity-list" data-activity-feed data-batch-size="6">
|
||||
{% set entries = [] %}
|
||||
{% for section_id in ['projects', 'articles', 'blog'] %}
|
||||
{% for item in site.get('/' ~ section_id).children %}
|
||||
@@ -30,7 +30,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for item in entries|sort(attribute='date', reverse=true) %}
|
||||
<article class="timeline-entry accent-{{ item.kicker|lower }}">
|
||||
<article class="timeline-entry accent-{{ item.kicker|lower }}" data-activity-item>
|
||||
<div class="entry-meta">
|
||||
<span class="label">{{ item.kicker }}</span>
|
||||
<time datetime="{{ item.date }}">{{ item.date|dateformat('YYYY.MM.dd') }}</time>
|
||||
@@ -41,5 +41,9 @@
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="activity-controls" data-activity-controls hidden>
|
||||
<p class="activity-status" data-activity-status aria-live="polite"></p>
|
||||
<button class="activity-more-button" type="button" data-activity-more aria-controls="recent-activity-list">Show 6 more</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user