add preloading to more places, add one part of the repo preloading
11 unmodified lines121314151617181920256 unmodified linesExpand all11 unmodified lines} from 'lucide-solid';import { A } from '@solidjs/router';import { Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js';import { createQuery, useQueryClient } from '@tanstack/solid-query';export { useQueryClient };import { resolveAvatarUrl } from '../lib/api/identity';
export const ToggleButton: Component<{ active: boolean;256 unmodified linesExpand all30 unmodified lines31323334353536373839401046 unmodified linesExpand all108710881089109010911092109310941095138 unmodified linesExpand all12341235123612371238123912401241124215 unmodified lines125812591260126112621263126412651266486 unmodified linesExpand all30 unmodified linesimport { getRepoIssueCount } from '../lib/api/issues';import { getRepoPullCount } from '../lib/api/pulls';import { formatRelativeTime, languageColor, encodePath, formatLanguagePercent, blobHref, treeHref } from '../lib/repo-utils';import { getTangledAppviewService } from '../lib/settings';import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles } from './common';import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles, usePreloader } from './common';import { repoQueryKey } from '../pages/repo/shared';
marked.setOptions({ gfm: true, breaks: false,1046 unmodified linesExpand all compact?: boolean; href?: string; noBorder?: boolean; showOwner?: boolean; repoDid?: string;}
export interface RepoStatsListProps { repoDid?: string;138 unmodified linesExpand all};
export const RepoCard: Component<RepoCardProps> = (props) => { const linkHref = () => props.href || `/${props.owner}/${props.name}`; const preload = usePreloader();
return ( <div class={clsx(15 unmodified lines class={clsx( 'truncate min-w-0 no-underline hover:underline text-black dark:text-white font-bold', props.compact && 'focus:outline-none' )} {...preload(repoQueryKey(props.owner, props.name), () => getRepo(props.owner, props.name))} > <Show when={props.showOwner !== false} fallback={props.name}> {props.owner}/{props.name} </Show>486 unmodified linesExpand all1234567891011121314151617181920212223242526272829303132333435363738import { useQueryClient } from '@tanstack/solid-query';import { usePreloader } from '../components/common';import { getRepo } from './api/repos';import { getIssue } from './api/issues';import { getPull } from './api/pulls';import { repoQueryKey, issueQueryKey, pullQueryKey } from './api/keys';
export const useIssuePreloader = () => { const queryClient = useQueryClient(); const preload = usePreloader();
return (repoLabel: string, kind: 'issue' | 'pull', uri: string, number?: number) => { const [owner, slug] = repoLabel.split('/'); const issueRef = String(number || uri.split('/').pop());
return preload( [kind, uri, 'prefetch'], async () => { const repo = await queryClient.fetchQuery({ queryKey: repoQueryKey(owner, slug), queryFn: () => getRepo(owner, slug), });
if (kind === 'issue') { await queryClient.prefetchQuery({ queryKey: issueQueryKey(repo.repoDid, issueRef), queryFn: () => getIssue(repo, issueRef), }); } else { await queryClient.prefetchQuery({ queryKey: pullQueryKey(repo.repoDid, issueRef), queryFn: () => getPull(repo, issueRef), }); } }, ); };};15 unmodified lines1617181920202122232424252627282930313233180 unmodified linesExpand all21421521621721821922022122299 unmodified lines322323324325326327328329330107 unmodified linesExpand all43843944044144244344444544692 unmodified lines539540541542538539540541542543544545546547548549543544545546547548549550551552553554555556557558559560561562563564565566178 unmodified linesExpand all7457467477487367497507517527537547557567577587597607617627637647657667677687697707717727737747757767773 unmodified lines7817827837847567857867877887897907917607927937947957967977987998005 unmodified lines80680780880977481081181281381481577781681781881982082182282382478278378482582682782882913 unmodified lines15 unmodified linesimport { A } from '@solidjs/router';import { createQuery } from '@tanstack/solid-query';import { For, Show, createEffect, createMemo, createSignal, onCleanup, type Component, type JSX } from 'solid-js';
import { Avatar, ErrorState, LoadingState, StateBadge } from '../components/common';import { Avatar, ErrorState, LoadingState, StateBadge, usePreloader } from '../components/common';import { RepoCard } from '../components/repo';import { listFollowRecords } from '../lib/api/graph';import { resolveActor } from '../lib/api/identity';import { listRepoRecords, type RepoRecord } from '../lib/api/repos';import { getRepo, listRepoRecords, type RepoRecord } from '../lib/api/repos';import { useAuth } from '../lib/auth';import { useLiveEvents, type LiveEvent } from '../lib/live-events';import { useIssuePreloader } from '../lib/preloading';import { formatRelativeTime, getErrorMessage, loadRecentRepos, loadRecentIssuesPulls } from '../lib/repo-utils';import { repoQueryKey } from '../lib/api/keys';
const SAMPLE_REPOS: Array<{ owner: string; slug: string;180 unmodified linesExpand all kind: HomeActivityKind; title: string; repoLabel: string; href: string; uri?: string; at?: string; body?: string; actorDid?: string; actorHandle?: string;99 unmodified lines
export const HomePage: Component = () => { const auth = useAuth(); const live = useLiveEvents(); const issuePreloader = useIssuePreloader(); const recentRepos = createMemo(loadRecentRepos); const recentIssuesPulls = createMemo(loadRecentIssuesPulls);
107 unmodified linesExpand all id: `live:${event.sourceRecord}:${event.receivedAt}`, ...label, repoLabel: repo.title, href: eventActivityHref(repo, event), uri: event.sourceRecord, at: event.receivedAt, actorDid: currentUserDid(), actorHandle: currentUserHandle(), };92 unmodified lines fallback={<HomeEmpty label="No recent issues or pull requests." />} > <div class="untangled-home-compact-list"> <For each={recentIssuesPulls().slice(0, 5)}> {(item) => ( <A href={item.href} class="untangled-home-repo-row"> <div class="text-sm font-semibold text-black dark:text-white truncate"> {item.title}{' '} <span class="text-gray-500 dark:text-gray-400 font-normal">#{item.uri.split('/').pop()}</span> </div> <div class="mt-2 flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 min-w-0"> <StateBadge state={item.state} kind={item.kind} /> <span class="truncate min-w-0">{item.author} · {item.repoLabel}</span> </div> </A> )} {(item) => { const preloadProps = issuePreloader(item.repoLabel, item.kind, item.uri, item.number);
return ( <A href={item.href} class="untangled-home-repo-row" {...preloadProps}> <div class="text-sm font-semibold text-black dark:text-white truncate"> {item.title}{' '} <span class="text-gray-500 dark:text-gray-400 font-normal"> #{item.uri.split('/').pop()} </span> </div> <div class="mt-2 flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 min-w-0"> <StateBadge state={item.state} kind={item.kind} /> <span class="truncate min-w-0"> {item.author} · {item.repoLabel} </span> </div> </A> ); }} </For> </div> </Show> </div>178 unmodified linesExpand all);
const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props) => { const repoUrl = () => `/${props.item.repoLabel}`; const actorUrl = () => props.item.actorHandle ? `/${props.item.actorHandle}` : '#'; const actorUrl = () => (props.item.actorHandle ? `/${props.item.actorHandle}` : '#'); const preload = usePreloader(); const issuePreloader = useIssuePreloader();
const actionText = () => { if (props.item.kind === 'repo') return 'published repo'; if (props.item.kind === 'issue') return 'created issue in'; if (props.item.kind === 'pull') return 'opened pull request in'; return 'updated'; };
const preloadProps = () => { if (props.item.kind === 'issue' || props.item.kind === 'pull') { if (props.item.uri) { return issuePreloader(props.item.repoLabel, props.item.kind, props.item.uri); } } return {}; };
const preloadRepoProps = () => { const [owner, slug] = props.item.repoLabel.split('/'); return preload(repoQueryKey(owner, slug), () => getRepo(owner, slug)); };
return ( <div class="relative"> <Show when={props.index > 0}> <div class="absolute left-8 -top-4 w-px h-4 bg-gray-300 dark:bg-gray-600" />3 unmodified lines <div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex items-center gap-2 text-sm min-w-0 overflow-hidden"> <Show when={props.item.actorDid}> <Avatar did={props.item.actorDid!} size="size-5" /> </Show> <A href={actorUrl()} class="font-bold no-underline hover:underline text-black dark:text-white truncate shrink-0 max-w-[8rem] sm:max-w-none"> <A href={actorUrl()} class="font-bold no-underline hover:underline text-black dark:text-white truncate shrink-0 max-w-[8rem] sm:max-w-none" > {props.item.actorHandle || 'user'} </A> <span class="whitespace-nowrap shrink-0">{actionText()}</span> <A href={repoUrl()} class="no-underline hover:underline font-bold text-black dark:text-white truncate min-w-0"> <A href={repoUrl()} class="no-underline hover:underline font-bold text-black dark:text-white truncate min-w-0" {...preloadRepoProps()} > {props.item.repoLabel} </A> <span class="text-gray-400 dark:text-gray-500 text-xs ml-auto pr-6 shrink-0 whitespace-nowrap"> {formatRelativeTime(props.item.at)}5 unmodified lines when={props.item.kind === 'repo'} fallback={ <div class="py-4 px-6 flex flex-col gap-1 bg-white dark:bg-gray-800"> <div class="font-medium text-black dark:text-white flex items-center"> <Show when={props.item.kind === 'pull'} fallback={<CircleDot class="w-4 h-4 mr-1.5 shrink-0 text-green-600" />}> <Show when={props.item.kind === 'pull'} fallback={<CircleDot class="w-4 h-4 mr-1.5 shrink-0 text-green-600" />} > <GitPullRequest class="w-4 h-4 mr-1.5 shrink-0 text-purple-600" /> </Show> <A href={props.item.href} class="no-underline hover:underline font-medium text-black dark:text-white text-base"> <A href={props.item.href} class="no-underline hover:underline font-medium text-black dark:text-white text-base" {...preloadProps()} > {props.item.title} </A> </div> <Show when={props.item.body}> <div class="text-gray-600 dark:text-gray-300 text-sm line-clamp-2 mt-1"> {props.item.body} </div> <div class="text-gray-600 dark:text-gray-300 text-sm line-clamp-2 mt-1">{props.item.body}</div> </Show> </div> } >13 unmodified lines1509 unmodified linesExpand all151015111512151315141515151615171518147 unmodified linesExpand all1509 unmodified linesExpand all {(repo) => ( <RepoCard owner={repo.owner.handle} name={repo.slug} repoDid={repo.repoDid} description={repo.record.value.description} isFork={Boolean(repo.record.value.source)} showOwner={true} />147 unmodified linesExpand all19 unmodified lines20212223242425262627282930308 unmodified linesExpand all339340341342343343344345346347348349350351304 unmodified linesExpand all19 unmodified linesimport { ErrorState, LoadingState, PaginationControls, usePreloader } from '../components/common';import { RepoStatsList } from '../components/repo';import { getIssueRecord, getIssue } from '../lib/api/issues';import { getPullRecord, getPull } from '../lib/api/pulls';import { getRepoByDid } from '../lib/api/repos';import { getRepoByDid, getRepo } from '../lib/api/repos';import { getString } from '../lib/api/strings';import { issueQueryKey, pullQueryKey } from './repo/shared';import { issueQueryKey, pullQueryKey, repoQueryKey } from './repo/shared';import { ISSUE_COLLECTION,} from '../lib/api/constants';import {308 unmodified linesExpand all <div class="flex items-center min-w-0 flex-1 mr-2"> <SearchResultIcon hit={props.hit} /> <Switch fallback={<span class="truncate min-w-0">{title()}</span>}> <Match when={props.hit.nsid === 'sh.tangled.repo'}> <A href={`/${props.hit.author.handle}/${repoName()}`} class="truncate min-w-0"> <A href={`/${props.hit.author.handle}/${repoName()}`} class="truncate min-w-0" {...preload(repoQueryKey(props.hit.author.handle, repoName()), () => getRepo(props.hit.author.handle, repoName()))} > {props.hit.author.handle}/{repoName()} </A> </Match> <Match when={props.hit.nsid === 'sh.tangled.repo.issue'}>304 unmodified linesExpand all12345export const repoQueryKey = (owner: string, repo: string) => ['repo', owner, repo] as const;export const issuesQueryKey = (repoDid: string) => ['issues', repoDid] as const;export const issueQueryKey = (repoDid: string, issueRef: string) => ['issue', repoDid, issueRef] as const;export const pullsQueryKey = (repoDid: string) => ['pulls', repoDid] as const;export const pullQueryKey = (repoDid: string, pullRef: string) => ['pull', repoDid, pullRef] as const;314 unmodified linesExpand all315316317318319319320321322323324324325326327328329330331332333334491 unmodified linesExpand all314 unmodified linesExpand allexport const getRepo = async (ownerIdentifier: string, repoSlug: string): Promise<RepoContext> => { const owner = await resolveActor(ownerIdentifier); const all = await listRepoRecords(owner); const wanted = repoSlug.trim().toLowerCase(); const match = all.find((record) => { const matches = all.filter((record) => { const name = record.value.name?.trim().toLowerCase(); return name === wanted || record.rkey.toLowerCase() === wanted; });
if (!match) { if (matches.length === 0) { throw new Error(`Repository not found: ${ownerIdentifier}/${repoSlug}`); }
// Prefer a record that doesn't use a localhost knot if multiple match const match = matches.find((r) => !r.value.knot?.includes('localhost')) || matches[0];
if (!match.value.repoDid) { throw new Error(`Repository is missing repoDid metadata`); }
491 unmodified linesExpand all12 unmodified lines13141516171818192021221920212223471 unmodified linesExpand all12 unmodified linesimport { useLiveEvents } from '../../lib/live-events';import { Avatar, ErrorState, cardStyles } from '../../components/common';import { RepoFrameSkeleton, RepoTabLink } from '../../components/repo';import { getErrorMessage, saveRecentRepo, trimUri } from '../../lib/repo-utils';import { repoQueryKey, issuesQueryKey, issueQueryKey, pullsQueryKey, pullQueryKey } from '../../lib/api/keys';
export const repoQueryKey = (owner: string, repo: string) => ['repo', owner, repo] as const;export const issuesQueryKey = (repoDid: string) => ['issues', repoDid] as const;export const issueQueryKey = (repoDid: string, issueRef: string) => ['issue', repoDid, issueRef] as const;export const pullsQueryKey = (repoDid: string) => ['pulls', repoDid] as const;export const pullQueryKey = (repoDid: string, pullRef: string) => ['pull', repoDid, pullRef] as const;export { repoQueryKey, issuesQueryKey, issueQueryKey, pullsQueryKey, pullQueryKey };
type OptimisticStarState = { state: 'starred'; rkey?: string } | { state: 'unstarred'; rkey: string };
const createRepoQuery = () => {471 unmodified linesExpand all