1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 11:15:41 +02:00

feat(frontend): small dashboard revamp (#26)

Add ⌘K search focus, URL-param'd search/tag/entry, persistent selection, extensions grid

* feat(frontend): focus search input on ⌘K/Ctrl+K

Add a global keydown handler that focuses (and selects) the search
input when Meta+K or Ctrl+K is pressed. If the current view is not
'archive', switch to it first and focus the input after the next
render frame via a pendingSearchFocus ref + requestAnimationFrame.

The ⌘K hint badge in the toolbar was already present; this wires
the behaviour behind it.

* feat(frontend): persist search query and tag filter in URL params

Extend parseLocation() to read ?q= and ?tag= search parameters.
Initialise searchQuery and tagFilter from the URL on mount so that
sharing or refreshing a filtered view restores the exact same
results.

- replaceState on every q/tag change (no extra history entries)
- pushState on view navigation now preserves existing search params
- popstate handler restores q and tag on back/forward
- firstArchiveLoad ref prevents the archive-change effect from
  wiping URL-initialised filters on the first mount

* 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.

* fix(frontend): add cookies and extensions to settings URL routing

SETTINGS_TABS was missing 'cookies' and 'extensions', so navigating
to /settings/cookies or /settings/extensions silently fell back to
the profile tab. Both tabs already existed in SettingsView; they
just weren't recognised by parseLocation().

Includes rebuilt frontend static assets.

* style(frontend): ext cards in auto-fill grid

* fix(frontend): URL state regressions from Codex review

Two issues:

1. Re-selection stall when popstate lands on the same entry UID.
   The restoration effect only had [entries, selectedEntryUid] as
   deps, so setting selectedEntry=null without changing either dep
   left the rail blank. Adding selectedEntry to the dep array lets
   the null→restore cycle complete.

2. tag/entry params leaking onto non-archive URLs.
   pushState on view changes carried window.location.search verbatim,
   so /settings?tag=foo was possible; reloading it triggered the
   tagFilter effect and force-switched back to archive.
   Fix: parseLocation() only extracts tag/entry when view=archive,
   and the params-sync effect only emits them on archive too.
This commit is contained in:
TheGeneralist 2026-07-13 14:50:52 +02:00 committed by GitHub
parent 72cf7ff642
commit 39765ef893
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 187 additions and 107 deletions

View file

@ -691,7 +691,7 @@ function ExtensionsTab() {
const cookieExtEnabled = settings?.cookie_ext_enabled ?? true
return (
<div style={{ maxWidth: 560 }}>
<div>
<div className="form-section">
<h2>Extensions</h2>
<p className="form-hint" style={{ marginBottom: 20 }}>
@ -699,60 +699,62 @@ function ExtensionsTab() {
accept cookie banners, and more. Changes take effect on the next capture.
</p>
<div className="ext-card">
<div className="ext-card-header">
<div className="ext-card-info">
<span className="ext-card-name">uBlock Origin Lite</span>
<span className="ext-card-desc">
Blocks ads, trackers, and other page clutter during archiving
via Chrome&rsquo;s declarativeNetRequest API (Manifest V3).
</span>
{!extAvailable && (
<span className="ext-card-hint">
Not configured &mdash; set <code>ARCHIVR_UBLOCK_EXT</code> to the
unpacked extension directory to enable.
<div className="ext-grid">
<div className="ext-card">
<div className="ext-card-header">
<div className="ext-card-info">
<span className="ext-card-name">uBlock Origin Lite</span>
<span className="ext-card-desc">
Blocks ads, trackers, and other page clutter during archiving
via Chrome&rsquo;s declarativeNetRequest API (Manifest V3).
</span>
)}
{!extAvailable && (
<span className="ext-card-hint">
Not configured &mdash; set <code>ARCHIVR_UBLOCK_EXT</code> to the
unpacked extension directory to enable.
</span>
)}
</div>
<button
type="button"
role="switch"
aria-checked={extEnabled}
className={`ext-toggle${extEnabled ? ' ext-toggle--on' : ''}`}
onClick={() => toggleUblock(!extEnabled)}
disabled={saving}
aria-label="Toggle uBlock Origin Lite"
>
<span className="ext-toggle-knob" />
</button>
</div>
<button
type="button"
role="switch"
aria-checked={extEnabled}
className={`ext-toggle${extEnabled ? ' ext-toggle--on' : ''}`}
onClick={() => toggleUblock(!extEnabled)}
disabled={saving}
aria-label="Toggle uBlock Origin Lite"
>
<span className="ext-toggle-knob" />
</button>
</div>
</div>
<div className="ext-card">
<div className="ext-card-header">
<div className="ext-card-info">
<span className="ext-card-name">I Still Don&rsquo;t Care About Cookies</span>
<span className="ext-card-desc">
Dismiss cookie consent banners during archiving.
</span>
{!cookieExtAvailable && (
<span className="ext-card-hint">
Not configured &mdash; set <code>ARCHIVR_COOKIE_EXT</code> to the
unpacked extension directory to enable.
<div className="ext-card">
<div className="ext-card-header">
<div className="ext-card-info">
<span className="ext-card-name">I Still Don&rsquo;t Care About Cookies</span>
<span className="ext-card-desc">
Dismiss cookie consent banners during archiving.
</span>
)}
{!cookieExtAvailable && (
<span className="ext-card-hint">
Not configured &mdash; set <code>ARCHIVR_COOKIE_EXT</code> to the
unpacked extension directory to enable.
</span>
)}
</div>
<button
type="button"
role="switch"
aria-checked={cookieExtEnabled}
className={`ext-toggle${cookieExtEnabled ? ' ext-toggle--on' : ''}`}
onClick={() => toggleCookieExt(!cookieExtEnabled)}
disabled={saving}
aria-label="Toggle I Still Don't Care About Cookies"
>
<span className="ext-toggle-knob" />
</button>
</div>
<button
type="button"
role="switch"
aria-checked={cookieExtEnabled}
className={`ext-toggle${cookieExtEnabled ? ' ext-toggle--on' : ''}`}
onClick={() => toggleCookieExt(!cookieExtEnabled)}
disabled={saving}
aria-label="Toggle I Still Don't Care About Cookies"
>
<span className="ext-toggle-knob" />
</button>
</div>
</div>