mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
feat(chapters): focus the title field when adding a chapter
This commit is contained in:
1 parent
efd45cf61e
commit
dad0ac86d0
3 files changed
+40
-7
No files matched your search
@@ -37,6 +37,8 @@ export function EditorView() {
|
|||||||
const setChapterContent = useBook((s) => s.setChapterContent);
|
const setChapterContent = useBook((s) => s.setChapterContent);
|
||||||
const setChapterTitle = useBook((s) => s.setChapterTitle);
|
const setChapterTitle = useBook((s) => s.setChapterTitle);
|
||||||
const setChapterNoTitle = useBook((s) => s.setChapterNoTitle);
|
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 markSaved = useBook((s) => s.markSaved);
|
||||||
const theme = useTheme((s) => s.theme);
|
const theme = useTheme((s) => s.theme);
|
||||||
const toggleTheme = useTheme((s) => s.toggle);
|
const toggleTheme = useTheme((s) => s.toggle);
|
||||||
@@ -88,6 +90,7 @@ export function EditorView() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const focusEditorSoon = useCallback(() => {
|
const focusEditorSoon = useCallback(() => {
|
||||||
|
if (useBook.getState().pendingTitleFocus) return;
|
||||||
requestAnimationFrame(() => editorRef.current?.commands.focus(undefined, { scrollIntoView: false }));
|
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"
|
chapter.noTitle ? "No title" : kind === "body" ? "Chapter title" : kind === "part" ? "Part title (optional)" : "Page title"
|
||||||
}
|
}
|
||||||
disabled={!!chapter.noTitle}
|
disabled={!!chapter.noTitle}
|
||||||
|
focusRequest={pendingTitleFocus === chapter.id}
|
||||||
|
onFocused={clearTitleFocus}
|
||||||
onChange={(value) => setChapterTitle(chapter.id, value)}
|
onChange={(value) => setChapterTitle(chapter.id, value)}
|
||||||
/>
|
/>
|
||||||
{(chapter.noTitle || !chapter.title.trim()) && (
|
{(chapter.noTitle || !chapter.title.trim()) && (
|
||||||
@@ -495,11 +500,15 @@ function ChapterTitleInput({
|
|||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled,
|
disabled,
|
||||||
|
focusRequest,
|
||||||
|
onFocused,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
focusRequest: boolean;
|
||||||
|
onFocused: () => void;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef<HTMLTextAreaElement>(null);
|
const ref = useRef<HTMLTextAreaElement>(null);
|
||||||
@@ -511,6 +520,15 @@ function ChapterTitleInput({
|
|||||||
el.style.height = `${el.scrollHeight}px`;
|
el.style.height = `${el.scrollHeight}px`;
|
||||||
}, [value, disabled]);
|
}, [value, disabled]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!focusRequest || disabled) return;
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
el.focus();
|
||||||
|
el.select();
|
||||||
|
onFocused();
|
||||||
|
}, [focusRequest, disabled, onFocused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -260,7 +260,13 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
{group.kind === "body" && (
|
{group.kind === "body" && (
|
||||||
<button className="add-chapter" onClick={addChapter}>
|
<button
|
||||||
|
className="add-chapter"
|
||||||
|
onClick={() => {
|
||||||
|
addChapter();
|
||||||
|
onNavigate?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Icon d="M12 5v14M5 12h14" />
|
<Icon d="M12 5v14M5 12h14" />
|
||||||
New chapter
|
New chapter
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+15
-6
@@ -25,6 +25,8 @@ interface BookState {
|
|||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
exporting: string | null;
|
exporting: string | null;
|
||||||
notice: string | null;
|
notice: string | null;
|
||||||
|
pendingTitleFocus: string | null;
|
||||||
|
clearTitleFocus: () => void;
|
||||||
setExporting: (label: string | null) => void;
|
setExporting: (label: string | null) => void;
|
||||||
setNotice: (message: string | null) => void;
|
setNotice: (message: string | null) => void;
|
||||||
openBook: (book: Book) => void;
|
openBook: (book: Book) => void;
|
||||||
@@ -57,6 +59,8 @@ export const useBook = create<BookState>((set, get) => ({
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
exporting: null,
|
exporting: null,
|
||||||
notice: null,
|
notice: null,
|
||||||
|
pendingTitleFocus: null,
|
||||||
|
clearTitleFocus: () => set({ pendingTitleFocus: null }),
|
||||||
setExporting: (label) => set({ exporting: label }),
|
setExporting: (label) => set({ exporting: label }),
|
||||||
setNotice: (message) => set({ notice: message }),
|
setNotice: (message) => set({ notice: message }),
|
||||||
openBook: (book) => {
|
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 valid = saved === COVER_ID || normalized.chapters.some((c) => c.id === saved);
|
||||||
const activeChapterId = valid ? (saved as string) : normalized.chapters[0]?.id ?? "";
|
const activeChapterId = valid ? (saved as string) : normalized.chapters[0]?.id ?? "";
|
||||||
applyBookFonts(normalized.settings.fonts);
|
applyBookFonts(normalized.settings.fonts);
|
||||||
set({ book: normalized, activeChapterId, dirty: false });
|
set({ book: normalized, activeChapterId, dirty: false, pendingTitleFocus: null });
|
||||||
},
|
},
|
||||||
closeBook: () => {
|
closeBook: () => {
|
||||||
flushOutgoing(get().book, get().dirty);
|
flushOutgoing(get().book, get().dirty);
|
||||||
resetBookFonts();
|
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) => {
|
goToAdjacentChapter: (dir) => {
|
||||||
const { book, activeChapterId } = get();
|
const { book, activeChapterId } = get();
|
||||||
if (!book) return;
|
if (!book) return;
|
||||||
@@ -81,7 +85,7 @@ export const useBook = create<BookState>((set, get) => ({
|
|||||||
const i = ids.indexOf(activeChapterId);
|
const i = ids.indexOf(activeChapterId);
|
||||||
if (i === -1) return;
|
if (i === -1) return;
|
||||||
const next = ids[(i + dir + ids.length) % ids.length];
|
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) =>
|
setChapterContent: (id, content) =>
|
||||||
set((s) =>
|
set((s) =>
|
||||||
@@ -110,11 +114,16 @@ export const useBook = create<BookState>((set, get) => ({
|
|||||||
addChapter: () =>
|
addChapter: () =>
|
||||||
set((s) => {
|
set((s) => {
|
||||||
if (!s.book) return {};
|
if (!s.book) return {};
|
||||||
const chapter = createChapter();
|
const chapter = createChapter("");
|
||||||
const chapters = [...s.book.chapters];
|
const chapters = [...s.book.chapters];
|
||||||
const backCount = chapters.filter((c) => chapterKind(c) === "back").length;
|
const backCount = chapters.filter((c) => chapterKind(c) === "back").length;
|
||||||
chapters.splice(chapters.length - backCount, 0, chapter);
|
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) =>
|
addPage: (group, title) =>
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
|||||||
Reference in new issue
Block a user