Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Turn the user directory into a sortable table

4739664b94c4321b83a2650a3a2486ec090aed64 · Keenan Nemetz · 7d ago · parent c14588ef

Turn the user directory into a sortable table

The list became a table with the same counts, and the column headers
order it: user, repositories, issues, changes, reviews, joining date.
Counts start largest-first and the name A to Z; clicking the active
header flips it. The active column carries aria-sort and an arrow.

The search, the status chips (everyone, active, no longer active), the
order and the page all live in the URL and are carried into every link,
so a view can be bookmarked and paging cannot change the question. It is
all server-side, so it works without JavaScript.

Sorting by a count needs every matched account's summary before the page
is cut, so the index now answers for the whole directory in one grouped
query — ActorSummaries — instead of a lookup for a page of names. That
also drops the per-request name list, so a large roster cannot run into
SQLite's bound-variable limit.

Retired accounts are marked in their row and can be filtered to rather
than living in a separate group, and a roster larger than one page is
covered by a test.

server/web/static/vole.css +10−18

@@ -2888,26 +2888,18 @@ textarea:focus-visible {
2888 2888 .profile-repo { gap: 4px; }
2889 2889 .profile-repo-meta { display: flex; flex-wrap: wrap; gap: 4px 10px; margin: 0; color: var(--fg-muted); font-size: calc(12.65px * var(--font-scale)); }
2890 2890
2891 /* One row per account. A list rather than a table: a row says what someone has
2892 been working on in a sentence, and a name is the only thing worth a column. */
2893 .people-list { list-style: none; margin: 10px 0 0; padding: 0; }
2894 .people-row {
2895 display: flex;
2896 align-items: center;
2897 gap: 11px;
2898 padding: 10px 2px;
2899 border-bottom: 1px solid var(--border);
2900 min-width: 0;
2901 }
2902 .people-row:last-child { border-bottom: 0; }
2903 .people-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
2891 /* The user directory is a table: one row per account, sortable headers, and
2892 counts right-aligned so a column can be scanned. */
2904 2893 .people-name { font-weight: 600; }
2905 .people-summary { color: var(--fg-muted); font-size: calc(12.65px * var(--font-scale)); }
2906 .people-joined { font-size: calc(12.65px * var(--font-scale)); white-space: nowrap; }
2907 .people-retired .people-name { color: var(--fg-muted); }
2908 .people-row .label { flex: 0 0 auto; }
2894 .user-scope { margin: 0 0 10px; font-size: calc(12.65px * var(--font-scale)); }
2895 .user-list th a { color: inherit; }
2896 .user-list th a:hover { color: var(--accent); text-decoration: none; }
2897 .user-list .sort-mark { margin-left: 4px; color: var(--accent); }
2898 .user-list th.count,
2899 .user-list td.count { text-align: right; font-variant-numeric: tabular-nums; }
2900 .user-list td.count { color: var(--fg-muted); }
2901 .user-list .state { margin-left: 4px; vertical-align: 1px; }
2909 2902
2910 2903 @media (max-width: 720px) {
2911 .people-joined { display: none; }
2912 2904 .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2913 2905 }