1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

Compare commits

...

3 commits

6 changed files with 87 additions and 50 deletions

View file

@ -90,8 +90,8 @@ fn save_with(
.arg(&out_file)
.arg(format!("--browser-executable-path={chrome}"))
.arg("--browser-headless")
.arg("--browser-wait-until=networkAlmostIdle")
// Extra delay after networkAlmostIdle: Cloudflare Fonts injects @font-face
.arg("--browser-wait-until=networkidle2")
// Extra delay after networkidle2: Cloudflare Fonts injects @font-face
// CSS after HTML parse, so the font hook needs more time to see it.
.arg("--browser-wait-delay=2000")
// Realistic UA: some origins block headless Chrome's default UA string.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

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

View file

@ -15,6 +15,22 @@ import ContextRail from './components/ContextRail'
export const AuthContext = createContext(null);
const VIEWS = ['archive','runs','admin','tags','collections','settings']
const SETTINGS_TABS = ['profile','tokens','instance']
function parseLocation() {
const parts = window.location.pathname.split('/').filter(Boolean)
const view = VIEWS.includes(parts[0]) ? parts[0] : 'archive'
const settingsTab = (view === 'settings' && SETTINGS_TABS.includes(parts[1])) ? parts[1] : 'profile'
return { view, settingsTab }
}
function locationPath(view, settingsTab) {
if (view === 'archive') return '/'
if (view === 'settings') return `/settings/${settingsTab}`
return `/${view}`
}
export default function App() {
const [authState, setAuthState] = useState('loading');
const [currentUser, setCurrentUser] = useState(null);
@ -36,13 +52,25 @@ export default function App() {
return () => window.removeEventListener('auth:expired', handler);
}, []);
// Sync URL state on back/forward
useEffect(() => {
const handler = () => {
const { view, settingsTab } = parseLocation()
setView(view)
setSettingsTab(settingsTab)
}
window.addEventListener('popstate', handler)
return () => window.removeEventListener('popstate', handler)
}, [])
const [archives, setArchives] = useState([])
const [archiveId, setArchiveId] = useState(null)
const [entries, setEntries] = useState([])
const [selectedEntryUid, setSelectedEntryUid] = useState(null)
const [selectedEntry, setSelectedEntry] = useState(null)
const [tagFilter, setTagFilter] = useState(null)
const [view, setView] = useState('archive')
const [view, setView] = useState(() => parseLocation().view)
const [settingsTab, setSettingsTab] = useState(() => parseLocation().settingsTab)
const [searchQuery, setSearchQuery] = useState('')
const [resultCount, setResultCount] = useState('')
const [searchBusy, setSearchBusy] = useState(false)
@ -105,10 +133,12 @@ export default function App() {
return () => clearTimeout(timer)
}, [searchQuery, archiveId]) // tagFilter handled separately below
// Tag filter change: switch to archive view, reload
// Tag filter applied: switch to archive view and reload.
// Only reset view when tagFilter is non-null; archive change alone (tagFilter=null)
// must not stomp the URL-initialised view on first load.
useEffect(() => {
if (archiveId === null) return
setView('archive')
if (tagFilter !== null) setView('archive')
loadEntries(archiveId, searchQuery, tagFilter)
}, [tagFilter, archiveId]) // intentional: searchQuery excluded to avoid double-fire
@ -123,6 +153,14 @@ export default function App() {
}
}, [archiveId])
// Sync view + settingsTab URL
useEffect(() => {
const path = locationPath(view, settingsTab)
if (window.location.pathname !== path) {
history.pushState(null, '', path)
}
}, [view, settingsTab])
const selectEntry = useCallback((entry) => {
setSelectedEntryUid(entry ? entry.entry_uid : null)
setSelectedEntry(entry)
@ -230,7 +268,7 @@ export default function App() {
<CollectionsView archiveId={archiveId} />
)}
{view === 'settings' && (
<SettingsView />
<SettingsView tab={settingsTab} onTabChange={setSettingsTab} />
)}
</div>
<ContextRail

View file

@ -8,10 +8,9 @@ import {
const ROLE_ADMIN = 4
export default function SettingsView() {
export default function SettingsView({ tab, onTabChange }) {
const { currentUser, setCurrentUser } = useContext(AuthContext) ?? {}
const isAdmin = currentUser && ((currentUser.role_bits & ROLE_ADMIN) !== 0)
const [tab, setTab] = useState('profile')
const tabs = ['profile', 'tokens', ...(isAdmin ? ['instance'] : [])]
const tabLabels = { profile: 'Profile', tokens: 'API Tokens', instance: 'Instance' }
@ -23,7 +22,7 @@ export default function SettingsView() {
{tabs.map(t => (
<button key={t}
className={`view-tab${tab === t ? ' is-active' : ''}`}
onClick={() => setTab(t)}>
onClick={() => onTabChange(t)}>
{tabLabels[t]}
</button>
))}