atmosphere explorer pds.ls
tool typescript atproto
434
fork

Configure Feed

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

add collection and rkey errors during creation

Juliet 444bd20a 203b9427

+28 -71
+26 -2
src/components/create.tsx
··· 1 1 import { Client } from "@atcute/client"; 2 2 import { Did } from "@atcute/lexicons"; 3 + import { isNsid, isRecordKey } from "@atcute/lexicons/syntax"; 3 4 import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 4 5 import { remove } from "@mary/exif-rm"; 5 6 import { useNavigate, useParams } from "@solidjs/router"; ··· 25 26 const [validate, setValidate] = createSignal<boolean | undefined>(undefined); 26 27 const [isMaximized, setIsMaximized] = createSignal(false); 27 28 const [isMinimized, setIsMinimized] = createSignal(false); 29 + const [collectionError, setCollectionError] = createSignal(""); 30 + const [rkeyError, setRkeyError] = createSignal(""); 28 31 let blobInput!: HTMLInputElement; 29 32 let formRef!: HTMLFormElement; 30 33 let insertMenuRef!: HTMLDivElement; ··· 77 80 createEffect(() => { 78 81 if (openDialog()) { 79 82 setValidate(undefined); 83 + setCollectionError(""); 84 + setRkeyError(""); 80 85 } 81 86 }); 82 87 ··· 365 370 id="collection" 366 371 name="collection" 367 372 placeholder="Collection (default: $type)" 368 - class="w-40 placeholder:text-xs lg:w-52" 373 + class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 374 + onInput={(e) => { 375 + const value = e.currentTarget.value; 376 + if (!value || isNsid(value)) setCollectionError(""); 377 + else 378 + setCollectionError( 379 + "Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)", 380 + ); 381 + }} 369 382 /> 370 383 <span>/</span> 371 384 <TextInput 372 385 id="rkey" 373 386 name="rkey" 374 387 placeholder="Record key (default: TID)" 375 - class="w-40 placeholder:text-xs lg:w-52" 388 + class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 389 + onInput={(e) => { 390 + const value = e.currentTarget.value; 391 + if (!value || isRecordKey(value)) setRkeyError(""); 392 + else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -"); 393 + }} 376 394 /> 377 395 </div> 396 + <Show when={collectionError() || rkeyError()}> 397 + <div class="text-xs text-red-500 dark:text-red-400"> 398 + <div>{collectionError()}</div> 399 + <div>{rkeyError()}</div> 400 + </div> 401 + </Show> 378 402 </Show> 379 403 <div class="min-h-0 flex-1"> 380 404 <Editor
+2 -3
src/components/json.tsx
··· 1 - import { isCid, isDid, isNsid, Nsid } from "@atcute/lexicons/syntax"; 1 + import { isCid, isDid, isNsid, isResourceUri, Nsid } from "@atcute/lexicons/syntax"; 2 2 import { A, useNavigate, useParams } from "@solidjs/router"; 3 3 import { createEffect, createSignal, ErrorBoundary, For, on, Show } from "solid-js"; 4 4 import { resolveLexiconAuthority } from "../utils/api"; 5 - import { ATURI_RE } from "../utils/types/at-uri"; 6 5 import { hideMedia } from "../views/settings"; 7 6 import { pds } from "./navbar"; 8 7 import { addNotification, removeNotification } from "./notification"; ··· 57 56 <For each={props.data.split(/(\s)/)}> 58 57 {(part) => ( 59 58 <> 60 - {ATURI_RE.test(part) ? 59 + {isResourceUri(part) ? 61 60 <A class="text-blue-400 hover:underline active:underline" href={`/${part}`}> 62 61 {part} 63 62 </A>
-66
src/utils/types/at-uri.ts
··· 1 - type Did<TMethod extends string = string> = `did:${TMethod}:${string}`; 2 - 3 - type Nsid = `${string}.${string}.${string}`; 4 - 5 - type RecordKey = string; 6 - 7 - const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/; 8 - 9 - const NSID_RE = 10 - /^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/; 11 - 12 - const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/; 13 - 14 - export const ATURI_RE = 15 - /^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 16 - 17 - const isDid = (input: unknown): input is Did => { 18 - return ( 19 - typeof input === "string" && input.length >= 7 && input.length <= 2048 && DID_RE.test(input) 20 - ); 21 - }; 22 - 23 - const isNsid = (input: unknown): input is Nsid => { 24 - return ( 25 - typeof input === "string" && input.length >= 5 && input.length <= 317 && NSID_RE.test(input) 26 - ); 27 - }; 28 - 29 - const isRecordKey = (input: unknown): input is RecordKey => { 30 - return ( 31 - typeof input === "string" && 32 - input.length >= 1 && 33 - input.length <= 512 && 34 - RECORD_KEY_RE.test(input) 35 - ); 36 - }; 37 - 38 - export interface AddressedAtUri { 39 - repo: Did; 40 - collection: Nsid; 41 - rkey: string; 42 - fragment: string | undefined; 43 - } 44 - 45 - export const parseAddressedAtUri = (str: string): AddressedAtUri => { 46 - const match = ATURI_RE.exec(str); 47 - assert(match !== null, `invalid addressed-at-uri: ${str}`); 48 - 49 - const [, r, c, k, f] = match; 50 - assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`); 51 - assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`); 52 - assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`); 53 - 54 - return { 55 - repo: r, 56 - collection: c, 57 - rkey: k, 58 - fragment: f, 59 - }; 60 - }; 61 - 62 - function assert(condition: boolean, message: string): asserts condition { 63 - if (!condition) { 64 - throw new Error(message); 65 - } 66 - }