import { useEffect, useRef } from "react"; import type { MobileView } from "./useResponsive"; export interface NavState { folder: string; note: string | null; view: MobileView; } const same = (a: NavState, b: NavState) => a.folder === b.folder && a.note === b.note && a.view === b.view; /** * Maps the app's current location onto browser history, so the phone's back * gesture steps back through notes and notebooks instead of leaving the app. * * `apply` is called when the user navigates back and should adopt the state it * is handed. */ export function useNavigationHistory( current: NavState, isMobile: boolean, apply: (state: NavState) => void ): void { const applyRef = useRef(apply); applyRef.current = apply; const currentRef = useRef(current); currentRef.current = current; const mobileRef = useRef(isMobile); mobileRef.current = isMobile; /** What the topmost history entry represents. */ const pushed = useRef({ folder: "dashboard", note: null, view: "list" }); useEffect(() => { if (same(pushed.current, current)) return; pushed.current = { ...current }; try { window.history.pushState(pushed.current, ""); } catch { /* pushState is rate-limited in some browsers; losing an entry is not fatal */ } }, [current.folder, current.note, current.view]); useEffect(() => { const onPop = (e: PopStateEvent) => { const state = e.state as Partial | null; const cur = currentRef.current; const view = mobileRef.current ? (state?.view ?? "list") : cur.view; let next: NavState; if (state) { next = { folder: state.folder || "dashboard", note: state.note ?? null, view }; } else if (cur.note) { // Stepping back past our own entries: close the note, then the notebook. next = { folder: cur.folder, note: null, view: mobileRef.current ? "list" : cur.view }; } else if (cur.folder !== "dashboard") { next = { folder: "dashboard", note: null, view: cur.view }; } else { return; } // Record where history now stands *before* applying it. Without this the // push effect sees a change it did not make and pushes a duplicate entry, // which makes the back button bounce between two views forever. pushed.current = next; applyRef.current(next); }; window.addEventListener("popstate", onPop); return () => window.removeEventListener("popstate", onPop); }, []); }