feat(chapters): focus the title field when adding a chapter

This commit is contained in:
pj committed 2026-08-10 18:36:39 +05:30
1 parent efd45cf61e
commit dad0ac86d0
3 files changed
+40 -7

No files matched your search

+18
View File
@@ -37,6 +37,8 @@ export function EditorView() {
const setChapterContent = useBook((s) => s.setChapterContent);
const setChapterTitle = useBook((s) => s.setChapterTitle);
const setChapterNoTitle = useBook((s) => s.setChapterNoTitle);
const pendingTitleFocus = useBook((s) => s.pendingTitleFocus);
const clearTitleFocus = useBook((s) => s.clearTitleFocus);
const markSaved = useBook((s) => s.markSaved);
const theme = useTheme((s) => s.theme);
const toggleTheme = useTheme((s) => s.toggle);
@@ -88,6 +90,7 @@ export function EditorView() {
}, []);
const focusEditorSoon = useCallback(() => {
if (useBook.getState().pendingTitleFocus) return;
requestAnimationFrame(() => editorRef.current?.commands.focus(undefined, { scrollIntoView: false }));
}, []);
@@ -420,6 +423,8 @@ export function EditorView() {
chapter.noTitle ? "No title" : kind === "body" ? "Chapter title" : kind === "part" ? "Part title (optional)" : "Page title"
}
disabled={!!chapter.noTitle}
focusRequest={pendingTitleFocus === chapter.id}
onFocused={clearTitleFocus}
onChange={(value) => setChapterTitle(chapter.id, value)}
/>
{(chapter.noTitle || !chapter.title.trim()) && (
@@ -495,11 +500,15 @@ function ChapterTitleInput({
value,
placeholder,
disabled,
focusRequest,
onFocused,
onChange,
}: {
value: string;
placeholder: string;
disabled: boolean;
focusRequest: boolean;
onFocused: () => void;
onChange: (value: string) => void;
}) {
const ref = useRef<HTMLTextAreaElement>(null);
@@ -511,6 +520,15 @@ function ChapterTitleInput({
el.style.height = `${el.scrollHeight}px`;
}, [value, disabled]);
useEffect(() => {
if (!focusRequest || disabled) return;
const el = ref.current;
if (!el) return;
el.focus();
el.select();
onFocused();
}, [focusRequest, disabled, onFocused]);
return (
<textarea
ref={ref}
+7 -1
View File
@@ -260,7 +260,13 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
)}
</ul>
{group.kind === "body" && (
<button className="add-chapter" onClick={addChapter}>
<button
className="add-chapter"
onClick={() => {
addChapter();
onNavigate?.();
}}
>
<Icon d="M12 5v14M5 12h14" />
New chapter
</button>
+15 -6
View File
@@ -25,6 +25,8 @@ interface BookState {
dirty: boolean;
exporting: string | null;
notice: string | null;
pendingTitleFocus: string | null;
clearTitleFocus: () => void;
setExporting: (label: string | null) => void;
setNotice: (message: string | null) => void;
openBook: (book: Book) => void;
@@ -57,6 +59,8 @@ export const useBook = create<BookState>((set, get) => ({
dirty: false,
exporting: null,
notice: null,
pendingTitleFocus: null,
clearTitleFocus: () => set({ pendingTitleFocus: null }),
setExporting: (label) => set({ exporting: label }),
setNotice: (message) => set({ notice: message }),
openBook: (book) => {
@@ -66,14 +70,14 @@ export const useBook = create<BookState>((set, get) => ({
const valid = saved === COVER_ID || normalized.chapters.some((c) => c.id === saved);
const activeChapterId = valid ? (saved as string) : normalized.chapters[0]?.id ?? "";
applyBookFonts(normalized.settings.fonts);
set({ book: normalized, activeChapterId, dirty: false });
set({ book: normalized, activeChapterId, dirty: false, pendingTitleFocus: null });
},
closeBook: () => {
flushOutgoing(get().book, get().dirty);
resetBookFonts();
set({ book: null, activeChapterId: "", dirty: false });
set({ book: null, activeChapterId: "", dirty: false, pendingTitleFocus: null });
},
setActiveChapter: (id) => set({ activeChapterId: id }),
setActiveChapter: (id) => set({ activeChapterId: id, pendingTitleFocus: null }),
goToAdjacentChapter: (dir) => {
const { book, activeChapterId } = get();
if (!book) return;
@@ -81,7 +85,7 @@ export const useBook = create<BookState>((set, get) => ({
const i = ids.indexOf(activeChapterId);
if (i === -1) return;
const next = ids[(i + dir + ids.length) % ids.length];
if (next !== activeChapterId) set({ activeChapterId: next });
if (next !== activeChapterId) set({ activeChapterId: next, pendingTitleFocus: null });
},
setChapterContent: (id, content) =>
set((s) =>
@@ -110,11 +114,16 @@ export const useBook = create<BookState>((set, get) => ({
addChapter: () =>
set((s) => {
if (!s.book) return {};
const chapter = createChapter();
const chapter = createChapter("");
const chapters = [...s.book.chapters];
const backCount = chapters.filter((c) => chapterKind(c) === "back").length;
chapters.splice(chapters.length - backCount, 0, chapter);
return { activeChapterId: chapter.id, dirty: true, book: { ...s.book, chapters } };
return {
activeChapterId: chapter.id,
pendingTitleFocus: chapter.id,
dirty: true,
book: { ...s.book, chapters },
};
}),
addPage: (group, title) =>
set((s) => {