atproto explorer
0
fork

Configure Feed

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

add create record

+183 -1
+166
src/components/create.tsx
··· 1 + import { 2 + createSignal, 3 + onMount, 4 + Show, 5 + type Component, 6 + onCleanup, 7 + createEffect, 8 + } from "solid-js"; 9 + import { XRPC, XRPCResponse } from "@atcute/client"; 10 + import { agent } from "../views/login.jsx"; 11 + import { Editor } from "../components/editor.jsx"; 12 + import { editor } from "monaco-editor"; 13 + import { theme } from "../main.jsx"; 14 + import { action, redirect } from "@solidjs/router"; 15 + import { ComAtprotoRepoCreateRecord } from "@atcute/client/lexicons"; 16 + 17 + const CreateRecord: Component = () => { 18 + const [modal, setModal] = createSignal<HTMLDialogElement>(); 19 + const [openCreate, setOpenCreate] = createSignal(false); 20 + const [createNotice, setCreateNotice] = createSignal(""); 21 + let model: editor.IModel; 22 + const placeholder = (date: string) => { 23 + return { 24 + $type: "app.bsky.feed.post", 25 + text: "This post was sent from PDSls", 26 + embed: { 27 + $type: "app.bsky.embed.external", 28 + external: { 29 + uri: "https://pdsls.dev", 30 + title: "PDSls", 31 + description: "Browse Atproto repositories", 32 + }, 33 + }, 34 + langs: ["en"], 35 + createdAt: date, 36 + }; 37 + }; 38 + 39 + let clickEvent = (event: MouseEvent) => { 40 + if (modal() && event.target == modal()) setOpenCreate(false); 41 + }; 42 + let keyEvent = (event: KeyboardEvent) => { 43 + if (modal() && event.key == "Escape") setOpenCreate(false); 44 + }; 45 + 46 + onMount(async () => { 47 + window.addEventListener("click", clickEvent); 48 + window.addEventListener("keydown", keyEvent); 49 + }); 50 + 51 + onCleanup(() => { 52 + window.removeEventListener("click", clickEvent); 53 + window.removeEventListener("keydown", keyEvent); 54 + }); 55 + 56 + const createRecord = action(async (formData: FormData) => { 57 + const rpc = new XRPC({ handler: agent }); 58 + const collection = formData.get("collection"); 59 + const rkey = formData.get("rkey"); 60 + let res: XRPCResponse<ComAtprotoRepoCreateRecord.Output>; 61 + try { 62 + res = await rpc.call("com.atproto.repo.createRecord", { 63 + data: { 64 + repo: agent.sub, 65 + collection: collection!.toString(), 66 + rkey: rkey?.toString() ?? undefined, 67 + record: JSON.parse(model.getValue()), 68 + }, 69 + }); 70 + } catch (err: any) { 71 + setCreateNotice(err.message); 72 + return; 73 + } 74 + setOpenCreate(false); 75 + throw redirect(`/at/${res.data.uri.split("at://")[1]}`); 76 + }); 77 + 78 + createEffect(() => { 79 + if (openCreate()) document.body.style.overflow = "hidden"; 80 + else document.body.style.overflow = "auto"; 81 + setCreateNotice(""); 82 + }); 83 + 84 + return ( 85 + <> 86 + <Show when={openCreate()}> 87 + <dialog 88 + ref={setModal} 89 + class="fixed left-0 top-0 z-[2] flex h-screen w-screen items-center justify-center bg-transparent" 90 + > 91 + <div class="dark:bg-dark-400 rounded-md border border-slate-900 bg-slate-100 p-4 text-slate-900 dark:border-slate-100 dark:text-slate-100"> 92 + <h3 class="mb-2 text-lg font-bold">Creating record</h3> 93 + <form 94 + class="flex flex-col gap-y-3" 95 + action={createRecord} 96 + method="post" 97 + > 98 + <div class="flex w-fit flex-col gap-y-3"> 99 + <div class="flex items-center gap-x-2"> 100 + <label for="collection" class="basis-1/2 select-none"> 101 + Collection 102 + </label> 103 + <input 104 + id="collection" 105 + name="collection" 106 + type="text" 107 + required 108 + spellcheck={false} 109 + value="app.bsky.feed.post" 110 + class="dark:bg-dark-100 rounded-lg border border-gray-400 px-2 py-1 focus:outline-none focus:ring-1 focus:ring-gray-300" 111 + /> 112 + </div> 113 + <div class="flex items-center gap-x-2"> 114 + <label for="rkey" class="basis-1/2 select-none"> 115 + Record key 116 + </label> 117 + <input 118 + id="rkey" 119 + name="rkey" 120 + type="text" 121 + spellcheck={false} 122 + placeholder="Optional" 123 + class="dark:bg-dark-100 rounded-lg border border-gray-400 px-2 py-1 focus:outline-none focus:ring-1 focus:ring-gray-300" 124 + /> 125 + </div> 126 + </div> 127 + <Editor theme={theme()} model={model!} /> 128 + <div class="flex flex-col gap-x-2"> 129 + <div class="text-red-500 dark:text-red-400"> 130 + {createNotice()} 131 + </div> 132 + <div class="flex items-center justify-end gap-2"> 133 + <button 134 + onclick={() => setOpenCreate(false)} 135 + class="dark:bg-dark-900 dark:hover:bg-dark-800 rounded-lg bg-white px-2.5 py-1.5 text-sm font-bold hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-slate-700 dark:focus:ring-slate-300" 136 + > 137 + Cancel 138 + </button> 139 + <button 140 + type="submit" 141 + class="rounded-lg bg-green-500 px-2.5 py-1.5 text-sm font-bold text-slate-100 hover:bg-green-400 focus:outline-none focus:ring-2 focus:ring-slate-700 dark:bg-green-600 dark:hover:bg-green-500 dark:focus:ring-slate-300" 142 + > 143 + Confirm 144 + </button> 145 + </div> 146 + </div> 147 + </form> 148 + </div> 149 + </dialog> 150 + </Show> 151 + <div 152 + class="i-octicon-pencil-16 cursor-pointer text-xl" 153 + title="Create record" 154 + onclick={() => { 155 + model = editor.createModel( 156 + JSON.stringify(placeholder(new Date().toISOString()), null, 2), 157 + "json", 158 + ); 159 + setOpenCreate(true); 160 + }} 161 + ></div> 162 + </> 163 + ); 164 + }; 165 + 166 + export { CreateRecord };
+4
src/main.tsx
··· 11 11 } from "@solidjs/router"; 12 12 import { agent, loginState, LoginStatus } from "./views/login.jsx"; 13 13 import { resolveHandle, resolvePDS } from "./utils/api.js"; 14 + import { CreateRecord } from "./components/create.jsx"; 14 15 15 16 export const [theme, setTheme] = createSignal( 16 17 ( ··· 140 141 : <div class="i-tabler-sun text-xl" />} 141 142 </div> 142 143 <LoginStatus /> 144 + <Show when={loginState()}> 145 + <CreateRecord /> 146 + </Show> 143 147 </div> 144 148 <div class="basis-1/3 text-center font-mono text-xl font-bold"> 145 149 <a href="/" class="hover:underline">
+12
src/styles/icons.css
··· 201 201 width: 1.2em; 202 202 height: 1.2em; 203 203 } 204 + 205 + .i-octicon-pencil-16 { 206 + --un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 16 16' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.25.25 0 0 0-.064.108l-.558 1.953l1.953-.558a.25.25 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44l1.263-1.263a.25.25 0 0 0 0-.354Z'/%3E%3C/svg%3E"); 207 + -webkit-mask: var(--un-icon) no-repeat; 208 + mask: var(--un-icon) no-repeat; 209 + -webkit-mask-size: 100% 100%; 210 + mask-size: 100% 100%; 211 + background-color: currentColor; 212 + color: inherit; 213 + width: 1.2em; 214 + height: 1.2em; 215 + }
+1 -1
src/views/record.tsx
··· 133 133 134 134 const deleteRecord = action(async () => { 135 135 rpc = new XRPC({ handler: agent }); 136 - rpc.call("com.atproto.repo.deleteRecord", { 136 + await rpc.call("com.atproto.repo.deleteRecord", { 137 137 data: { 138 138 repo: params.repo, 139 139 collection: params.collection,