chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
useNavigate as useRouterNavigate,
useRouterState,
} from '@tanstack/react-router';
import { useCallback } from 'react';
import { loadAccount } from '#/lib/account.js';
import { loadRepos, repoRecord, reposKey, spaceOf } from '#/lib/git.js';
/**
* The router's location and navigation, in the shape the pages take: a path
* string, and a function that goes to one.
*
* The pages build their own addresses, since a pull request's address holds
* a DID and a branch name with slashes in it, and a route pattern cannot say
* where one ends. So a link and a navigation take a path, and the router
* matches it.
*/
export function useLocation() {
const path = useRouterState({ select: (state) => state.location.pathname });
const navigate = useNavigate();
return { path, navigate };
}
/** @returns {(to: string, search?: Record<string, unknown>) => void} */
export function useNavigate() {
const navigate = useRouterNavigate();
return useCallback(
(to, search) => {
navigate(search ? { to, search } : { to });
window.scrollTo(0, 0);
},
[navigate],
);
}
/**
* The space a repository page is reading from, from its `?space=`. A private
* repository is addressed by name and space together; the space is kept on
* the address as a query parameter, so a page inside the repository carries
* it by the router's retainSearchParams middleware.
* @returns {string} - the space URI, '' for a public repository
*/
export function useSpace() {
const repo = useRouterState({
select: (state) => state.location.pathname.split('/').filter(Boolean)[0],
});
return repo ? spaceOf(decodeURIComponent(repo)) : '';
}
/**
* The fragment on the address now, and a way to set one.
*
* A fragment names a place on the page the reader is already on, so setting
* one replaces the history entry rather than adding to it, and neither the
* router nor the browser moves the page to it.
* @returns {[string, (hash: string) => void]}
*/
export function useHash() {
const hash = useRouterState({ select: (state) => state.location.hash });
const navigate = useRouterNavigate();
const setHash = useCallback(
(next) => {
navigate({
hash: next,
replace: true,
resetScroll: false,
hashScrollIntoView: false,
});
},
[navigate],
);
return [hash, setHash];
}
/**
* The repository record a route names, subscribed to the listing so the
* route re-renders when a fresh listing lands. A direct load paints from the
* last visit's rows, which may not hold a repository made since; `settled`
* says whether the fresh listing has been read, so a route can hold its
* placeholder rather than saying "no such repository" too soon.
* @param {string} repo
* @param {string} [space]
* @returns {{record: Record<string, unknown>|null, settled: boolean}}
*/
export function useRepoRecord(repo, space) {
const queryClient = useQueryClient();
const account = useQuery({
queryKey: ['account'],
queryFn: () => loadAccount(queryClient),
staleTime: Number.POSITIVE_INFINITY,
});
useQuery({
queryKey: [reposKey()],
queryFn: loadRepos,
staleTime: Number.POSITIVE_INFINITY,
});
return {
record: repoRecord(repo, space || spaceOf(repo)),
settled: account.isSuccess || account.isError,
};
}