atmosphere explorer
0
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 - }