an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm
at main 4.8 kB view raw
1import { AtUri } from "@atproto/api"; 2import { useNavigate, type UseNavigateResult } from "@tanstack/react-router"; 3import { useAtom } from "jotai"; 4import { useState } from "react"; 5 6import { useAuth } from "~/providers/UnifiedAuthProvider"; 7import { lycanURLAtom } from "~/utils/atoms"; 8import { useQueryLycanStatus } from "~/utils/useQuery"; 9 10/** 11 * Basically the best equivalent to Search that i can do 12 */ 13export function Import({ 14 optionaltextstring, 15}: { 16 optionaltextstring?: string; 17}) { 18 const [textInput, setTextInput] = useState<string | undefined>( 19 optionaltextstring 20 ); 21 const navigate = useNavigate(); 22 23 const { status } = useAuth(); 24 const [lycandomain] = useAtom(lycanURLAtom); 25 const lycanExists = lycandomain !== ""; 26 const { data: lycanstatusdata } = useQueryLycanStatus(); 27 const lycanIndexed = lycanstatusdata?.status === "finished" || false; 28 const lycanIndexing = lycanstatusdata?.status === "in_progress" || false; 29 const lycanIndexingProgress = lycanIndexing 30 ? lycanstatusdata?.progress 31 : undefined; 32 const authed = status === "signedIn"; 33 34 const lycanReady = lycanExists && lycanIndexed && authed; 35 36 const handleEnter = () => { 37 if (!textInput) return; 38 handleImport({ 39 text: textInput, 40 navigate, 41 lycanReady: 42 lycanReady || (!!lycanIndexingProgress && lycanIndexingProgress > 0), 43 }); 44 }; 45 46 const placeholder = lycanReady ? "Search..." : "Import..."; 47 48 return ( 49 <div className="w-full relative"> 50 <IconMaterialSymbolsSearch className="w-5 h-5 absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" /> 51 52 <input 53 type="text" 54 placeholder={placeholder} 55 value={textInput} 56 onChange={(e) => setTextInput(e.target.value)} 57 onKeyDown={(e) => { 58 if (e.key === "Enter") handleEnter(); 59 }} 60 className="w-full h-12 pl-12 pr-4 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-500 box-border transition" 61 /> 62 </div> 63 ); 64} 65 66function handleImport({ 67 text, 68 navigate, 69 lycanReady, 70}: { 71 text: string; 72 navigate: UseNavigateResult<string>; 73 lycanReady?: boolean; 74}) { 75 const trimmed = text.trim(); 76 // parse text 77 /** 78 * text might be 79 * 1. bsky dot app url (reddwarf link segments might be uri encoded,) 80 * 2. aturi 81 * 3. plain handle 82 * 4. plain did 83 */ 84 85 // 1. Check if it’s a URL 86 try { 87 const url = new URL(text); 88 const knownHosts = [ 89 "bsky.app", 90 "social.daniela.lol", 91 "deer.social", 92 "reddwarf.whey.party", 93 "reddwarf.app", 94 "main.bsky.dev", 95 "catsky.social", 96 "blacksky.community", 97 "red-dwarf-social-app.whey.party", 98 "zeppelin.social", 99 ]; 100 if (knownHosts.includes(url.hostname)) { 101 // parse path to get URI or handle 102 const path = decodeURIComponent(url.pathname.slice(1)); // remove leading / 103 console.log("BSky URL path:", path); 104 navigate({ 105 to: `/${path}`, 106 }); 107 return; 108 } 109 } catch { 110 // not a URL, continue 111 } 112 113 // 2. Check if text looks like an at-uri 114 try { 115 if (text.startsWith("at://")) { 116 console.log("AT URI detected:", text); 117 const aturi = new AtUri(text); 118 switch (aturi.collection) { 119 case "app.bsky.feed.post": { 120 navigate({ 121 to: "/profile/$did/post/$rkey", 122 params: { 123 did: aturi.host, 124 rkey: aturi.rkey, 125 }, 126 }); 127 return; 128 } 129 case "app.bsky.actor.profile": { 130 navigate({ 131 to: "/profile/$did", 132 params: { 133 did: aturi.host, 134 }, 135 }); 136 return; 137 } 138 // todo add more handlers as more routes are added. like feeds, lists, etc etc thanks! 139 default: { 140 // continue 141 } 142 } 143 } 144 } catch { 145 // continue 146 } 147 148 // 3. Plain handle (starts with @) 149 try { 150 if (text.startsWith("@")) { 151 const handle = text.slice(1); 152 console.log("Handle detected:", handle); 153 navigate({ to: "/profile/$did", params: { did: handle } }); 154 return; 155 } 156 } catch { 157 // continue 158 } 159 160 // 4. Plain DID (starts with did:) 161 try { 162 if (text.startsWith("did:")) { 163 console.log("did detected:", text); 164 navigate({ to: "/profile/$did", params: { did: text } }); 165 return; 166 } 167 } catch { 168 // continue 169 } 170 171 // if all else fails 172 173 // try { 174 // // probably a user? 175 // navigate({ to: "/profile/$did", params: { did: text } }); 176 // return; 177 // } catch { 178 // // continue 179 // } 180 181 if (lycanReady) { 182 navigate({ to: "/search", search: { q: text } }); 183 } 184}