- 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)
155 lines
No EOL
5.6 KiB
JavaScript
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();
|
|
}
|
|
})(); |