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

fix(frontend): guard ctrl/meta cache-miss fetch; add / search shortcut

App.jsx ctrl/meta branch: cache-miss fetchEntryDetail now captures
tok = ++jkSeqRef.current before the fetch and gates selectEntry on
tok === jkSeqRef.current — same pattern as the j/k handler — so a
slow response after a later click/navigate can't overwrite selection.

/ key: reuses the Cmd+K/Ctrl+K handler path (focus+select search input,
or pendingSearchFocus + archive view switch). Guards: editable targets
(INPUT/TEXTAREA/SELECT/contentEditable) and modifier keys are checked
before preventDefault() so typing / in inputs is untouched.
This commit is contained in:
TheGeneralist 2026-07-21 17:27:14 +02:00
parent bd90a4b77f
commit 33e51bbaef
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
3 changed files with 31 additions and 26 deletions

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Archivr</title> <title>Archivr</title>
<script type="module" crossorigin src="/assets/index-BOyLHdsN.js"></script> <script type="module" crossorigin src="/assets/index-B2Gbe8XQ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D8ic-z4p.css"> <link rel="stylesheet" crossorigin href="/assets/index-D8ic-z4p.css">
</head> </head>
<body> <body>

View file

@ -308,9 +308,9 @@ export default function App() {
if (cached) { if (cached) {
selectEntry(cached) selectEntry(cached)
} else { } else {
// Cache miss fetch from server rather than clearing the detail panel. const tok = ++jkSeqRef.current
fetchEntryDetail(archiveId, remainingUid) fetchEntryDetail(archiveId, remainingUid)
.then(det => { if (det?.summary) selectEntry(det.summary) }) .then(det => { if (tok === jkSeqRef.current && det?.summary) selectEntry(det.summary) })
.catch(() => {}) .catch(() => {})
} }
} else { } else {
@ -436,18 +436,23 @@ export default function App() {
if (current !== url) history.replaceState(null, '', url) if (current !== url) history.replaceState(null, '', url)
}, [searchQuery, tagFilter, selectedEntryUid, view]) }, [searchQuery, tagFilter, selectedEntryUid, view])
// K / Ctrl+K: focus the search input, switching to archive view first if needed. // K / Ctrl+K / /: focus the search input, switching to archive view first if needed.
useEffect(() => { useEffect(() => {
const handler = (e) => { const handler = (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') { const isSlash = e.key === '/' && !e.metaKey && !e.ctrlKey && !e.altKey
e.preventDefault() const isCmdK = (e.metaKey || e.ctrlKey) && e.key === 'k'
if (view === 'archive') { if (!isSlash && !isCmdK) return
searchInputRef.current?.focus() // Don't intercept / when already typing in an input
searchInputRef.current?.select() const tag = document.activeElement?.tagName
} else { if (isSlash && (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT')) return
pendingSearchFocus.current = true if (isSlash && document.activeElement?.isContentEditable) return
setView('archive') e.preventDefault()
} if (view === 'archive') {
searchInputRef.current?.focus()
searchInputRef.current?.select()
} else {
pendingSearchFocus.current = true
setView('archive')
} }
} }
document.addEventListener('keydown', handler) document.addEventListener('keydown', handler)