BREAKING CHANGE: Unify ebook reader type system to match actual data structures Problem: - ReflowableBook type had direct properties (spine, resources, toc, metadata) - UniversalReader wraps EbookCIF in cif property with runtime state - Type mismatch caused unsafe 'as any' casts and runtime errors - Two conflicting UniversalReader definitions existed (reader-shell vs reader-context) Root Cause: - Parsers return EbookCIF (nested structure) - ReflowableBook expected flat structure - Code mixed both approaches causing confusion Changes: Type System Updates: - Replace all ReflowableBook references with UniversalReader - Remove duplicate UniversalReader definition in reader-context.ts - Import UniversalReader from canonical source (reader-shell.ts) - Update all function signatures across reflowable module Property Access Patterns: - book.cif.spine instead of book.spine - book.cif.resources instead of book.resources - book.cif.toc instead of book.toc - book.cif.metadata instead of book.metadata Fixed Modules: - reader-shell.ts: UniversalReader object construction - reader-context.ts: Remove duplicate interface, import from reader-shell - reader-navigation.ts: Remove unsafe type casts, fix property access - reader-services.ts: Align with UniversalReader structure - reflowable/navigation.ts: Update all navigation function signatures - reflowable/progress-tracker.ts: Update tracker function signatures - reflowable/parser.ts: Return UniversalReader with proper structure - reflowable/page-calculator.ts: Update calculation function signatures - reflowable/ebook/search.ts: Fix property access patterns - types/reader.d.ts: Remove duplicate type definitions Impact: - ✅ Type-safe throughout ebook reader - ✅ Matches actual data structures from parsers - ✅ No more unsafe type casts - ✅ Single source of truth for UniversalReader - ✅ Aligns with EbookCIF format from API/parsers Files changed: 10 Lines changed: +320, -180
75 lines
1.9 KiB
TypeScript
75 lines
1.9 KiB
TypeScript
import { apiPut, ReadingProgress } from "../../api";
|
|
|
|
interface ExtendedProgress {
|
|
character?: number;
|
|
chapter?: number;
|
|
percentage?: number;
|
|
}
|
|
|
|
export async function updateReadingProgress(
|
|
mediaItemId: string,
|
|
progress: ReadingProgress,
|
|
extended?: ExtendedProgress,
|
|
): Promise<void> {
|
|
if (!mediaItemId || mediaItemId === "undefined") {
|
|
console.warn("Skipping progress update - no valid mediaItemId");
|
|
return;
|
|
}
|
|
|
|
const payload: ReadingProgress = {
|
|
current_page: progress.current_page,
|
|
total_pages: progress.total_pages,
|
|
cfi: progress.cfi || "",
|
|
percentage: extended?.percentage || 0,
|
|
last_read_at: new Date().toISOString(),
|
|
};
|
|
const response = await apiPut(
|
|
`/media-items/${mediaItemId}/progress`,
|
|
payload,
|
|
);
|
|
if (!response.ok) {
|
|
const clonedResponse = response.clone();
|
|
const errorText = await clonedResponse.text();
|
|
console.warn("Progress update failed:", clonedResponse.status, errorText);
|
|
return;
|
|
}
|
|
}
|
|
|
|
export function getChapterNavigation(chapters: Chapter[]) {
|
|
return {
|
|
getNextChapter: (currentPage: number) => {
|
|
for (let i = 0; i < chapters.length - 1; i++) {
|
|
const chapter = chapters[i];
|
|
const nextChapter = chapters[i + 1];
|
|
|
|
if (
|
|
currentPage >= chapter.start_page &&
|
|
currentPage < nextChapter.start_page
|
|
) {
|
|
return nextChapter.start_page;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
getPreviousChapter: (currentPage: number) => {
|
|
for (let i = 1; i < chapters.length; i++) {
|
|
const chapter = chapters[i];
|
|
|
|
if (
|
|
currentPage >= chapter.start_page &&
|
|
currentPage < chapter.start_page + chapter.page_count
|
|
) {
|
|
return chapters[i - 1].start_page;
|
|
}
|
|
}
|
|
|
|
if (currentPage < chapters[0].start_page) {
|
|
return null;
|
|
}
|
|
|
|
return chapters[0].start_page;
|
|
},
|
|
};
|
|
}
|