agilentics / boiler
{% extends "base.html" %}

{# The signed-in layout: a top bar, a foldable sidebar, and the region pages
   fill. Every page behind the session guard should extend this rather than
   base.html, so the navigation is inherited instead of re-declared.

   The sections come from app/endpoints/shell.py NAV, already filtered to what
   this caller may see and already carrying their fold state. Rendered by the
   server rather than fetched: the sidebar is part of the page, and painting it
   from JavaScript means every navigation shows an empty rail for a moment. #}

{% block head %}
<link rel="stylesheet" href="/static/css/shell.css">
{% endblock %}

{% block body %}
<header class="appbar">
  {# The control is the button. aria-expanded is toggled by shell.js, so a
     screen reader is told the state rather than left to infer it from a glyph. #}
  <button class="navtoggle" id="navToggle" type="button"
          aria-label="Toggle navigation" aria-controls="sidebar" aria-expanded="true">
    <span></span><span></span><span></span>
  </button>

  <a class="appbrand" href="/">{{ app_name }}</a>

  <div class="acct" id="acctMenu">
    <button class="avatarbtn" id="acctBtn" type="button"
            aria-haspopup="menu" aria-expanded="false" aria-label="Account"></button>
    <div class="acctdrop" id="acctDrop" role="menu" hidden>
      <a role="menuitem" href="/account">Security</a>
      <a role="menuitem" href="/logout">Sign out</a>
    </div>
  </div>
</header>

<div class="shell">
  {# Closes the sidebar when it is over the content on a narrow screen. Inert at
     desktop width, where the sidebar is beside the content and nothing is
     covered. #}
  <div class="scrim" id="scrim" hidden></div>

  <aside class="sidebar" id="sidebar">
    <nav aria-label="Sections">
      {% for section in nav_sections %}
      <div class="navsec{% if section.folded %} folded{% endif %}"
           data-key="{{ section.key }}">
        <div class="h">
          {# The twisty is the control; the heading around it is only a heading.
             A control inside a control has no meaning in the accessibility tree
             - a screen reader announces one thing where there are two - so the
             button is the twisty, and the rest of the heading keeps its click
             for the mouse alone, with no role and no tabindex duplicating it. #}
          <button class="navsec-tw" type="button"
                  aria-label="{{ section.title }}"
                  aria-controls="navsec-b-{{ loop.index }}"
                  aria-expanded="{{ 'false' if section.folded else 'true' }}">▾</button>
          <span class="navsec-t">{{ section.title }}</span>
        </div>
        <div class="navsec-b" id="navsec-b-{{ loop.index }}">
          {% for item in section["items"] %}
          <a class="navitem{% if item.href == request.path %} here{% endif %}"
             href="{{ item.href }}"
             {% if item.href == request.path %}aria-current="page"{% endif %}>{{ item.label }}</a>
          {% endfor %}
        </div>
      </div>
      {% endfor %}

      {# The tree: the organisation's own categories, and everything nobody has
         filed. Two levels, both foldable, both remembered per person. #}
      <div class="navsec" data-key="sec:tree">
        <div class="h">
          <button class="navsec-tw" type="button" aria-label="Categories"
                  aria-controls="navsec-b-tree" aria-expanded="true">▾</button>
          <span class="navsec-t">Categories</span>
          {% if nav_tree.can_arrange %}
          {# Inside the heading but outside the twisty: a control within a
             control has no meaning in the accessibility tree. #}
          <button class="navsec-add" type="button" id="addCategory"
                  aria-label="Add a category" title="Add a category">+</button>
          {% endif %}
        </div>

        <div class="navsec-b" id="navsec-b-tree">
          {% for category in nav_tree.categories %}
          <div class="navcat{% if category.folded %} folded{% endif %}"
               data-key="cat:{{ category.id }}" data-cat="{{ category.id }}">
            <div class="navcat-h">
              <button class="navcat-tw" type="button"
                      aria-label="{{ category.name }}"
                      aria-controls="navcat-b-{{ category.id }}"
                      aria-expanded="{{ 'false' if category.folded else 'true' }}">▾</button>
              <span class="navcat-nm">{{ category.name }}</span>
              <span class="navcat-n">{{ category["items"]|length }}</span>
            </div>
            <div class="navcat-b" id="navcat-b-{{ category.id }}">
              {% for item in category["items"] %}
              {% include "_navitem.html" %}
              {% else %}
              <p class="navcat-empty">Nothing filed here yet.</p>
              {% endfor %}
            </div>
          </div>
          {% endfor %}

          {# Not a category. It has no row on the server, cannot be renamed or
             deleted, and is not in the reorder - it is simply where the rest
             are. Drawn only when it holds something, because a permanently
             empty heading is worse than no heading. #}
          {% if nav_tree.ungrouped %}
          <div class="navcat navcat-fixed" data-cat="">
            <div class="navcat-h">
              <span class="navcat-tw"></span>
              <span class="navcat-nm loose">Ungrouped</span>
              <span class="navcat-n">{{ nav_tree.ungrouped|length }}</span>
            </div>
            <div class="navcat-b">
              {% for item in nav_tree.ungrouped %}
              {% include "_navitem.html" %}
              {% endfor %}
            </div>
          </div>
          {% endif %}

          {% if not nav_tree.categories and not nav_tree.ungrouped %}
          <p class="navcat-empty">
            No categories yet.{% if nav_tree.can_arrange %} Use + to add one.{% endif %}
          </p>
          {% endif %}
        </div>
      </div>
    </nav>
  </aside>

  <main class="main">
    {% block content %}{% endblock %}
  </main>
</div>
{% endblock %}

{% block scripts %}
<script src="/static/js/shell.js"></script>
{% block page_scripts %}{% endblock %}
{% endblock %}