website-htmx-rustelo-code/site/public/js/reinit-handlers.js

111 lines
4.4 KiB
JavaScript
Raw Normal View History

2026-07-10 03:44:13 +01:00
/**
* reinit-handlers.js registers the standard set of HTMX swap re-initialisers.
*
* Loaded AFTER htmx-reinit.js and after the underlying libraries (hljs,
* cytoscape) so the handler bodies can call them directly. Each handler
* scopes its DOM queries to the swap target passing an Element to
* querySelectorAll only searches that subtree, so handlers are O(swap)
* rather than O(document) on every swap.
*/
(function () {
'use strict';
if (!window.RusteloReinit) {
console.error('[reinit-handlers] htmx-reinit.js must load before this script');
return;
}
// --- highlight.js -------------------------------------------------------
// Highlight every <pre><code> in the swapped region that isn't already
// decorated. hljs marks processed blocks by adding the .hljs class, which
// makes the selector self-cleaning on re-runs.
window.RusteloReinit.register('highlight', function (scope) {
if (typeof window.hljs === 'undefined' || !window.hljs.highlightElement) {
return;
}
scope.querySelectorAll('pre code:not(.hljs)').forEach(function (block) {
window.hljs.highlightElement(block);
});
});
// --- theme-init ---------------------------------------------------------
// Re-apply the dark/light class on <html> from the theme cookie. Cheap
// and idempotent: setting an already-set class is a no-op.
window.RusteloReinit.register('theme', function (_scope) {
const matches = document.cookie.match(/theme=([^;]+)/);
const theme = matches ? matches[1] : 'dark';
const html = document.documentElement;
const wantDark = theme === 'dark' ||
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (wantDark) {
html.classList.add('dark');
html.classList.remove('light');
} else {
html.classList.add('light');
html.classList.remove('dark');
}
});
// --- tag-filters --------------------------------------------------------
// Re-arm the filter pill click handlers in the swapped region. The
// legacy script set window.tagFiltersInitialized = true and bailed on
// subsequent runs; bypass that flag by re-binding only pills that don't
// yet carry our data-reinit attribute.
window.RusteloReinit.register('tag-filters', function (scope) {
if (typeof window.filterPostsByTag !== 'function') {
return;
}
scope.querySelectorAll('[data-tag-pill]:not([data-tag-reinit])').forEach(function (pill) {
pill.setAttribute('data-tag-reinit', '1');
pill.addEventListener('click', function (ev) {
ev.preventDefault();
const tag = pill.getAttribute('data-tag');
if (tag) {
window.filterPostsByTag(tag);
}
});
});
});
// --- scroll-to-top -------------------------------------------------------
// Wires the footer "Scroll to top" button emitted by UnifiedFooter (SSR).
// The Leptos on:click handler is WASM-only and compiles to nothing in SSR;
// this provides the equivalent for the HTMX profile via event delegation.
// Runs once: the button lives in the footer which is never swapped.
(function () {
if (window.__rusteloScrollToTopWired) {
return;
}
window.__rusteloScrollToTopWired = true;
document.addEventListener('click', function (ev) {
var btn = ev.target && ev.target.closest('button[aria-label="Scroll to top"]');
if (btn) {
ev.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
});
})();
// --- content-graph ------------------------------------------------------
// ContentGraph.render is idempotent against the same container id (it
// tears down the previous instance internally). Only fire when the
// swap actually contains a graph container.
window.RusteloReinit.register('content-graph', function (scope) {
if (!window.ContentGraph || typeof window.ContentGraph.render !== 'function') {
return;
}
scope.querySelectorAll('[data-content-graph]').forEach(function (el) {
const containerId = el.getAttribute('id');
const focusId = el.getAttribute('data-focus-id') || null;
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
if (containerId) {
try {
window.ContentGraph.render(containerId, focusId, theme);
} catch (err) {
console.error('[reinit:content-graph] render failed', err);
}
}
});
});
})();