import { useState, useEffect, useRef } from 'react' import { fetchEntryDetail, fetchEntryTags, assignTag, removeTag, listEntryCollections, updateEntryTitle, deleteEntry } from '../api' import { formatTimestamp, formatBytes, valueText, sourceIconSvg, displayPath } from '../utils' const VIS_LABEL = { 0: 'Private', 1: 'Public', 2: 'Users only', 3: 'Public' } const ExternalIcon = () => ( ) export default function ContextRail({ archiveId, selectedEntry, onTagFilterSet, tagNodes, onTagsRefresh, onEntryTitleChange, onEntryDeleted, humanizeTags }) { const [detail, setDetail] = useState(null) const [tags, setTags] = useState([]) const [assignInput, setAssignInput] = useState('') const [entryCollections, setEntryCollections] = useState([]) const [assignError, setAssignError] = useState('') const selectSeqRef = useRef(0) const titleCancelRef = useRef(false) const [editingTitle, setEditingTitle] = useState(false) const [titleDraft, setTitleDraft] = useState('') useEffect(() => { if (!selectedEntry || !archiveId) { setDetail(null) setTags([]) setEntryCollections([]) return } setEditingTitle(false) setTitleDraft('') titleCancelRef.current = false const seq = ++selectSeqRef.current setDetail(null) setTags([]) Promise.all([ fetchEntryDetail(archiveId, selectedEntry.entry_uid), fetchEntryTags(archiveId, selectedEntry.entry_uid), listEntryCollections(archiveId, selectedEntry.entry_uid), ]).then(([det, tgs, ecs]) => { if (seq !== selectSeqRef.current) return setDetail(det) setTags(tgs) setEntryCollections(ecs) }).catch(() => {}) }, [selectedEntry, archiveId]) async function handleTitleSave() { const newTitle = titleDraft.trim() || null try { await updateEntryTitle(archiveId, selectedEntry.entry_uid, newTitle) setDetail(prev => prev ? { ...prev, summary: { ...prev.summary, title: newTitle } } : prev) onEntryTitleChange?.(selectedEntry.entry_uid, newTitle) } catch { // silently revert } finally { setEditingTitle(false) } } async function handleAssignTag() { const path = assignInput.trim() if (!path || !selectedEntry) return try { await assignTag(archiveId, selectedEntry.entry_uid, path) setAssignInput('') setAssignError('') const updated = await fetchEntryTags(archiveId, selectedEntry.entry_uid) setTags(updated) onTagsRefresh() } catch (e) { setAssignError(e.message) } } async function handleRemoveTag(tagUid) { try { await removeTag(archiveId, selectedEntry.entry_uid, tagUid) const updated = await fetchEntryTags(archiveId, selectedEntry.entry_uid) setTags(updated) onTagsRefresh() } catch { // silently ignore } } async function handleDeleteEntry() { if (!selectedEntry || !archiveId) return if (!window.confirm('Delete this entry? This cannot be undone.')) return try { await deleteEntry(archiveId, selectedEntry.entry_uid) onEntryDeleted?.(selectedEntry.entry_uid) } catch { // silently ignore — entry stays selected if delete failed } } const metaRows = detail ? [ ['Added', formatTimestamp(detail.summary.archived_at)], ['Source', detail.summary.source_kind], ['Type', detail.summary.entity_kind], ['Visibility', VIS_LABEL[detail.summary.visibility] ?? detail.summary.visibility], ['Root', detail.structured_root_relpath], ] : [] return ( ) }