1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

fix(frontend): make child entry rows interactive

ChildRow now receives onRowClick and selectedUids from EntryRow (which
receives selectedUids from EntriesView alongside the existing booleans).
Clicking a child row invokes onRowClick(child, e) so it flows through
handleRowClick → selectEntry → fetchEntryDetail exactly as a root entry
would. Shift-range selection gracefully degrades to single-select since
child entries are not in the root entries array.

Selected/multi-selected visual state is wired: .child-entry-row.is-selected
shows the same #eee2d2 background + accent outline as root rows; hover
restores full opacity. Frontend static assets rebuilt.
This commit is contained in:
TheGeneralist 2026-07-20 16:09:42 +02:00
parent 08087a4d6b
commit 9c1d416463
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
9 changed files with 86 additions and 106 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,8 +4,8 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Archivr</title>
<script type="module" crossorigin src="/assets/index-YmIQCrug.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DLdY9nrw.css">
<script type="module" crossorigin src="/assets/index-DILm4nQE.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Ba3TFwml.css">
</head>
<body>
<div id="root"></div>

View file

@ -26,6 +26,7 @@ export default function EntriesView({ entries, selectedUids, onRowClick, archive
isSelected={selectedUids.size === 1 && selectedUids.has(entry.entry_uid)}
isMultiSelected={selectedUids.size >= 2 && selectedUids.has(entry.entry_uid)}
onRowClick={onRowClick}
selectedUids={selectedUids}
/>
))}
</div>

View file

@ -2,9 +2,24 @@ import { useState } from 'react';
import { formatTimestamp, formatBytes, valueText, sourceIconSvg } from '../utils';
import { fetchEntryChildren } from '../api';
function ChildRow({ entry }) {
function ChildRow({ entry, onRowClick, selectedUids }) {
const isSelected = (selectedUids?.size === 1) && selectedUids.has(entry.entry_uid);
const isMultiSelected = (selectedUids?.size >= 2) && selectedUids.has(entry.entry_uid);
const cls = ['child-entry-row',
isSelected && 'is-selected',
isMultiSelected && 'is-multi-selected',
].filter(Boolean).join(' ');
return (
<div className="child-entry-row">
<div
className={cls}
tabIndex={0}
data-entry-uid={entry.entry_uid}
onMouseDown={e => { if (e.shiftKey) e.preventDefault(); }}
onClick={e => onRowClick(entry, e)}
onKeyDown={e => { if (e.key === 'Enter') onRowClick(entry, e); }}
>
<div className="col-check" aria-hidden="true" />
<div className="col-added">{formatTimestamp(entry.archived_at)}</div>
<div className="col-title">
@ -24,7 +39,7 @@ function ChildRow({ entry }) {
);
}
export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected, onRowClick }) {
export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected, onRowClick, selectedUids }) {
const [favFailed, setFavFailed] = useState(false);
const [expanded, setExpanded] = useState(false);
const [children, setChildren] = useState(null);
@ -78,9 +93,6 @@ export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected
}
}
// Outer wrapper: display:block so child-entries lives below the flex row.
// One wrapper per entry nth-child striping stays correct.
// is-selected/is-multi-selected go here so #entries-body > div.is-selected still matches.
const outerClass = [
'entry-row-outer',
isSelected && 'is-selected',
@ -89,7 +101,6 @@ export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected
return (
<div className={outerClass} data-entry-uid={entry.entry_uid}>
{/* entry-row-main is the interactive flex row */}
<div
className="entry-row-main"
tabIndex={0}
@ -142,7 +153,12 @@ export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected
<div className="child-entries" aria-label={`${entry.child_count} child entries`}>
{childrenLoading && <div className="child-entries-loading">Loading</div>}
{children && children.map(child => (
<ChildRow key={child.entry_uid} entry={child} />
<ChildRow
key={child.entry_uid}
entry={child}
onRowClick={onRowClick}
selectedUids={selectedUids}
/>
))}
</div>
)}

View file

@ -2796,7 +2796,18 @@ body.has-audio-bar { padding-bottom: 56px; }
border-bottom: 1px solid var(--line-soft);
opacity: 0.88;
}
.child-entry-row:hover { opacity: 1; }
.child-entry-row:last-child { border-bottom: none; }
.child-entry-row.is-selected {
background: #eee2d2;
outline: 2px solid var(--accent);
outline-offset: -2px;
opacity: 1;
}
.child-entry-row.is-multi-selected {
background: #eee2d2;
opacity: 1;
}
.child-entry-row > div {
padding: 6px 10px;
flex-shrink: 0;