add permalink and highlighting to comments, add missing skeleton loading anims on profiles
3618 unmodified linesExpand all3619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693618 unmodified linesExpand all background-color: rgb(55 65 81 / 0.5) !important; /* bg-gray-700/50 */ }}
.untangled-comment-highlight { position: relative; background-color: rgb(254 240 138 / 0.15) !important; border-radius: 0;}
.untangled-comment-highlight::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 3px; background-color: #eab308; border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; z-index: 1;}
/* Align solid bar with the vertical replies thread line */.-ml-4.untangled-comment-highlight::before,.-ml-4 > .untangled-comment-highlight::before { left: calc(1rem - 2px); border-radius: 0;}
.-ml-4.untangled-comment-highlight,.-ml-4 > .untangled-comment-highlight { background-color: transparent !important; background-image: linear-gradient(to right, transparent calc(1rem - 2px), rgb(254 240 138 / 0.15) calc(1rem - 2px)) !important;}
@media (prefers-color-scheme: dark) { .untangled-comment-highlight { background-color: rgb(161 98 7 / 0.1) !important; } .untangled-comment-highlight::before { background-color: #ca8a04; } .-ml-4.untangled-comment-highlight, .-ml-4 > .untangled-comment-highlight { background-color: transparent !important; background-image: linear-gradient(to right, transparent calc(1rem - 2px), rgb(161 98 7 / 0.1) calc(1rem - 2px)) !important; }}
2090 unmodified linesExpand all20912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242090 unmodified linesExpand all </div> );};
export const RepoCardSkeleton: Component<{ compact?: boolean; noBorder?: boolean }> = (props) => { return ( <div class={clsx( props.compact ? 'focus-within:bg-gray-100 dark:focus-within:bg-gray-800/80' : 'min-h-32', 'py-4 px-6 gap-1 flex flex-col drop-shadow-sm bg-white dark:bg-gray-800', !props.noBorder && 'border border-gray-200 dark:border-gray-700 rounded-sm' )} > <div class="font-medium dark:text-white flex items-center justify-between"> <div class="flex items-center min-w-0 flex-1 mr-2 gap-2"> <SkeletonBlock class="w-4 h-4 shrink-0 rounded-sm" /> <SkeletonBlock class="h-5 w-1/3" /> </div> </div>
<div class="space-y-2 mt-2"> <SkeletonBlock class="h-4 w-5/6" /> <SkeletonBlock class="h-4 w-2/3" /> </div>
<div class="flex items-center gap-4 mt-3"> <SkeletonBlock class="h-4 w-12" /> <SkeletonBlock class="h-4 w-12" /> </div> </div> );};
20 unmodified lines21222324252625262728293016 unmodified lines474849505152535455545556575859606117 unmodified lines798081828182838485868788899027 unmodified lines118119120121122123124125126127128129142 unmodified linesExpand all2722732742752762682692772782792802812822832842852862772872882892902912922932942952812962972982993003013023033043054 unmodified lines3103113123133143153163173183193203213222942953233243253263273283293303313023323333343353363373383393403063413423433443453463473483493503113123133143153513523533543553563573583593603613623633643653663673683693703713173723733193203213223233243253263273283293303313743753763773783793803813823833843853863873333343353363373383393403413423433443453463473483493503513523533543883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384393643654404414424434444454464474484494504514523673684534544554563713723733743753763773783793803813823833843853864574584594604614624634644654664674684693883894704714723913923933943953964734744754764774784793983994004014024804044054814824834844854864 unmodified lines491492493494495418419420421422423424425426427428429430431432433434496497498499500501502503504505506507508509510511512513514515516517518519436437520521522439440441442443444445446447448449450451452453523524525526527528529530531532533534535536289 unmodified linesExpand all826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856403 unmodified linesExpand all126012611262126311621163116411651264126512661267126821 unmodified lines1290129112921293119512941295129612971298129913001301130213031304130525 unmodified lines13311332133313341335133613371338133913401341134217 unmodified lines136013611362136312591260136413651366126212631264136713681369137013711372137313741375137613771378137913801381126712681269127012711272127312741275127613821383138413851386138713881389139013911392139313941395139642 unmodified lines143914401441144213271328144314441445133013311332144614471448144914501451145214531454145514561457145813351336133713381339134013411342134313441459146014611462146314641465146614671468146914701471147214731 unmodified line147514761477147813541479148014811482148314841485148614871488148923 unmodified lines151315141515151613861517151815191520152115221523152415251526152722 unmodified lines155015511552155314171554155515561557155815591560156115621563156420 unmodified lines15851586158715881446158915901591159215931 unmodified line20 unmodified linesimport { A, useParams, useSearchParams, useNavigate } from '@solidjs/router';import { createQuery, useQueryClient } from '@tanstack/solid-query';import { For, Show, Switch, Match, createMemo, createSignal, createEffect, type Component } from 'solid-js';import type { Did } from '@atcute/lexicons/syntax';import { ErrorState, LoadingState, PlaceholderAvatar, textareaStyles, inputStyles } from '../components/common';import { RepoCard } from '../components/repo';import { ErrorState, LoadingState, PlaceholderAvatar, SkeletonBlock, textareaStyles, inputStyles } from '../components/common';import { RepoCard, RepoCardSkeleton } from '../components/repo';import { listFollowRecords, createFollow, deleteFollow,16 unmodified linesconst ProfileTabs: Component<{ activeTab: string; reposCount: number; starsCount: number; reposLoading?: boolean; starsLoading?: boolean;}> = (props) => { const tabs = [ { id: 'overview', label: 'overview', icon: () => <GalleryVertical class="w-4 h-4 mr-2" /> }, { id: 'repos', label: 'repos', icon: () => <BookMarked class="w-4 h-4 mr-2" />, count: () => props.reposCount }, { id: 'starred', label: 'starred', icon: () => <Star class="w-4 h-4 mr-2" />, count: () => props.starsCount }, { id: 'repos', label: 'repos', icon: () => <BookMarked class="w-4 h-4 mr-2" />, count: () => props.reposCount, loading: () => props.reposLoading }, { id: 'starred', label: 'starred', icon: () => <Star class="w-4 h-4 mr-2" />, count: () => props.starsCount, loading: () => props.starsLoading }, ];
return ( <nav class="w-full pl-4 overflow-x-auto overflow-y-hidden">17 unmodified lines <span class="flex items-center justify-center"> {tab.icon()} {tab.label} {tab.count !== undefined && ( <span class="bg-gray-200 dark:bg-gray-700 rounded py-0.5 px-1 text-sm ml-1"> {tab.count()} <span class="bg-gray-200 dark:bg-gray-700 rounded py-0.5 px-1 text-sm ml-1 min-w-[20px] inline-flex items-center justify-center"> <Show when={!tab.loading?.()} fallback={<SkeletonBlock class="h-3 w-4" />}> {tab.count()} </Show> </span> )} </span> </div>27 unmodified lines closedPRCount: number; openPRCount: number; openIssueCount: number; closedIssueCount: number; profileLoading?: boolean; followersLoading?: boolean; followingLoading?: boolean; vouchLoading?: boolean;}> = (props) => { const userIdent = () => props.actor.handle || props.actor.did; const pronouns = () => props.profile?.pronouns;142 unmodified linesExpand all <div class="flex items-center flex-row flex-nowrap gap-2"> <p title={userIdent()} class="text-lg font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap"> {userIdent()} </p> <Show when={!props.profileLoading} fallback={<SkeletonBlock class="h-4 w-12" />}> <Show when={pronouns()}> <p class="text-gray-500 dark:text-gray-400">{pronouns()}</p> <Show when={pronouns()}> <p class="text-gray-500 dark:text-gray-400">{pronouns()}</p> </Show> </Show> </div>
<div class="md:hidden"> <div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm"> <span class="flex-shrink-0"><Users class="size-4" /></span> <span id="followers"> <A href="?tab=followers">{props.followersCount} followers</A> <A href="?tab=followers"> <Show when={!props.followersLoading} fallback={<SkeletonBlock class="h-4 w-8 inline-block mr-1 align-middle" />}> {props.followersCount} </Show> {' '}followers </A> </span> <span class="select-none after:content-['·']"></span> <span id="following"> <A href="?tab=following">{props.followingCount} following</A> <A href="?tab=following"> <Show when={!props.followingLoading} fallback={<SkeletonBlock class="h-4 w-8 inline-block mr-1 align-middle" />}> {props.followingCount} </Show> {' '}following </A> </span> </div> </div> </div>4 unmodified lines <Show when={isEditing()} fallback={ <> <Show when={!props.profileLoading} fallback={ <div class="space-y-2 pb-4 md:pb-2"> <SkeletonBlock class="h-4 w-full" /> <SkeletonBlock class="h-4 w-5/6" /> </div> } > <Show when={description()}> <p class="text-base pb-4 md:pb-2">{description()}</p> <Show when={description()}> <p class="text-base pb-4 md:pb-2">{description()}</p> </Show> </Show>
<div class="hidden md:block"> <div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm"> <span class="flex-shrink-0"><Users class="size-4" /></span> <span id="followers"> <A href="?tab=followers">{props.followersCount} followers</A> <A href="?tab=followers"> <Show when={!props.followersLoading} fallback={<SkeletonBlock class="h-4 w-8 inline-block mr-1 align-middle" />}> {props.followersCount} </Show> {' '}followers </A> </span> <span class="select-none after:content-['·']"></span> <span id="following"> <A href="?tab=following">{props.followingCount} following</A> <A href="?tab=following"> <Show when={!props.followingLoading} fallback={<SkeletonBlock class="h-4 w-8 inline-block mr-1 align-middle" />}> {props.followingCount} </Show> {' '}following </A> </span> </div> </div>
<div class="flex flex-col gap-2 mb-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full"> <Show when={location()}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"><MapPin class="size-4" /></span> <span>{location()}</span> <Show when={!props.profileLoading} fallback={ <div class="flex flex-col gap-2 mb-2"> <div class="flex items-center gap-2"> <SkeletonBlock class="size-4 shrink-0 rounded-full" /> <SkeletonBlock class="h-4 w-24" /> </div> <div class="flex items-center gap-2"> <SkeletonBlock class="size-4 shrink-0 rounded-full" /> <SkeletonBlock class="h-4 w-36" /> </div> </div> } > <div class="flex flex-col gap-2 mb-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full"> <Show when={location()}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"><MapPin class="size-4" /></span> <span>{location()}</span> </div> </Show> </Show>
<Show when={includeBluesky()}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"> <svg class="w-4 h-4 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="-3 -3 30 30"> <title>Bluesky</title> <path fill="none" stroke="currentColor" d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" stroke-width="2.25"/> </svg> </span> <a id="bluesky-link" href={`https://bsky.app/profile/${props.actor.did}`} target="_blank" rel="noopener noreferrer"> {props.actor.handle} </a> </div> </Show> <Show when={includeBluesky()}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"> <svg class="w-4 h-4 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="-3 -3 30 30"> <title>Bluesky</title> <path fill="none" stroke="currentColor" d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" stroke-width="2.25"/> </svg> </span> <a id="bluesky-link" href={`https://bsky.app/profile/${props.actor.did}`} target="_blank" rel="noopener noreferrer"> {props.actor.handle} </a> </div> </Show>
<For each={websiteLinks()}> {(link) => ( <Show when={link}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"><Link class="size-4" /></span> <a rel="nofollow me" href={link} target="_blank" class="truncate"> {link} </a> </div> </Show> )} </For> </div>
<Show when={props.profile?.stats && props.profile.stats.length > 0}> <div class="flex items-center justify-evenly gap-2 py-2"> <For each={props.profile.stats}> {(statKind) => ( <Show when={statKind.length > 0}> <div class="flex flex-col items-center gap-2"> <span class="text-xl font-bold">{getStatValue(statKind)}</span> <span>{formatStatLabel(statKind)}</span> <For each={websiteLinks()}> {(link) => ( <Show when={link}> <div class="flex items-center gap-2"> <span class="flex-shrink-0"><Link class="size-4" /></span> <a rel="nofollow me" href={link} target="_blank" class="truncate"> {link} </a> </div> </Show> )} </For> </div> </Show>
<Show when={!props.profileLoading} fallback={ <div class="flex items-center justify-evenly gap-2 py-2"> <div class="flex flex-col items-center gap-2"> <SkeletonBlock class="h-6 w-8" /> <SkeletonBlock class="h-4 w-12" /> </div> <div class="flex flex-col items-center gap-2"> <SkeletonBlock class="h-6 w-8" /> <SkeletonBlock class="h-4 w-12" /> </div> </div> } > <Show when={props.profile?.stats && props.profile.stats.length > 0}> <div class="flex items-center justify-evenly gap-2 py-2"> <For each={props.profile.stats}> {(statKind) => ( <Show when={statKind.length > 0}> <div class="flex flex-col items-center gap-2"> <span class="text-xl font-bold">{getStatValue(statKind)}</span> <span>{formatStatLabel(statKind)}</span> </div> </Show> )} </For> </div> </Show> </Show>
<div class="flex my-2 items-center gap-2"> <Show when={!props.profileLoading} fallback={<SkeletonBlock class="h-8 w-full rounded" />} > <Show when={!isSelf()} fallback={ when={!isSelf()} fallback={ <button type="button" onClick={startEditing} class="btn w-full flex gap-2 items-center justify-center group text-sm" > <Pencil class="size-4 inline" /> edit </button> } > <button type="button" onClick={startEditing} onClick={handleFollowClick} disabled={props.actionLoading} class="btn w-full flex gap-2 items-center justify-center group text-sm" > <Pencil class="size-4 inline" /> edit </button> } > <button onClick={handleFollowClick} disabled={props.actionLoading} class="btn w-full flex gap-2 items-center justify-center group text-sm" > <Show when={props.actionLoading} fallback={ <Show when={props.isFollowing} fallback={ <Show when={props.actionLoading} fallback={ <Show when={props.isFollowing} fallback={ <> <UserRoundPlus class="size-4 inline" /> follow </> } > <> <UserRoundPlus class="size-4 inline" /> follow <UserRoundMinus class="size-4 inline" /> unfollow </> } > <> <UserRoundMinus class="size-4 inline" /> unfollow </> </Show> } > <LoaderCircle class="size-4 animate-spin inline" /> <Show when={props.isFollowing} fallback="follow"> unfollow </Show> } > <LoaderCircle class="size-4 animate-spin inline" /> <Show when={props.isFollowing} fallback="follow"> unfollow </Show> </Show> </button> </button> </Show> </Show>
<a class="btn text-sm no-underline hover:no-underline flex items-center gap-2 group"4 unmodified lines </div>
<Show when={!isSelf()}> <div class="relative w-full mt-2"> <Show when={!props.profileLoading && !props.vouchLoading} fallback={<SkeletonBlock class="h-8 w-full rounded" />}> <button id={`vouch-btn-${popoverId()}`} type="button" popovertarget={popoverId()} popovertargetaction="toggle" onClick={handleVouchClick} class={clsx( 'w-full flex gap-2 items-center justify-center text-sm', isVouched() ? 'btn-create' : isDenounced() ? 'btn-cancel' : 'btn' )} > <Show when={isVouched()} fallback={ <Show when={isDenounced()} fallback={ <button id={`vouch-btn-${popoverId()}`} type="button" popovertarget={popoverId()} popovertargetaction="toggle" onClick={handleVouchClick} class={clsx( 'w-full flex gap-2 items-center justify-center text-sm', isVouched() ? 'btn-create' : isDenounced() ? 'btn-cancel' : 'btn' )} > <Show when={isVouched()} fallback={ <Show when={isDenounced()} fallback={ <> <Shield class="size-4" /> vouch </> } > <> <Shield class="size-4" /> vouch <ShieldAlert class="size-4" /> denounced </> } > <> <ShieldAlert class="size-4" /> denounced </> </Show> } > <> <ShieldCheck class="size-4" /> vouched </> </Show> </button> </Show> } > <> <ShieldCheck class="size-4" /> vouched </> </Show> </button> </Show>
<div id={popoverId()} popover="auto"289 unmodified linesExpand all </div> );};
export const FollowCardSkeleton: Component = () => ( <div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700"> <div class="py-4 px-6 drop-shadow-sm bg-white dark:bg-gray-800 flex items-center gap-4"> <div class="flex-shrink-0 w-24 h-24"> <SkeletonBlock class="w-24 h-24 rounded-full p-2" /> </div> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full min-w-0"> <div class="flex-1 min-h-0 justify-around flex flex-col space-y-2"> <SkeletonBlock class="h-5 w-1/3" /> <SkeletonBlock class="h-4 w-2/3" /> <div class="flex items-center gap-2"> <SkeletonBlock class="size-4 shrink-0 rounded-full" /> <SkeletonBlock class="h-4 w-24" /> </div> </div> <div class="w-full md:w-auto md:max-w-24 order-last md:order-none"> <SkeletonBlock class="h-8 w-24 rounded" /> </div> </div> </div> </div>);
const FollowCard: Component<{ item: FollowerFollowingItem; loggedInUserDid?: string; viewerFollows?: FollowRecord[];403 unmodified linesExpand all
<Match when={actorQuery.data}> {(resolvedActor) => ( <Switch> <Match when={profileQuery.isLoading}> <LoadingState label="Loading profile data..." /> </Match> <Match when={!profileQuery.data}> <Match when={!profileQuery.isLoading && !profileQuery.data}> {(() => { const unresolvedAvatarQuery = createQuery(() => ({ queryKey: ['avatar', resolvedActor().did], queryFn: async () => resolveAvatarUrl(resolvedActor().did),21 unmodified lines </section> ); })()} </Match> <Match when={profileQuery.data}> <Match when={profileQuery.data || profileQuery.isLoading}> <div class="flex flex-col"> <ProfileTabs activeTab={activeTab()} reposCount={reposQuery.data?.length ?? 0} starsCount={starsQuery.data?.length ?? 0} reposLoading={reposQuery.isLoading} starsLoading={starsQuery.isLoading} />
<section class="bg-white dark:bg-gray-800 px-2 py-6 md:p-6 rounded w-full dark:text-white shadow-sm"> <div class="grid grid-cols-1 md:grid-cols-11 gap-4">25 unmodified lines closedPRCount={closedPRCount()} openPRCount={openPRCount()} openIssueCount={openIssueCount()} closedIssueCount={closedIssueCount()} profileLoading={profileQuery.isLoading} followersLoading={followersQuery.isLoading} followingLoading={followingQuery.isLoading} vouchLoading={vouchQuery.isLoading} />
<Show when={error()}> <div class="mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-sm text-red-600 dark:text-red-400">17 unmodified lines <span>pinned repos</span> </A> </div> <div id="repos" class="grid grid-cols-1 gap-4 items-stretch"> <For each={pinnedRepos()} <Show when={!reposQuery.isLoading} fallback={ <div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded"> <span>This user does not have any pinned repos.</span> </div> <> <RepoCardSkeleton /> <RepoCardSkeleton /> <RepoCardSkeleton /> </> } > <For each={pinnedRepos()} fallback={ <div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded"> <span>This user does not have any pinned repos.</span> </div> } > {(repo) => ( <RepoCard owner={resolvedActor().handle} name={repo.value.name || repo.rkey} description={repo.value.description} isFork={Boolean(repo.value.source)} showOwner={false} /> )} </For> {(repo) => ( <RepoCard owner={resolvedActor().handle} name={repo.value.name || repo.rkey} description={repo.value.description} isFork={Boolean(repo.value.source)} showOwner={false} /> )} </For> </Show> </div> </div> </div> </div>42 unmodified lines </form> </div>
<div id="repos" class="grid grid-cols-1 gap-4 mb-6"> <For each={filteredRepos()} <Show when={!reposQuery.isLoading} fallback={ <div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded"> <span>This user does not have any repos yet.</span> </div> <For each={Array.from({ length: 5 })}> {() => <RepoCardSkeleton />} </For> } > <For each={filteredRepos()} fallback={ <div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded"> <span>This user does not have any repos yet.</span> </div> } > {(repo) => ( <RepoCard owner={resolvedActor().handle} name={repo.value.name || repo.rkey} description={repo.value.description} isFork={Boolean(repo.value.source)} showOwner={false} /> )} </For> {(repo) => ( <RepoCard owner={resolvedActor().handle} name={repo.value.name || repo.rkey} description={repo.value.description} isFork={Boolean(repo.value.source)} showOwner={false} /> )} </For> </Show> </div> </div> </Match>
1 unmodified line <Match when={activeTab() === 'starred'}> <div id="all-repos" class="md:col-span-8 order-2 md:order-2"> <Show when={!starsQuery.isLoading} fallback={<LoadingState label="Loading starred repos..." />} fallback={ <div id="repos" class="grid grid-cols-1 gap-4 mb-6"> <For each={Array.from({ length: 4 })}> {() => <RepoCardSkeleton />} </For> </div> } > <div id="repos" class="grid grid-cols-1 gap-4 mb-6"> <For each={starsQuery.data ?? []}23 unmodified lines <div id="all-followers" class="md:col-span-8 order-2 md:order-2"> <p class="text-base font-bold uppercase tracking-wider p-2 dark:text-white">all followers</p> <Show when={!followersQuery.isLoading} fallback={<LoadingState label="Loading followers..." />} fallback={ <div id="followers" class="grid grid-cols-1 gap-4 mb-6"> <For each={Array.from({ length: 3 })}> {() => <FollowCardSkeleton />} </For> </div> } > <div id="followers" class="grid grid-cols-1 gap-4 mb-6"> <For each={followersQuery.data ?? []}22 unmodified lines <div id="all-following" class="md:col-span-8 order-2 md:order-2"> <p class="text-base font-bold uppercase tracking-wider p-2 dark:text-white">all following</p> <Show when={!followingQuery.isLoading} fallback={<LoadingState label="Loading following list..." />} fallback={ <div id="following" class="grid grid-cols-1 gap-4 mb-6"> <For each={Array.from({ length: 3 })}> {() => <FollowCardSkeleton />} </For> </div> } > <div id="following" class="grid grid-cols-1 gap-4 mb-6"> <For each={followingQuery.data ?? []}20 unmodified lines </div> </section> </div> </Match> </Switch> </Switch> )} </Match> </Switch> );1 unmodified line172 unmodified linesExpand all1731741751761771781791801813 unmodified lines18518618718818818919019119219319446 unmodified lines241242243244243245246247248249376 unmodified linesExpand all172 unmodified linesExpand allconst RepoThreadLink: Component<{ kind: 'issue' | 'pull'; repoDid: Did; threadRef: string; commentRef?: string; children: JSX.Element;}> = (props) => { const repoQuery = createQuery(() => ({ queryKey: ['repo-by-did', props.repoDid],3 unmodified lines const href = createMemo(() => { const repo = repoQuery.data; if (!repo) return undefined; const fn = props.kind === 'issue' ? issueHref : pullHref; return fn(repo, props.threadRef); const baseHref = fn(repo, props.threadRef); return props.commentRef ? `${baseHref}#comment-${props.commentRef}` : baseHref; });
return ( <Show when={href()} fallback={<span>{props.children}</span>}>46 unmodified lines const repoDid = createMemo(() => parent().kind === 'issue' ? (parent().record.value as { repo: Did }).repo : (parent().record.value as { target: { repo: Did } }).target.repo); return ( <RepoThreadLink kind={parent().kind} repoDid={repoDid()} threadRef={parent().record.rkey}> <RepoThreadLink kind={parent().kind} repoDid={repoDid()} threadRef={parent().record.rkey} commentRef={props.hit.rkey}> comment on {parent().kind} </RepoThreadLink> ); }}376 unmodified linesExpand all12334567293 unmodified linesExpand all30130230330430530630730830924 unmodified lines334335336337338339340341342343344345346347348349350351352353354355356357358359174 unmodified linesExpand all5345355365375195385395405415422 unmodified lines5455465475485305495505515525531 unmodified line5555565575585405595605615625632 unmodified lines566567568569551570571572573574146 unmodified linesExpand allimport clsx from 'clsx';import { Ban, CirclePlus, LoaderCircle, MessageSquarePlus, Pencil, RefreshCcwDot, Reply, Search, Smile, X } from 'lucide-solid';import { A, useNavigate, useParams, useSearchParams } from '@solidjs/router';import { A, useNavigate, useParams, useSearchParams, useLocation } from '@solidjs/router';import { createQuery, useQueryClient } from '@tanstack/solid-query';import type { ResourceUri } from '@atcute/lexicons/syntax';import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js';import { createIssue, createIssueComment, getIssue, listIssuesPage, setIssueState, type IssueComment } from '../../lib/api/issues';293 unmodified linesExpand all const auth = useAuth(); const repoQuery = useRepoQuery(); const params = useParams(); const client = useQueryClient(); const location = useLocation(); const issueRef = createMemo(() => params.issueRef ?? ''); const [comment, setComment] = createSignal(''); const [replyingTo, setReplyingTo] = createSignal<IssueComment | null>(null); const [replyBody, setReplyBody] = createSignal('');24 unmodified lines }); } });
createEffect(() => { const hash = location.hash; const cleanHash = hash.startsWith('#') ? hash.slice(1) : hash; if (cleanHash && cleanHash.startsWith('comment-') && issueQuery.data?.comments) { setTimeout(() => { const element = document.getElementById(cleanHash); if (element) { // Remove highlight from any other elements first document.querySelectorAll('.untangled-comment-highlight').forEach((el) => { el.classList.remove('untangled-comment-highlight'); }); element.scrollIntoView({ behavior: 'smooth', block: 'center' }); element.classList.add('untangled-comment-highlight'); } }, 150); } });
const withIssueInvalidation = async (repo: RepoContext, issueRef: string, issueUri: string) => { await client.invalidateQueries({ queryKey: issuesQueryKey(repo.repoDid) }); await client.invalidateQueries({ queryKey: issueQueryKey(repo.repoDid, issueRef) }); await client.invalidateQueries({ queryKey: issueQueryKey(repo.repoDid, parseAtUri(issueUri).rkey) });174 unmodified linesExpand all <div class="mt-4 flex flex-col gap-4"> <For each={commentThreads()}> {(thread) => ( <div class="overflow-hidden rounded border border-gray-200 bg-gray-50 shadow-sm dark:border-gray-700 dark:bg-gray-800/50"> <div class="flex gap-2 rounded bg-white px-6 py-4 dark:bg-gray-800"> <div id={`comment-${thread.item.rkey}`} class="flex gap-2 rounded bg-white px-6 py-4 dark:bg-gray-800"> <div class="shrink-0"> <Avatar did={thread.item.author.did} size="size-8 mr-1" /> </div> <div class="min-w-0 flex-1">2 unmodified lines <Show when={thread.item.author.did === detail().issue.author.did}> <span>(author)</span> </Show> <span class="select-none">·</span> <span>{formatRelativeTime(thread.item.value.createdAt)}</span> <a href={`#comment-${thread.item.rkey}`} class="hover:underline">{formatRelativeTime(thread.item.value.createdAt)}</a> </div> <MarkdownBlock markdown={thread.item.value.body} /> </div> </div>1 unmodified line <Show when={thread.replies.length > 0}> <div class="relative ml-10 border-l border-gray-200 dark:border-gray-700"> <For each={thread.replies}> {(reply) => ( <div class="-ml-4 flex gap-2 py-4 pr-4"> <div id={`comment-${reply.rkey}`} class="-ml-4 flex gap-2 py-4 pr-4"> <div class="shrink-0 relative z-10"> <Avatar did={reply.author.did} size="size-8 mr-1" /> </div> <div class="min-w-0 flex-1">2 unmodified lines <Show when={reply.author.did === detail().issue.author.did}> <span>(author)</span> </Show> <span class="select-none">·</span> <span>{formatRelativeTime(reply.value.createdAt)}</span> <a href={`#comment-${reply.rkey}`} class="hover:underline">{formatRelativeTime(reply.value.createdAt)}</a> </div> <MarkdownBlock markdown={reply.value.body} /> </div> </div>146 unmodified linesExpand all