atmosphere explorer pds.ls
tool typescript atproto
427
fork

Configure Feed

Select the types of activity you want to include in your feed.

backlinks group page

Juliet ea3c2365 0c9e020a

+139 -57
+1 -1
src/auth/account.tsx
··· 198 198 </Modal> 199 199 <button 200 200 onclick={() => setOpenManager(true)} 201 - class={`flex items-center rounded-md ${agent() && avatars[agent()!.sub] ? "p-1.25" : "p-1.5"} hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600`} 201 + class={`flex items-center rounded-md ${agent() && avatars[agent()!.sub] ? "p-1.25" : "p-1.5"} hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700/50 dark:active:bg-neutral-700`} 202 202 > 203 203 {agent() && avatars[agent()!.sub] ? 204 204 <img src={getThumbnailUrl(avatars[agent()!.sub])} class="size-5 rounded-full" />
+133 -52
src/components/backlinks.tsx
··· 1 1 import * as TID from "@atcute/tid"; 2 + import { A, useLocation } from "@solidjs/router"; 2 3 import { createResource, createSignal, For, onMount, Show } from "solid-js"; 3 4 import { getAllBacklinks, getRecordBacklinks, LinksWithRecords } from "../lib/api.js"; 4 5 import { localDateFromTimestamp } from "../utils/date.js"; ··· 19 20 counts: { distinct_dids: number; records: number }; 20 21 }; 21 22 23 + type CollectionGroup = { 24 + collection: string; 25 + entries: BacklinkEntry[]; 26 + totalRecords: number; 27 + totalDids: number; 28 + }; 29 + 22 30 const flattenLinks = (links: Record<string, any>): BacklinkEntry[] => { 23 31 const entries: BacklinkEntry[] = []; 24 32 Object.keys(links) ··· 34 42 }); 35 43 }); 36 44 return entries; 45 + }; 46 + 47 + const groupByCollection = (entries: BacklinkEntry[]): CollectionGroup[] => { 48 + const map = new Map<string, BacklinkEntry[]>(); 49 + for (const entry of entries) { 50 + const existing = map.get(entry.collection); 51 + if (existing) { 52 + existing.push(entry); 53 + } else { 54 + map.set(entry.collection, [entry]); 55 + } 56 + } 57 + return Array.from(map.entries()).map(([collection, entries]) => ({ 58 + collection, 59 + entries, 60 + totalRecords: entries.reduce((sum, e) => sum + e.counts.records, 0), 61 + totalDids: entries.reduce((sum, e) => sum + e.counts.distinct_dids, 0), 62 + })); 37 63 }; 38 64 39 65 const BacklinkRecords = (props: BacklinksProps & { cursor?: string }) => { ··· 110 136 ); 111 137 }; 112 138 113 - const Backlinks = (props: { target: string }) => { 114 - const [response] = createResource(async () => { 115 - const res = await getAllBacklinks(props.target); 116 - return flattenLinks(res.links); 117 - }); 118 - 139 + const BacklinkDirectory = (props: { groups: CollectionGroup[]; pathname: string }) => { 119 140 return ( 120 - <div class="flex w-full flex-col gap-3 text-sm"> 121 - <Show when={response()} fallback={<p class="text-neutral-500">Loading…</p>}> 122 - <Show when={response()!.length === 0}> 123 - <p class="text-neutral-500">No backlinks found.</p> 124 - </Show> 125 - <For each={response()}> 126 - {(entry) => ( 127 - <BacklinkSection 128 - target={props.target} 129 - collection={entry.collection} 130 - path={entry.path} 131 - counts={entry.counts} 132 - /> 133 - )} 134 - </For> 141 + <div class="flex w-full flex-col gap-1.5"> 142 + <Show when={props.groups.length === 0}> 143 + <p class="text-neutral-500">No backlinks found.</p> 135 144 </Show> 145 + <For each={props.groups}> 146 + {(group) => { 147 + const authority = () => group.collection.split(".").slice(0, 2).join("."); 148 + return ( 149 + <A 150 + href={`${props.pathname}#backlinks:${group.collection}`} 151 + class="flex items-center justify-between gap-3 rounded-lg border border-neutral-200 px-3 py-2 text-left hover:bg-neutral-200/50 dark:border-neutral-700 dark:hover:bg-neutral-800" 152 + > 153 + <div class="flex min-w-0 flex-1 items-center gap-2"> 154 + <Favicon domain={authority()} reverse /> 155 + <span class="min-w-0 truncate">{group.collection}</span> 156 + </div> 157 + <div class="flex shrink-0 items-center gap-2 text-neutral-600 dark:text-neutral-400"> 158 + <span class="text-xs"> 159 + {group.totalRecords} from {group.totalDids} repo 160 + {group.totalDids > 1 ? "s" : ""} 161 + </span> 162 + <span class="iconify lucide--chevron-right" /> 163 + </div> 164 + </A> 165 + ); 166 + }} 167 + </For> 136 168 </div> 137 169 ); 138 170 }; 139 171 140 - const BacklinkSection = ( 141 - props: BacklinksProps & { counts: { distinct_dids: number; records: number } }, 142 - ) => { 143 - const [expanded, setExpanded] = createSignal(false); 144 - 172 + const BacklinkCollectionDetail = (props: { 173 + target: string; 174 + collection: string; 175 + entries: BacklinkEntry[]; 176 + pathname: string; 177 + }) => { 145 178 const authority = () => props.collection.split(".").slice(0, 2).join("."); 146 179 147 180 return ( 148 - <div class="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700"> 149 - <button 150 - class="flex w-full items-center justify-between gap-3 px-3 py-2 text-left hover:bg-neutral-50 dark:hover:bg-neutral-800/50" 151 - onClick={() => setExpanded(!expanded())} 152 - > 153 - <div class="flex min-w-0 flex-1 items-center gap-2"> 181 + <div class="flex w-full flex-col gap-3"> 182 + <div class="flex items-center gap-1"> 183 + <A 184 + href={`${props.pathname}#backlinks`} 185 + class="-ml-2 flex items-center rounded-md p-2 text-neutral-700 hover:bg-neutral-200 dark:text-neutral-300 dark:hover:bg-neutral-700/50" 186 + > 187 + <span class="iconify lucide--arrow-left" /> 188 + </A> 189 + <div class="flex items-center gap-2"> 154 190 <Favicon domain={authority()} reverse /> 155 - <div class="flex min-w-0 flex-1 flex-col"> 156 - <span class="w-full truncate">{props.collection}</span> 157 - <span class="w-full text-xs wrap-break-word text-neutral-500 dark:text-neutral-400"> 158 - {props.path.slice(1)} 159 - </span> 160 - </div> 191 + <span class="font-medium">{props.collection}</span> 161 192 </div> 162 - <div class="flex shrink-0 items-center gap-2 text-neutral-700 dark:text-neutral-300"> 163 - <span class="text-xs"> 164 - {props.counts.records} from {props.counts.distinct_dids} repo 165 - {props.counts.distinct_dids > 1 ? "s" : ""} 166 - </span> 167 - <span 168 - class="iconify lucide--chevron-down transition-transform" 169 - classList={{ "rotate-180": expanded() }} 193 + </div> 194 + <For each={props.entries}> 195 + {(entry) => ( 196 + <div class="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700"> 197 + <div class="flex items-center justify-between gap-3 border-b border-neutral-200 px-3 py-2 text-sm dark:border-neutral-700"> 198 + <span class="min-w-0 truncate text-neutral-700 dark:text-neutral-300"> 199 + {entry.path.slice(1)} 200 + </span> 201 + <span class="shrink-0 text-xs text-neutral-500 dark:text-neutral-400"> 202 + {entry.counts.records} from {entry.counts.distinct_dids} repo 203 + {entry.counts.distinct_dids > 1 ? "s" : ""} 204 + </span> 205 + </div> 206 + <div class="bg-neutral-50/50 dark:bg-neutral-800/30"> 207 + <BacklinkRecords 208 + target={props.target} 209 + collection={entry.collection} 210 + path={entry.path} 211 + /> 212 + </div> 213 + </div> 214 + )} 215 + </For> 216 + </div> 217 + ); 218 + }; 219 + 220 + const Backlinks = (props: { target: string }) => { 221 + const location = useLocation(); 222 + 223 + const [response] = createResource(async () => { 224 + const res = await getAllBacklinks(props.target); 225 + return flattenLinks(res.links); 226 + }); 227 + 228 + const groups = () => (response() ? groupByCollection(response()!) : []); 229 + 230 + const selectedCollection = () => { 231 + const hash = location.hash; 232 + if (hash.startsWith("#backlinks:")) { 233 + return hash.slice("#backlinks:".length); 234 + } 235 + return null; 236 + }; 237 + 238 + const selectedEntries = () => { 239 + const col = selectedCollection(); 240 + if (!col || !response()) return []; 241 + return response()!.filter((e) => e.collection === col); 242 + }; 243 + 244 + return ( 245 + <div class="flex w-full flex-col gap-3 text-sm"> 246 + <Show when={response()} fallback={<p class="text-neutral-500">Loading…</p>}> 247 + <Show 248 + when={selectedCollection()} 249 + fallback={<BacklinkDirectory groups={groups()} pathname={location.pathname} />} 250 + > 251 + <BacklinkCollectionDetail 252 + target={props.target} 253 + collection={selectedCollection()!} 254 + entries={selectedEntries()} 255 + pathname={location.pathname} 170 256 /> 171 - </div> 172 - </button> 173 - <Show when={expanded()}> 174 - <div class="border-t border-neutral-200 bg-neutral-50/50 dark:border-neutral-700 dark:bg-neutral-800/30"> 175 - <BacklinkRecords target={props.target} collection={props.collection} path={props.path} /> 176 - </div> 257 + </Show> 177 258 </Show> 178 259 </div> 179 260 );
+1 -1
src/components/create/index.tsx
··· 467 467 <button 468 468 class={ 469 469 hasPermission() ? 470 - `flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 ${props.create ? "rounded-md" : "rounded-sm"}` 470 + `flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700/50 dark:active:bg-neutral-700 ${props.create ? "rounded-md" : "rounded-sm"}` 471 471 : `flex items-center p-1.5 opacity-40 ${props.create ? "rounded-md" : "rounded-sm"}` 472 472 } 473 473 onclick={() => {
+1 -1
src/components/dropdown.tsx
··· 157 157 <div class="relative"> 158 158 <button 159 159 class={ 160 - "flex items-center hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 " + 160 + "flex items-center hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700/50 dark:active:bg-neutral-700 " + 161 161 props.buttonClass 162 162 } 163 163 ref={setMenuButton}
+2 -1
src/views/record.tsx
··· 390 390 if (props.tab === "record") return !location.hash || location.hash.startsWith("#record"); 391 391 if (location.hash === `#${props.tab}`) return true; 392 392 if (props.tab === "schema" && location.hash.startsWith("#schema:")) return true; 393 + if (props.tab === "backlinks" && location.hash.startsWith("#backlinks:")) return true; 393 394 return false; 394 395 }; 395 396 ··· 566 567 </ErrorBoundary> 567 568 </Show> 568 569 </Show> 569 - <Show when={location.hash === "#backlinks"}> 570 + <Show when={location.hash === "#backlinks" || location.hash.startsWith("#backlinks:")}> 570 571 <ErrorBoundary 571 572 fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>} 572 573 >
+1 -1
src/views/repo/index.tsx
··· 456 456 <PlcLogView did={did} /> 457 457 </LazyTab> 458 458 </Show> 459 - <Show when={location.hash === "#backlinks"}> 459 + <Show when={location.hash === "#backlinks" || location.hash.startsWith("#backlinks:")}> 460 460 <LazyTab> 461 461 <Backlinks target={did} /> 462 462 </LazyTab>