/** * 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
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 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);
}
}
});
});
})();