atmosphere explorer pds.ls
tool typescript atproto
439
fork

Configure Feed

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

favicon in backlinks and car explorer

juliet.paris d22e1bed ef068af8

verified
+62 -31
+11 -5
src/components/backlinks.tsx
··· 3 3 import { getAllBacklinks, getRecordBacklinks, LinksWithRecords } from "../utils/api.js"; 4 4 import { localDateFromTimestamp } from "../utils/date.js"; 5 5 import { Button } from "./button.jsx"; 6 + import { Favicon } from "./favicon.jsx"; 6 7 7 8 type BacklinksProps = { 8 9 target: string; ··· 122 123 ) => { 123 124 const [expanded, setExpanded] = createSignal(false); 124 125 126 + const authority = () => props.collection.split(".").slice(0, 2).join("."); 127 + 125 128 return ( 126 129 <div class="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700"> 127 130 <button 128 131 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" 129 132 onClick={() => setExpanded(!expanded())} 130 133 > 131 - <div class="flex min-w-0 flex-1 flex-col"> 132 - <span class="w-full truncate">{props.collection}</span> 133 - <span class="w-full text-xs wrap-break-word text-neutral-500 dark:text-neutral-400"> 134 - {props.path.slice(1)} 135 - </span> 134 + <div class="flex min-w-0 flex-1 items-center gap-2"> 135 + <Favicon authority={authority()} /> 136 + <div class="flex min-w-0 flex-1 flex-col"> 137 + <span class="w-full truncate">{props.collection}</span> 138 + <span class="w-full text-xs wrap-break-word text-neutral-500 dark:text-neutral-400"> 139 + {props.path.slice(1)} 140 + </span> 141 + </div> 136 142 </div> 137 143 <div class="flex shrink-0 items-center gap-2 text-neutral-700 dark:text-neutral-300"> 138 144 <span class="text-xs">
+33
src/components/favicon.tsx
··· 1 + import { createSignal, JSX, Show } from "solid-js"; 2 + 3 + export const Favicon = (props: { 4 + authority: string; 5 + wrapper?: (children: JSX.Element) => JSX.Element; 6 + }) => { 7 + const [loaded, setLoaded] = createSignal(false); 8 + const domain = () => props.authority.split(".").reverse().join("."); 9 + 10 + const content = ( 11 + <> 12 + <Show when={!loaded()}> 13 + <span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" /> 14 + </Show> 15 + <img 16 + src={ 17 + ["bsky.app", "bsky.chat"].includes(domain()) ? 18 + "https://web-cdn.bsky.app/static/apple-touch-icon.png" 19 + : `https://${domain()}/favicon.ico` 20 + } 21 + alt="" 22 + class="h-4 w-4" 23 + classList={{ hidden: !loaded() }} 24 + onLoad={() => setLoaded(true)} 25 + onError={() => setLoaded(false)} 26 + /> 27 + </> 28 + ); 29 + 30 + return props.wrapper ? 31 + props.wrapper(content) 32 + : <div class="flex h-5 w-4 shrink-0 items-center justify-center">{content}</div>; 33 + };
+3
src/views/car/explore.tsx
··· 6 6 import { Title } from "@solidjs/meta"; 7 7 import { createEffect, createMemo, createSignal, For, Match, Show, Switch } from "solid-js"; 8 8 import { Button } from "../../components/button.jsx"; 9 + import { Favicon } from "../../components/favicon.jsx"; 9 10 import { JSONValue } from "../../components/json.jsx"; 10 11 import { TextInput } from "../../components/text-input.jsx"; 11 12 import { isTouchDevice } from "../../layout.jsx"; ··· 309 310 <For each={filteredEntries()}> 310 311 {(entry) => { 311 312 const hasSingleEntry = entry.entries.length === 1; 313 + const authority = () => entry.name.split(".").slice(0, 2).join("."); 312 314 313 315 return ( 314 316 <li> ··· 326 328 }} 327 329 class="flex w-full items-center gap-2 rounded p-2 text-left text-sm hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-800 dark:active:bg-neutral-700" 328 330 > 331 + <Favicon authority={authority()} /> 329 332 <span 330 333 class="truncate font-medium" 331 334 classList={{
+15 -26
src/views/repo.tsx
··· 23 23 MenuSeparator, 24 24 NavMenu, 25 25 } from "../components/dropdown.jsx"; 26 + import { Favicon } from "../components/favicon.jsx"; 26 27 import { 27 28 addNotification, 28 29 removeNotification, ··· 453 454 )} 454 455 > 455 456 {(authority) => { 456 - const reversedDomain = authority.split(".").reverse().join("."); 457 - const [faviconLoaded, setFaviconLoaded] = createSignal(false); 458 - 459 457 const isHighlighted = () => location.hash === `#collections:${authority}`; 460 458 461 459 return ( ··· 467 465 "bg-blue-100 dark:bg-blue-500/25": isHighlighted(), 468 466 }} 469 467 > 470 - <a 471 - href={`#collections:${authority}`} 472 - class="relative flex h-5 w-4 shrink-0 items-center justify-center hover:opacity-70" 473 - > 474 - <span class="absolute top-1/2 -left-5 flex -translate-y-1/2 items-center text-base opacity-0 transition-opacity group-hover:opacity-100"> 475 - <span class="iconify lucide--link absolute -left-2 w-7"></span> 476 - </span> 477 - <Show when={!faviconLoaded()}> 478 - <span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" /> 479 - </Show> 480 - <img 481 - src={ 482 - ["bsky.app", "bsky.chat"].includes(reversedDomain) ? 483 - "https://web-cdn.bsky.app/static/apple-touch-icon.png" 484 - : `https://${reversedDomain}/favicon.ico` 485 - } 486 - alt={`${reversedDomain} favicon`} 487 - class="h-4 w-4" 488 - classList={{ hidden: !faviconLoaded() }} 489 - onLoad={() => setFaviconLoaded(true)} 490 - onError={() => setFaviconLoaded(false)} 491 - /> 492 - </a> 468 + <Favicon 469 + authority={authority} 470 + wrapper={(children) => ( 471 + <a 472 + href={`#collections:${authority}`} 473 + class="relative flex h-5 w-4 shrink-0 items-center justify-center hover:opacity-70" 474 + > 475 + <span class="absolute top-1/2 -left-5 flex -translate-y-1/2 items-center text-base opacity-0 transition-opacity group-hover:opacity-100"> 476 + <span class="iconify lucide--link absolute -left-2 w-7"></span> 477 + </span> 478 + {children} 479 + </a> 480 + )} 481 + /> 493 482 <div class="flex flex-1 flex-col"> 494 483 <For 495 484 each={nsids()?.[authority].nsids.filter((nsid) =>