this repo has no description
at filter-styling 200 lines 6.7 kB view raw
1import * as TID from "@atcute/tid"; 2import { createResource, createSignal, For, onMount, Show } from "solid-js"; 3import { getAllBacklinks, getDidBacklinks, getRecordBacklinks } from "../utils/api.js"; 4import { localDateFromTimestamp } from "../utils/date.js"; 5import { Button } from "./button.jsx"; 6 7// the actual backlink api will probably become closer to this 8const linksBySource = (links: Record<string, any>) => { 9 let out: any[] = []; 10 Object.keys(links) 11 .toSorted() 12 .forEach((collection) => { 13 const paths = links[collection]; 14 Object.keys(paths) 15 .toSorted() 16 .forEach((path) => { 17 if (paths[path].records === 0) return; 18 out.push({ collection, path, counts: paths[path] }); 19 }); 20 }); 21 return out; 22}; 23 24const Backlinks = (props: { target: string }) => { 25 const fetchBacklinks = async () => { 26 const res = await getAllBacklinks(props.target); 27 setBacklinks(linksBySource(res.links)); 28 return res; 29 }; 30 31 const [response] = createResource(fetchBacklinks); 32 const [backlinks, setBacklinks] = createSignal<any>(); 33 34 const [show, setShow] = createSignal<{ 35 collection: string; 36 path: string; 37 showDids: boolean; 38 } | null>(); 39 40 return ( 41 <Show when={response()}> 42 <div class="flex w-full flex-col gap-1 text-sm wrap-anywhere"> 43 <For each={backlinks()}> 44 {({ collection, path, counts }) => ( 45 <div> 46 <div> 47 <div title="Collection containing linking records" class="flex items-center gap-1"> 48 <span class="iconify lucide--book-text shrink-0"></span> 49 {collection} 50 </div> 51 <div title="Record path where the link is found" class="flex items-center gap-1"> 52 <span class="iconify lucide--route shrink-0"></span> 53 {path.slice(1)} 54 </div> 55 </div> 56 <div class="ml-4.5"> 57 <p> 58 <button 59 class="text-blue-400 hover:underline active:underline" 60 title="Show linking records" 61 onclick={() => 62 ( 63 show()?.collection === collection && 64 show()?.path === path && 65 !show()?.showDids 66 ) ? 67 setShow(null) 68 : setShow({ collection, path, showDids: false }) 69 } 70 > 71 {counts.records} record{counts.records < 2 ? "" : "s"} 72 </button> 73 {" from "} 74 <button 75 class="text-blue-400 hover:underline active:underline" 76 title="Show linking DIDs" 77 onclick={() => 78 ( 79 show()?.collection === collection && 80 show()?.path === path && 81 show()?.showDids 82 ) ? 83 setShow(null) 84 : setShow({ collection, path, showDids: true }) 85 } 86 > 87 {counts.distinct_dids} DID 88 {counts.distinct_dids < 2 ? "" : "s"} 89 </button> 90 </p> 91 <Show when={show()?.collection === collection && show()?.path === path}> 92 <Show when={show()?.showDids}> 93 {/* putting this in the `dids` prop directly failed to re-render. idk how to solidjs. */} 94 <p class="w-full font-semibold">Distinct identities</p> 95 <BacklinkItems 96 target={props.target} 97 collection={collection} 98 path={path} 99 dids={true} 100 /> 101 </Show> 102 <Show when={!show()?.showDids}> 103 <p class="w-full font-semibold">Records</p> 104 <BacklinkItems 105 target={props.target} 106 collection={collection} 107 path={path} 108 dids={false} 109 /> 110 </Show> 111 </Show> 112 </div> 113 </div> 114 )} 115 </For> 116 </div> 117 </Show> 118 ); 119}; 120 121// switching on !!did everywhere is pretty annoying, this could probably be two components 122// but i don't want to duplicate or think about how to extract the paging logic 123const BacklinkItems = ({ 124 target, 125 collection, 126 path, 127 dids, 128 cursor, 129}: { 130 target: string; 131 collection: string; 132 path: string; 133 dids: boolean; 134 cursor?: string; 135}) => { 136 const [links, setLinks] = createSignal<any>(); 137 const [more, setMore] = createSignal<boolean>(false); 138 139 onMount(async () => { 140 const links = await (dids ? getDidBacklinks : getRecordBacklinks)( 141 target, 142 collection, 143 path, 144 cursor, 145 ); 146 setLinks(links); 147 }); 148 149 // TODO: could pass the `total` into this component, which can be checked against each call to this endpoint to find if it's stale. 150 // also hmm 'total' is misleading/wrong on that api 151 152 return ( 153 <Show when={links()} fallback={<p>Loading&hellip;</p>}> 154 <Show when={dids}> 155 <For each={links().linking_dids}> 156 {(did) => ( 157 <a 158 href={`/at://${did}`} 159 class="relative flex w-full font-mono text-blue-400 hover:underline active:underline" 160 > 161 {did} 162 </a> 163 )} 164 </For> 165 </Show> 166 <Show when={!dids}> 167 <For each={links().linking_records}> 168 {({ did, collection, rkey }) => ( 169 <p class="relative flex w-full items-center gap-1 font-mono"> 170 <a 171 href={`/at://${did}/${collection}/${rkey}`} 172 class="text-blue-400 hover:underline active:underline" 173 > 174 {rkey} 175 </a> 176 <span class="text-xs text-neutral-500 dark:text-neutral-400"> 177 {TID.validate(rkey) ? 178 localDateFromTimestamp(TID.parse(rkey).timestamp / 1000) 179 : undefined} 180 </span> 181 </p> 182 )} 183 </For> 184 </Show> 185 <Show when={links().cursor}> 186 <Show when={more()} fallback={<Button onClick={() => setMore(true)}>Load More</Button>}> 187 <BacklinkItems 188 target={target} 189 collection={collection} 190 path={path} 191 dids={dids} 192 cursor={links().cursor} 193 /> 194 </Show> 195 </Show> 196 </Show> 197 ); 198}; 199 200export { Backlinks };