an appview-less Bluesky client using Constellation and PDS Queries
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
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}