~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
54 lines · 1.9 KB Raw History
import { useCallback, useEffect, useRef, useState } from 'react';

/**
 * Path routing over the History API. The server serves this document for
 * every account path, so a reload or a shared link lands on the same screen
 * the nav does. Both the pathname and the query string are tracked, so a
 * detail view like a collection is a real, addressable page.
 */
export function useRouter() {
  const [href, setHref] = useState(
    () => window.location.pathname + window.location.search,
  );
  // Depth in this app's own history stack. 0 is the first page loaded, so Back
  // can tell a real step back (still inside the app) from one that would leave.
  const depth = useRef(window.history.state?.idx ?? 0);

  useEffect(() => {
    if (window.history.state?.idx == null) {
      window.history.replaceState({ idx: 0 }, '');
    }
    /** @param {PopStateEvent} event */
    const onPop = (event) => {
      depth.current = event.state?.idx ?? 0;
      setHref(window.location.pathname + window.location.search);
    };
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);

  const navigate = useCallback(
    /** @param {string} next */ (next) => {
      depth.current += 1;
      window.history.pushState({ idx: depth.current }, '', next);
      setHref(next);
      window.scrollTo(0, 0);
    },
    [],
  );

  // Step back to wherever the visitor came from. When they landed here directly
  // (a shared link or a reload, nothing to step back to), take the fallback so
  // Back still lands somewhere sensible instead of leaving the app.
  const back = useCallback(
    /** @param {string} [fallback] */
    (fallback) => {
      if (depth.current > 0) window.history.back();
      else if (fallback) navigate(fallback);
    },
    [navigate],
  );

  const url = new URL(href, window.location.origin);
  return { path: url.pathname, params: url.searchParams, navigate, back };
}