feat(analysis): admin UI — reader context-menu action + dashboard section
Surfaces the scoped re-enqueue and per-page force-analyze in the UI:
- api/admin: reenqueueAnalysis({onlyUnanalyzed, mangaId, chapterId}) and
analyzePage(pageId).
- Reader PageContextMenu gains an admin-only "Queue for analysis" item
(canAnalyze/onAnalyzePage/analyzeState props) wired to the force-analyze
endpoint with inline busy/done/error feedback; reset per page.
- New /admin/analysis dashboard section + nav tab: scope selector
(whole library / one manga / one chapter), id input, and an
"include already-analyzed" toggle (default off), with busy/notice/error.
Tests: vitest for the two clients; Playwright for the context-menu action
(admin vs non-admin) and the admin section (scope + include toggle +
validation). svelte-check + build clean; 262 vitest, 10 new e2e green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import { preferences } from '$lib/preferences.svelte';
|
||||
import { updateReadProgress } from '$lib/api/read_progress';
|
||||
import { chapterLabel } from '$lib/api/chapters';
|
||||
import { resyncChapter } from '$lib/api/admin';
|
||||
import { resyncChapter, analyzePage } from '$lib/api/admin';
|
||||
import { readerFullscreen } from '$lib/reader-fullscreen.svelte';
|
||||
import { session } from '$lib/session.svelte';
|
||||
import Sheet from '$lib/components/Sheet.svelte';
|
||||
@@ -118,6 +118,8 @@
|
||||
let actionSheetOpen = $state(false);
|
||||
let collectionsModalOpen = $state(false);
|
||||
let tagsModalOpen = $state(false);
|
||||
// Admin-only "Queue for analysis" action feedback, reset per page.
|
||||
let analyzeState = $state<'idle' | 'busy' | 'done' | 'error'>('idle');
|
||||
|
||||
// Monotonically-increasing token so a slow loadPageSummary for
|
||||
// page A can't clobber a fresh load for page B. The user right-
|
||||
@@ -148,13 +150,27 @@
|
||||
activePageId = pageId;
|
||||
contextMenuAnchor = anchor;
|
||||
contextMenuOpen = true;
|
||||
analyzeState = 'idle';
|
||||
void loadPageSummary(pageId);
|
||||
}
|
||||
|
||||
async function handleAnalyzePage() {
|
||||
const pageId = activePageId;
|
||||
if (!pageId || !session.user?.is_admin || analyzeState === 'busy') return;
|
||||
analyzeState = 'busy';
|
||||
try {
|
||||
await analyzePage(pageId);
|
||||
analyzeState = 'done';
|
||||
} catch {
|
||||
analyzeState = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
function openActionSheet(pageId: string) {
|
||||
if (!session.user) return;
|
||||
activePageId = pageId;
|
||||
actionSheetOpen = true;
|
||||
analyzeState = 'idle';
|
||||
void loadPageSummary(pageId);
|
||||
}
|
||||
|
||||
@@ -1454,6 +1470,9 @@
|
||||
onCopyLink={handleCopyLink}
|
||||
collectionsCount={activePageCollectionCount}
|
||||
tags={activePageTags}
|
||||
canAnalyze={!!session.user?.is_admin}
|
||||
onAnalyzePage={handleAnalyzePage}
|
||||
{analyzeState}
|
||||
/>
|
||||
|
||||
<!-- Mobile action sheet: same two actions, larger touch targets. -->
|
||||
|
||||
Reference in New Issue
Block a user