atproto explorer pdsls.dev
atproto tool

add collection and rkey errors during creation

juli.ee 444bd20a 203b9427

verified
Changed files
+28 -71
src
components
utils
types
+26 -2
src/components/create.tsx
···
import { Client } from "@atcute/client";
import { Did } from "@atcute/lexicons";
import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client";
import { remove } from "@mary/exif-rm";
import { useNavigate, useParams } from "@solidjs/router";
···
const [validate, setValidate] = createSignal<boolean | undefined>(undefined);
const [isMaximized, setIsMaximized] = createSignal(false);
const [isMinimized, setIsMinimized] = createSignal(false);
let blobInput!: HTMLInputElement;
let formRef!: HTMLFormElement;
let insertMenuRef!: HTMLDivElement;
···
createEffect(() => {
if (openDialog()) {
setValidate(undefined);
}
});
···
id="collection"
name="collection"
placeholder="Collection (default: $type)"
-
class="w-40 placeholder:text-xs lg:w-52"
/>
<span>/</span>
<TextInput
id="rkey"
name="rkey"
placeholder="Record key (default: TID)"
-
class="w-40 placeholder:text-xs lg:w-52"
/>
</div>
</Show>
<div class="min-h-0 flex-1">
<Editor
···
import { Client } from "@atcute/client";
import { Did } from "@atcute/lexicons";
+
import { isNsid, isRecordKey } from "@atcute/lexicons/syntax";
import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client";
import { remove } from "@mary/exif-rm";
import { useNavigate, useParams } from "@solidjs/router";
···
const [validate, setValidate] = createSignal<boolean | undefined>(undefined);
const [isMaximized, setIsMaximized] = createSignal(false);
const [isMinimized, setIsMinimized] = createSignal(false);
+
const [collectionError, setCollectionError] = createSignal("");
+
const [rkeyError, setRkeyError] = createSignal("");
let blobInput!: HTMLInputElement;
let formRef!: HTMLFormElement;
let insertMenuRef!: HTMLDivElement;
···
createEffect(() => {
if (openDialog()) {
setValidate(undefined);
+
setCollectionError("");
+
setRkeyError("");
}
});
···
id="collection"
name="collection"
placeholder="Collection (default: $type)"
+
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" : ""}`}
+
onInput={(e) => {
+
const value = e.currentTarget.value;
+
if (!value || isNsid(value)) setCollectionError("");
+
else
+
setCollectionError(
+
"Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)",
+
);
+
}}
/>
<span>/</span>
<TextInput
id="rkey"
name="rkey"
placeholder="Record key (default: TID)"
+
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" : ""}`}
+
onInput={(e) => {
+
const value = e.currentTarget.value;
+
if (!value || isRecordKey(value)) setRkeyError("");
+
else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -");
+
}}
/>
</div>
+
<Show when={collectionError() || rkeyError()}>
+
<div class="text-xs text-red-500 dark:text-red-400">
+
<div>{collectionError()}</div>
+
<div>{rkeyError()}</div>
+
</div>
+
</Show>
</Show>
<div class="min-h-0 flex-1">
<Editor
+2 -3
src/components/json.tsx
···
-
import { isCid, isDid, isNsid, Nsid } from "@atcute/lexicons/syntax";
import { A, useNavigate, useParams } from "@solidjs/router";
import { createEffect, createSignal, ErrorBoundary, For, on, Show } from "solid-js";
import { resolveLexiconAuthority } from "../utils/api";
-
import { ATURI_RE } from "../utils/types/at-uri";
import { hideMedia } from "../views/settings";
import { pds } from "./navbar";
import { addNotification, removeNotification } from "./notification";
···
<For each={props.data.split(/(\s)/)}>
{(part) => (
<>
-
{ATURI_RE.test(part) ?
<A class="text-blue-400 hover:underline active:underline" href={`/${part}`}>
{part}
</A>
···
+
import { isCid, isDid, isNsid, isResourceUri, Nsid } from "@atcute/lexicons/syntax";
import { A, useNavigate, useParams } from "@solidjs/router";
import { createEffect, createSignal, ErrorBoundary, For, on, Show } from "solid-js";
import { resolveLexiconAuthority } from "../utils/api";
import { hideMedia } from "../views/settings";
import { pds } from "./navbar";
import { addNotification, removeNotification } from "./notification";
···
<For each={props.data.split(/(\s)/)}>
{(part) => (
<>
+
{isResourceUri(part) ?
<A class="text-blue-400 hover:underline active:underline" href={`/${part}`}>
{part}
</A>
-66
src/utils/types/at-uri.ts
···
-
type Did<TMethod extends string = string> = `did:${TMethod}:${string}`;
-
-
type Nsid = `${string}.${string}.${string}`;
-
-
type RecordKey = string;
-
-
const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
-
-
const NSID_RE =
-
/^[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})?)$/;
-
-
const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/;
-
-
export const ATURI_RE =
-
/^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
-
-
const isDid = (input: unknown): input is Did => {
-
return (
-
typeof input === "string" && input.length >= 7 && input.length <= 2048 && DID_RE.test(input)
-
);
-
};
-
-
const isNsid = (input: unknown): input is Nsid => {
-
return (
-
typeof input === "string" && input.length >= 5 && input.length <= 317 && NSID_RE.test(input)
-
);
-
};
-
-
const isRecordKey = (input: unknown): input is RecordKey => {
-
return (
-
typeof input === "string" &&
-
input.length >= 1 &&
-
input.length <= 512 &&
-
RECORD_KEY_RE.test(input)
-
);
-
};
-
-
export interface AddressedAtUri {
-
repo: Did;
-
collection: Nsid;
-
rkey: string;
-
fragment: string | undefined;
-
}
-
-
export const parseAddressedAtUri = (str: string): AddressedAtUri => {
-
const match = ATURI_RE.exec(str);
-
assert(match !== null, `invalid addressed-at-uri: ${str}`);
-
-
const [, r, c, k, f] = match;
-
assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`);
-
assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`);
-
assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`);
-
-
return {
-
repo: r,
-
collection: c,
-
rkey: k,
-
fragment: f,
-
};
-
};
-
-
function assert(condition: boolean, message: string): asserts condition {
-
if (!condition) {
-
throw new Error(message);
-
}
-
}
···