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,10 +436,16 @@ 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
const isCmdK = (e.metaKey || e.ctrlKey) && e.key === 'k'
if (!isSlash && !isCmdK) return
// Don't intercept / when already typing in an input
const tag = document.activeElement?.tagName
if (isSlash && (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT')) return
if (isSlash && document.activeElement?.isContentEditable) return
e.preventDefault() e.preventDefault()
if (view === 'archive') { if (view === 'archive') {
searchInputRef.current?.focus() searchInputRef.current?.focus()
@ -449,7 +455,6 @@ export default function App() {
setView('archive') setView('archive')
} }
} }
}
document.addEventListener('keydown', handler) document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler) return () => document.removeEventListener('keydown', handler)
}, [view]) }, [view])