/** * Delegated listener that binds exactly once. * * modal_footer.php re-loads this file every time an ajax modal opens, which is * why the jQuery originals used a namespaced .off().on() - without it the * handlers stacked up and fired once per modal ever opened. The named flag on * window is the vanilla equivalent. `this` is the matched element, matching * jQuery's delegation contract so the handler bodies are unchanged. */ /** * Run one initialiser in isolation. * * itflowInit() sets up eight independent libraries in sequence. Without this, * a throw in any one of them aborts the whole function and every initialiser * after it silently never runs - which is exactly the kind of failure that * looks like "everything is broken" while the console shows one error from a * library you were not looking at. */ function itflowStep(name, fn) { try { fn(); } catch (e) { console.error('itflow init [' + name + '] failed:', e); } } /** * TinyMCE skin options for the current colour mode. * * TinyMCE renders its chrome in its own DOM and its content inside an iframe, * so it inherits nothing from the page - a dark page still got a white editor. * Both the oxide-dark UI skin and the dark content stylesheet are already * vendored under libs/tinymce/skins, so this only has to point at them. */ function itflowTinyMceSkin() { var dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'; var root = getComputedStyle(document.documentElement); var body = getComputedStyle(document.body); function v(name, fallback) { var value = root.getPropertyValue(name).trim(); return value || fallback; } // The editing surface lives in an iframe, so no page CSS reaches it and // custom properties do not cross the boundary either - the values have to // be read here and passed through as literal text. Without this the editor // keeps TinyMCE's own blue-slate dark (#222f3e) next to the app's neutral // grey (#212529), which is close enough to look like a mistake. var contentStyle = 'body{' + 'background-color:' + v('--bs-body-bg', '#fff') + ';' + 'color:' + v('--bs-body-color', '#212529') + ';' + 'font-family:' + body.fontFamily + ';' + 'font-size:' + body.fontSize + ';' + '}' + 'a{color:' + v('--itflow-accent', '#007bff') + ';}' + 'table td,table th{border-color:' + v('--bs-border-color', '#dee2e6') + ';}'; return { skin: dark ? 'oxide-dark' : 'oxide', content_css: dark ? 'dark' : 'default', content_style: contentStyle }; } /** * Chart.js defaults for the current colour mode. * * Charts are drawn to a , so no CSS reaches them - every colour has to * be handed to Chart.js as a literal. The pages used to hardcode * Chart.defaults.color = '#292b2c', which was fine while the cards were * effectively light but is invisible on a real dark background. * * Returns the axis/grid colour too, since the charts set that per-scale. */ function itflowChartDefaults() { var root = getComputedStyle(document.documentElement); var body = getComputedStyle(document.body); function v(name, fallback) { return (root.getPropertyValue(name) || '').trim() || fallback; } var text = v('--bs-body-color', '#292b2c'); var grid = v('--bs-border-color', 'rgba(0, 0, 0, .125)'); if (window.Chart) { Chart.defaults.font.family = body.fontFamily; Chart.defaults.color = text; Chart.defaults.borderColor = grid; } return { text: text, grid: grid }; } function itflowBindOnce(name, type, selector, handler) { window.itflowBound = window.itflowBound || {}; if (window.itflowBound[name]) { return; } window.itflowBound[name] = true; document.addEventListener(type, function (e) { const match = e.target.closest(selector); if (match) { handler.call(match, e); } }); } /** * Run fn once the DOM is ready, the way jQuery's .ready() did. * * js/ajax_modal.js injects a modal payload's