Refreshing the chapter page was landing the user back at the top, because only ?resume=1 triggered the saved-position restore. Added isPageReload() helper (checks performance navigation entry type === 'reload') and OR'd it with the resume flag. Refresh now restores to the last scroll position; drawer/list clicks still go to top as intended. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.0 KiB
TypeScript
79 lines
2.0 KiB
TypeScript
export type ReadingProgress = {
|
|
chapter: number;
|
|
page: number;
|
|
ratio: number;
|
|
};
|
|
|
|
export type StorageLike = Pick<Storage, "getItem" | "setItem">;
|
|
|
|
export function storageKey(slug: string): string {
|
|
return `sunnymh:last-read:${slug}`;
|
|
}
|
|
|
|
function clampRatio(n: unknown): number {
|
|
const v = typeof n === "number" ? n : Number(n);
|
|
if (!Number.isFinite(v)) return 0;
|
|
if (v < 0) return 0;
|
|
if (v > 1) return 1;
|
|
return v;
|
|
}
|
|
|
|
export function parseProgress(raw: string | null): ReadingProgress | null {
|
|
if (!raw) return null;
|
|
if (raw.startsWith("{")) {
|
|
try {
|
|
const parsed = JSON.parse(raw) as Partial<ReadingProgress>;
|
|
if (
|
|
typeof parsed.chapter === "number" &&
|
|
typeof parsed.page === "number" &&
|
|
parsed.chapter > 0 &&
|
|
parsed.page > 0
|
|
) {
|
|
return {
|
|
chapter: parsed.chapter,
|
|
page: parsed.page,
|
|
ratio: clampRatio(parsed.ratio),
|
|
};
|
|
}
|
|
} catch {
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
const n = Number(raw);
|
|
return Number.isFinite(n) && n > 0
|
|
? { chapter: n, page: 1, ratio: 0 }
|
|
: null;
|
|
}
|
|
|
|
export function readProgress(
|
|
slug: string,
|
|
storage: StorageLike | null = defaultStorage()
|
|
): ReadingProgress | null {
|
|
if (!storage) return null;
|
|
return parseProgress(storage.getItem(storageKey(slug)));
|
|
}
|
|
|
|
export function writeProgress(
|
|
slug: string,
|
|
progress: ReadingProgress,
|
|
storage: StorageLike | null = defaultStorage()
|
|
): void {
|
|
if (!storage) return;
|
|
storage.setItem(storageKey(slug), JSON.stringify(progress));
|
|
}
|
|
|
|
function defaultStorage(): StorageLike | null {
|
|
return typeof window === "undefined" ? null : window.localStorage;
|
|
}
|
|
|
|
export function isPageReload(): boolean {
|
|
if (typeof window === "undefined") return false;
|
|
if (typeof performance === "undefined") return false;
|
|
const entries = performance.getEntriesByType("navigation");
|
|
if (entries.length === 0) return false;
|
|
return (
|
|
(entries[0] as PerformanceNavigationTiming).type === "reload"
|
|
);
|
|
}
|