110 lines
4.4 KiB
JavaScript
110 lines
4.4 KiB
JavaScript
/**
|
|
* 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);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
})();
|