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