cyb/optica/templates/files.html

{% extends "base.html" %}

{% block title %}Files β€” {{ site.title }}{% endblock %}
{% block description %}All files sorted by focus{% endblock %}

{% block content %}
<div class="pages-discovery">
  <div class="files-stats-top">{{ total_files }} files &middot; {{ total_connections }} connections &middot; {{ total_size }} &middot; {{ total_lines }}</div>
  <table class="pages-table">
    <thead>
      <tr>
        <th class="col-rank" data-col="rank">#</th>
        <th class="col-title" data-col="title">file</th>
        <th class="col-created" data-col="created">created</th>
        <th class="col-modified" data-col="modified">modified</th>
        <th class="col-size" data-col="size">size</th>
        <th class="col-links" data-col="out">out</th>
        <th class="col-links" data-col="in">in</th>
        <th class="col-pagerank" data-col="focus">Ο€%</th>
        <th class="col-density" data-col="density">density</th>
        <th class="col-gravity" data-col="gravity">gravity</th>
      </tr>
    </thead>
    <tbody>
      {% for f in files %}
      <tr id="r{{ f.rank }}" class="page-row">
        <td class="col-rank" data-sort="{{ f.rank }}">{{ f.rank }}</td>
        <td class="col-title" data-sort="{{ f.title }}">
          <a href="{{ f.url }}" title="{{ f.title }}">{% if f.icon %}<span class="page-icon">{{ f.icon }}</span> {% endif %}<span class="row-title-text">{{ f.title }}</span></a>
          {% if f.tags %}<span class="row-tags">{% for t in f.tags %}<a href="/{{ t | slugify }}" class="row-tag">{{ t }}</a>{% endfor %}</span>{% endif %}
        </td>
        <td class="col-created" data-sort="{{ f.created_sort }}">{{ f.created }}</td>
        <td class="col-modified" data-sort="{{ f.modified_sort }}">{{ f.modified }}</td>
        <td class="col-size" data-sort="{{ f.size_sort }}" style="color: hsl(50, 60%, {{ f.size_light }}%)">{{ f.size }}</td>
        <td class="col-links" data-sort="{{ f.out_sort }}" style="color: hsl(30, 60%, {{ f.out_light }}%)">{{ f.links_out }}</td>
        <td class="col-links" data-sort="{{ f.in_sort }}" style="color: hsl(210, 60%, {{ f.in_light }}%)">{{ f.links_in }}</td>
        <td class="col-pagerank" data-sort="{{ f.focus_sort }}" style="color: hsl(120, 60%, {{ f.focus_light }}%)">{{ f.pagerank }}</td>
        <td class="col-density" data-sort="{{ f.density_sort }}" style="color: hsl(0, 60%, {{ f.den_light }}%)">{{ f.density }}</td>
        <td class="col-gravity" data-sort="{{ f.gravity_sort }}" style="color: hsl(270, 60%, {{ f.grav_light }}%)">{{ f.gravity }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

<script>
(function() {
  if (location.hash) {
    var el = document.getElementById(location.hash.slice(1));
    if (el) {
      requestAnimationFrame(function() {
        el.scrollIntoView({ block: 'start' });
      });
    }
  }

  var rows = document.querySelectorAll('.page-row');
  if (!rows.length) return;

  // Use IntersectionObserver to track which row sits at the top of
  // the viewport. The old approach iterated all 22k rows per scroll
  // event, calling getBoundingClientRect each time and forcing
  // 22k layout flushes per frame β€” laggy + the URL hash didn't
  // update reliably either.
  //
  // Strategy: define a thin "tracking band" at the top of the
  // viewport via rootMargin and remember which rows are currently
  // intersecting it. The topmost of those rows is the current entry;
  // pick it on every observer batch.
  var inBand = new Set();
  function topmostInBand() {
    var best = null;
    inBand.forEach(function(r) {
      if (!best) { best = r; return; }
      // earlier in DOM = higher in the scrolled page
      if (r.compareDocumentPosition(best) & Node.DOCUMENT_POSITION_FOLLOWING) {
        best = r;
      }
    });
    return best;
  }
  var io = new IntersectionObserver(function(entries) {
    for (var i = 0; i < entries.length; i++) {
      var e = entries[i];
      if (e.isIntersecting) inBand.add(e.target);
      else inBand.delete(e.target);
    }
    var t = topmostInBand();
    if (t && t.id && location.hash !== '#' + t.id) {
      history.replaceState(null, '', '#' + t.id);
    }
  }, { rootMargin: '0px 0px -92% 0px' });
  rows.forEach(function(r) { io.observe(r); });

  // Column sorting
  var thead = document.querySelector('.pages-table thead');
  var tbody = document.querySelector('.pages-table tbody');
  if (thead && tbody) {
    var currentCol = null;
    var ascending = false;

    thead.querySelectorAll('th[data-col]').forEach(function(th) {
      th.addEventListener('click', function() {
        var col = th.dataset.col;
        if (currentCol === col) {
          ascending = !ascending;
        } else {
          currentCol = col;
          ascending = col === 'title';
        }

        thead.querySelectorAll('th').forEach(function(h) {
          h.classList.remove('sort-asc', 'sort-desc');
        });
        th.classList.add(ascending ? 'sort-asc' : 'sort-desc');

        var sortRows = Array.from(tbody.querySelectorAll('tr'));
        var colIdx = Array.from(thead.querySelectorAll('th')).indexOf(th);

        sortRows.sort(function(a, b) {
          var aVal = a.children[colIdx].dataset.sort || a.children[colIdx].textContent.trim();
          var bVal = b.children[colIdx].dataset.sort || b.children[colIdx].textContent.trim();
          var aNum = parseFloat(aVal);
          var bNum = parseFloat(bVal);
          var cmp;
          if (!isNaN(aNum) && !isNaN(bNum)) {
            cmp = aNum - bNum;
          } else {
            cmp = aVal.localeCompare(bVal);
          }
          return ascending ? cmp : -cmp;
        });

        sortRows.forEach(function(row) { tbody.appendChild(row); });
      });
    });
  }
})();
</script>
{% endblock %}

Graph