1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00

feat(frontend): persist selected entry in URL params

Extend parseLocation() to read ?entry= and initialise
selectedEntryUid from it on mount.

When entries load (or reload), a new effect checks whether
selectedEntryUid is set without a corresponding selectedEntry
object and restores it by finding the matching entry in the list.
This covers page refresh, URL sharing, and back/forward navigation.

The URL params sync effect now includes selectedEntryUid alongside
q and tag, and the popstate handler restores all three.

Also includes rebuilt frontend static assets.
This commit is contained in:
TheGeneralist 2026-07-13 14:28:38 +02:00
parent bb4e57e1a2
commit 6db480229c
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
4 changed files with 63 additions and 52 deletions

View file

@ -36,7 +36,8 @@ function parseLocation() {
const params = new URLSearchParams(window.location.search)
const q = params.get('q') ?? ''
const tag = params.get('tag') ?? null
return { view, settingsTab, q, tag }
const entry = params.get('entry') ?? null
return { view, settingsTab, q, tag, entry }
}
function locationPath(view, settingsTab) {
@ -69,11 +70,12 @@ export default function App() {
// Sync URL state on back/forward
useEffect(() => {
const handler = () => {
const { view, settingsTab, q, tag } = parseLocation()
const { view, settingsTab, q, tag, entry } = parseLocation()
setView(view)
setSettingsTab(settingsTab)
setSearchQuery(q)
setTagFilter(tag)
setSelectedEntryUid(entry)
setSelectedEntry(null)
}
window.addEventListener('popstate', handler)
@ -83,7 +85,7 @@ export default function App() {
const [archives, setArchives] = useState([])
const [archiveId, setArchiveId] = useState(null)
const [entries, setEntries] = useState([])
const [selectedEntryUid, setSelectedEntryUid] = useState(null)
const [selectedEntryUid, setSelectedEntryUid] = useState(() => parseLocation().entry)
const [selectedEntry, setSelectedEntry] = useState(null)
const [tagFilter, setTagFilter] = useState(() => parseLocation().tag)
const [view, setView] = useState(() => parseLocation().view)
@ -285,17 +287,26 @@ export default function App() {
setSelectedEntryUid(prev => prev === entryUid ? null : prev)
}, [])
// Restore selectedEntry object from selectedEntryUid when entries load.
// Handles page refresh and back/forward navigation where only the UID is known.
useEffect(() => {
if (!selectedEntryUid || selectedEntry) return
const found = entries.find(e => e.entry_uid === selectedEntryUid)
if (found) setSelectedEntry(found)
}, [entries, selectedEntryUid])
// Sync search params URL via replaceState (no new history entry).
useEffect(() => {
if (PREVIEW_ROUTE) return
const params = new URLSearchParams()
if (searchQuery) params.set('q', searchQuery)
if (tagFilter) params.set('tag', tagFilter)
if (selectedEntryUid) params.set('entry', selectedEntryUid)
const qs = params.toString()
const url = window.location.pathname + (qs ? '?' + qs : '')
const current = window.location.pathname + window.location.search
if (current !== url) history.replaceState(null, '', url)
}, [searchQuery, tagFilter])
}, [searchQuery, tagFilter, selectedEntryUid])
// K / Ctrl+K: focus the search input, switching to archive view first if needed.
useEffect(() => {