1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-10-09 21:03:17 +02:00

feat: reorder child entries with a configurable role permission

- Persist sibling order for child entries (archived_entries.position):
  backfilled to the previous archived_at order, new children appended
  (initial playlist order kept; sync appends after user order).
- PUT /api/archives/:id/entries/:uid/children/order takes the full child
  UID list in one IMMEDIATE transaction (401 guest, 403 role not allowed,
  404 unknown or invisible parent, 400 unless exactly the current children).
- Reorder permission is a role mask in the auth instance settings
  (reorder_children_role_bits, default Admin|Owner). Only the Owner can
  change it, built-in or custom roles, never Guest. /api/auth/me and login
  expose can_reorder_children. Settings PATCH is now one IMMEDIATE txn.
- Main page: drag handle for mouse/trackpad on viewports >640px, up/down
  arrows otherwise (pure CSS, arrows are the fallback), Alt+Up/Down
  everywhere; optimistic with revert. Settings > Instance > Permissions
  for the Owner (read-only for admins).
- Fix: renaming a child entry now updates its row immediately.
- Remove Storybook (deps, config, stories, docs).
This commit is contained in:
TheGeneralist 2026-10-04 18:54:04 +02:00
parent 8fc6754e28
commit 699eb7f62d
27 changed files with 1300 additions and 2477 deletions

View file

@ -3062,6 +3062,56 @@ body.has-audio-bar { padding-bottom: 56px; }
.child-entry-row .col-added { padding-left: 22px; }
.child-entry-row > div:last-child { padding-right: 22px; }
/* Child reorder: drag handle or move buttons inside .col-title, never both.
The arrows are the default (touch, no pointer, phone-sized ≤640px viewports,
and any browser that can't evaluate the query below). The one media query
switches a mouse/trackpad on a wider viewport to the drag handle, so the two
cases are exact complements without range syntax. Alt+↑/↓ on a focused row
works everywhere. */
.child-reorder-controls {
display: inline-flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
transition: opacity 0.15s;
}
.child-drag-handle {
display: none;
cursor: grab;
padding: 0 3px;
font-size: 0.8em;
letter-spacing: -2px;
user-select: none;
color: var(--muted);
}
.child-drag-handle:active { cursor: grabbing; }
.child-move-btn {
width: 28px;
height: 28px;
padding: 0;
border: none;
background: none;
color: inherit;
font-size: 0.9em;
line-height: 1;
cursor: pointer;
}
.child-move-btn:disabled { opacity: 0.3; cursor: default; }
.child-move-btn:focus { outline: none; }
.child-move-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 2px; }
@media (hover: hover) and (pointer: fine) and (min-width: 641px) {
/* Dimmed until the row is hovered/focused; there is hover to reveal it. */
.child-reorder-controls { gap: 1px; opacity: 0.3; }
.child-entry-row:hover .child-reorder-controls,
.child-entry-row:focus-within .child-reorder-controls { opacity: 1; }
.child-drag-handle { display: inline; }
.child-move-btn { display: none; }
}
.child-entry-row.is-dragging { opacity: 0.4; }
.child-entry-row.is-drop-before { box-shadow: inset 0 2px 0 var(--accent); }
.child-entry-row.is-drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
.child-entries[aria-busy="true"] .child-entry-row { cursor: progress; }
/* Expand chevron button */
.entry-expand-btn {
display: inline-flex;