feat(editor): persist active chapter per book

Remember which chapter was open per book and restore it on reopen,
falling back to the first chapter when the saved id is no longer valid.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
This commit is contained in:
pj committed 2026-06-22 13:15:01 -04:00
1 parent c0da4c53db
commit 0bc3e36ee6
2 files changed
+33 -1

No files matched your search

+20
View File
@@ -25,3 +25,23 @@ export function savePosition(bookId: string, chapterId: string, position: Chapte
(all[bookId] ||= {})[chapterId] = position;
localStorage.setItem(KEY, JSON.stringify(all));
}
const ACTIVE_KEY = "margin-active-chapter";
function readActive(): Record<string, string> {
try {
return JSON.parse(localStorage.getItem(ACTIVE_KEY) || "{}") as Record<string, string>;
} catch {
return {};
}
}
export function loadActiveChapter(bookId: string): string | null {
return readActive()[bookId] ?? null;
}
export function saveActiveChapter(bookId: string, chapterId: string): void {
const all = readActive();
all[bookId] = chapterId;
localStorage.setItem(ACTIVE_KEY, JSON.stringify(all));
}
+13 -1
View File
@@ -12,6 +12,7 @@ import {
createPage,
normalizeBook,
} from "../model/book";
import { loadActiveChapter, saveActiveChapter } from "../editor/positions";
export const COVER_ID = "__cover__";
@@ -50,7 +51,10 @@ export const useBook = create<BookState>((set) => ({
setNotice: (message) => set({ notice: message }),
openBook: (book) => {
const normalized = normalizeBook(book);
set({ book: normalized, activeChapterId: normalized.chapters[0]?.id ?? "", dirty: false });
const saved = loadActiveChapter(normalized.id);
const valid = saved === COVER_ID || normalized.chapters.some((c) => c.id === saved);
const activeChapterId = valid ? (saved as string) : normalized.chapters[0]?.id ?? "";
set({ book: normalized, activeChapterId, dirty: false });
},
closeBook: () => set({ book: null, activeChapterId: "", dirty: false }),
setActiveChapter: (id) => set({ activeChapterId: id }),
@@ -132,3 +136,11 @@ export const useBook = create<BookState>((set) => ({
set((s) => (s.book ? { dirty: true, book: { ...s.book, cover: { ...s.book.cover, ...patch } } } : {})),
markSaved: () => set({ dirty: false }),
}));
let lastActive = "";
useBook.subscribe((s) => {
if (s.book && s.activeChapterId && s.activeChapterId !== lastActive) {
lastActive = s.activeChapterId;
saveActiveChapter(s.book.id, s.activeChapterId);
}
});