{# PR4 (p17e) -- fragment-style base for /admin/* pages. Pages that extend this template render as a content fragment injected into the main slot by render_shell_document(content_html=...). The shell owns the surrounding chrome (header, sidebar, layout); this fragment contributes only the page-specific body, any extra CSS, and any extra JS. The legacy Vue + bootstrap-vue script stack has been retired -- no admin template mounts Vue widgets any more. {% block title %} is preserved so per-page templates can still set their without altering the wrapper -- the shell document picks the title up from the render call. Legacy chrome (the old fixed header, sidebar nav, login link) is intentionally removed; PR4 task F-4 deletes the duplicated chrome since the site shell now provides it. #} <section class="pyc-admin-page-fragment"> {#- The child template's title block feeds the shell document's <title>; the shell wrapper reads and removes this element. #} <meta name="pyc-page-title" content="{% block title %}{% endblock %}"> {# Carry stylesheets the admin templates depend on, alongside any extra_css the child block contributes. Material Icons is the only stylesheet the shell document does not already provide; color-theme.css is repo-local. bootstrap / bootstrap-vue / ccai.css were dropped when the project moved to the webworks + mj-* component stack -- the files no longer ship and the legacy <link> tags returned 404. #} {% block extra_css %}{% endblock %} {% if flashes %} {% for category, message in flashes %} <div class="alert alert-{{ category }}">{{ message }}</div> {% endfor %} {% endif %} {% block content %}{% endblock %} {# The legacy polyfill / vue / bootstrap-vue script tags were removed: no admin template mounts a Vue widget any more, so the ~2.4 MB stack was dead weight on every /admin page. #} <script nonce="{{ csp_nonce() }}"> // Unsaved-changes guard for the admin POST forms: warn on // navigation after an edit until the form is submitted. // GET filter forms are exempt -- losing an unsubmitted // filter value costs nothing. (function () { var dirty = false; function owning_post_form(target) { if (target === null || typeof target.closest !== "function") { return null; } var form = target.closest("form"); if (form === null) { return null; } var method = (form.getAttribute("method") || "get").toLowerCase(); if (method !== "post") { return null; } return form; } document.addEventListener("input", function (event) { if (owning_post_form(event.target) !== null) { dirty = true; } }, true); document.addEventListener("change", function (event) { if (owning_post_form(event.target) !== null) { dirty = true; } }, true); document.addEventListener("submit", function () { dirty = false; }, true); window.addEventListener("beforeunload", function (event) { if (dirty === true) { event.preventDefault(); event.returnValue = ""; } }); })(); </script> {% block extra_js %}{% endblock %} </section>