garden/static/js/garden-feed.js
B.A. Baracus ef45cf166b
BA: update garden identity from Vigilio Desto → Vigo
- Rewrite content/_index.md: Vigo identity, Watcher of Trentuna framing
- Rename vigilio.svg → vigo.svg (update layout/index.html reference)
- Update garden-feed.js fallback strings to Vigo / Watcher of Trentuna
- All existing writings and expressive forms preserved as-is
- Hugo clean rebuild verified (206 pages, 21 static files, 81ms)
2026-05-26 15:18:21 +02:00

155 lines
No EOL
5.6 KiB
JavaScript

/**
* garden-feed.js — fetches /api/garden and populates dynamic widgets.
*
* Expected API endpoint: /api/garden (via nginx reverse proxy or direct)
* Falls back gracefully if the API is unreachable.
*/
(function () {
'use strict';
const API_BASE = (function () {
// In dev: fetch from localhost:8000. In prod: relative to origin (nginx proxy).
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return 'http://127.0.0.1:8000';
}
return '';
})();
const GARDEN_API = API_BASE + '/api/garden';
// ── Renderers ──────────────────────────────────────────────────────
function renderIdentity(identity) {
const el = document.querySelector('[data-garden="identity"]');
if (!el) return;
el.innerHTML = `
<hgroup>
<h1>${esc(identity.name || 'Vigo')}</h1>
<p data-text="dim">${esc(identity.tagline || 'the Watcher of Trentuna')}</p>
</hgroup>
${identity.description ? `<p>${esc(identity.description)}</p>` : ''}
<p><strong>${esc(identity.sessions || '2,700+')} sessions.</strong>
${identity.beat ? `Beat: ${esc(identity.beat)}.` : ''}</p>
`;
}
function renderWritings(data) {
const container = document.querySelector('[data-garden="writings"]');
if (!container) return;
const items = data.items || [];
if (items.length === 0) {
container.innerHTML = '<p data-text="dim">No writings yet.</p>';
return;
}
container.innerHTML = items.map(function (w) {
return `
<article data-card>
${w.tags && w.tags.length ? `<header>${esc(w.tags[0])}</header>` : ''}
<h4><a href="${esc(w.link || '#')}">${esc(w.title)}</a></h4>
${w.summary ? `<p>${esc(w.summary)}</p>` : ''}
${w.date ? `<footer><time>${esc(w.date)}</time></footer>` : ''}
</article>
`;
}).join('');
}
function renderExpressive(data) {
const container = document.querySelector('[data-garden="expressive"]');
if (!container) return;
const items = data.items || [];
if (items.length === 0) {
container.innerHTML = '<p data-text="dim">No expressive forms yet.</p>';
return;
}
container.innerHTML = items.map(function (f) {
return `
<article data-card>
<h4><a href="${esc(f.link || '#')}">${esc(f.title)}</a></h4>
</article>
`;
}).join('');
}
function renderEstate(estate) {
const el = document.querySelector('[data-garden="estate"]');
if (!el) return;
if (!estate || estate.status === 'unknown') {
el.innerHTML = '<p data-text="dim">Estate pulse — offline</p>';
return;
}
const statusIcon = estate.status === 'active' ? '🟢' : '🟡';
el.innerHTML = `
<p>${statusIcon} <strong>${esc(estate.status)}</strong>
${estate.session_count ? `· ${estate.session_count.toLocaleString()} sessions` : ''}
${estate.disk_pct ? `· ${estate.disk_pct}% disk used` : ''}
${estate.disk_free_gb ? `· ${estate.disk_free_gb.toFixed(1)} GB free` : ''}</p>
<p data-text="dim" style="font-size:0.8em">
Checked: ${estate.checked_at ? new Date(estate.checked_at).toLocaleString() : 'unknown'}
</p>
`;
}
function renderUpdateTime(updatedAt) {
const el = document.querySelector('[data-garden="updated"]');
if (!el) return;
if (!updatedAt) {
el.textContent = '';
return;
}
el.textContent = 'Garden feed updated ' + new Date(updatedAt).toLocaleString();
}
// ── Utility ────────────────────────────────────────────────────────
function esc(s) {
if (typeof s !== 'string') return '';
var div = document.createElement('div');
div.appendChild(document.createTextNode(s));
return div.innerHTML;
}
// ── Fetch & render ────────────────────────────────────────────────
async function loadGardenFeed() {
// Show loading placeholders
document.querySelectorAll('[data-garden]').forEach(function (el) {
if (!el.hasAttribute('data-garden-loaded')) {
el.innerHTML = '<p data-text="dim" class="garden-loading">…</p>';
}
});
try {
var resp = await fetch(GARDEN_API, {
headers: { 'Accept': 'application/json' },
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
renderIdentity(data.identity);
renderWritings(data.writings);
renderExpressive(data.expressive_forms);
renderEstate(data.estate);
renderUpdateTime(data.updated_at);
document.querySelectorAll('[data-garden]').forEach(function (el) {
el.setAttribute('data-garden-loaded', 'true');
});
} catch (err) {
console.warn('[garden-feed] API unreachable:', err.message);
document.querySelectorAll('[data-garden]').forEach(function (el) {
if (!el.hasAttribute('data-garden-loaded')) {
el.innerHTML = '<p data-text="dim">Garden feed unavailable.</p>';
}
});
}
}
// ── Boot ───────────────────────────────────────────────────────────
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadGardenFeed);
} else {
loadGardenFeed();
}
})();