{# The admin shell. Every page extends this. The theme is applied as attributes on rather than by swapping a stylesheet, so there is one stylesheet and no flash of the wrong theme on load. #} {# The widgets script produces a handful of strings of its own -- "No results", "3 selected". They are handed over here rather than compiled into the script, because the script is one cached file for every language and this page is not. #} {# An admin must never be indexed, and never leak its URLs through a referer. #} {% block title %}{{ page_title }}{% endblock %} · {{ _(settings.project_name) }} {# A tab with no icon gets the browser's blank-page glyph, which is what every other unremarkable tab has -- and an admin is a tab people keep open and then hunt for. The project's own icon wins when it set one. #} {# Blocking, and before the stylesheets, on purpose. It applies the stored theme and sidebar state to while the document is still parsing, so the first paint is already correct. The main script is deferred -- it should not block the page -- but that means anything it sets happens after the first paint and is therefore a visible flash. #} {% for stylesheet in stylesheets %} {% endfor %} {% if theme.richtext_url %} {# The project's own editor, upgrading every `data-ff-richtext` textarea. Deferred like the main script: an editor is an enhancement, and the textarea underneath already works. #} {% endif %} {# The CSRF pair, so a confirmed action can post without a form of its own. No more exposed than it already is: the same value is a hidden input in every form on the page. #} {# Every icon on the page, once. Inline rather than a second request: an external target blocks the first paint of every icon that needs it. #} {{ ff_sprite() }} {# First tab stop on every page: keyboard users should not have to walk the whole sidebar to reach the table. #} {{ _("Skip to content") }} {# A popup is the same page without the shell: it was opened from a field on another form, so a sidebar listing every other model is noise, and there is nowhere for it to navigate to. One content block either way -- Jinja cannot declare the same block twice, and two copies of the form would be two copies to keep in step. #} {% if not popup %} {# The collapsed state lives on , set by boot.js before the first paint. It cannot live here: this element does not exist that early. #}
{# Collapsing the sidebar was reachable only by pressing "[", which is a feature nobody finds. On a narrow desktop the width it returns is the difference between six columns and four. #} {# Ctrl+K. In an admin with thirty models the sidebar stops being navigation and becomes a list to read; typing three letters beats all of it. Rendered only with script, because it opens a dialog rather than being a form that could submit on its own. #} {% if theme.environment_label %} {# In the header rather than the footer: which of two open windows is production has to be answerable without scrolling. #} {{ _(theme.environment_label) }} {% endif %} {% block topbar_actions %}{% endblock %} {# A one-click swap between light and dark. Which icon shows is decided by CSS from the theme attribute, so it is right on the first paint rather than after a script has run. The three-way choice, including "follow the system", lives in the account menu. #}
{% endif %}
{% if messages %} {# role="status" so a screen reader announces the outcome of a save without the person having to go looking for it. #}
{% for message in messages %}
{{ message.text }}
{% endfor %}
{% endif %} {% block content %}{% endblock %}
{% if not popup %}
{% endif %} {# Appearance. Every control here writes to local storage and applies to the root element, so it is per-person rather than per-project: the settings decide what a new visitor sees, and this decides what this one does. Script-only, and not rendered without it -- there is nothing a form submission could do with a preference the server does not store. #} {# The palette's contents, as data rather than markup: it is the sidebar's own list, a few hundred bytes, and a palette that has to wait for a request before it can answer is one people stop reaching for. #}