import { Nsid } from "@atcute/lexicons"; import { AtprotoDid } from "@atcute/lexicons/syntax"; import { A, useLocation, useNavigate } from "@solidjs/router"; import { createEffect, For, Show } from "solid-js"; import { resolveLexiconAuthority } from "../utils/api.js"; import Tooltip from "./tooltip.jsx"; // Style constants const CONTAINER_CLASS = "divide-y divide-neutral-200 rounded-lg border border-neutral-200 bg-neutral-50/50 px-3 dark:divide-neutral-700 dark:border-neutral-700 dark:bg-neutral-800/30"; const CARD_CLASS = "flex flex-col gap-2 rounded-lg border border-neutral-200 bg-neutral-50/50 p-3 dark:border-neutral-700 dark:bg-neutral-800/30"; const RESOURCE_COLORS = { repo: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300", rpc: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300", default: "bg-neutral-200 text-neutral-800 dark:bg-neutral-700 dark:text-neutral-300", } as const; const DEF_TYPE_COLORS = { record: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300", query: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300", procedure: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300", subscription: "bg-pink-100 text-pink-800 dark:bg-pink-900/30 dark:text-pink-300", object: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", token: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300", "permission-set": "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300", default: "bg-neutral-200 text-neutral-800 dark:bg-neutral-700 dark:text-neutral-300", } as const; // Utility functions const hasConstraints = (property: LexiconProperty | LexiconDef) => property.minLength !== undefined || property.maxLength !== undefined || property.maxGraphemes !== undefined || property.minGraphemes !== undefined || property.minimum !== undefined || property.maximum !== undefined || property.maxSize !== undefined || property.accept || property.enum || property.const || property.default !== undefined || property.knownValues || property.closed; interface LexiconSchema { lexicon: number; id: string; description?: string; defs: { [key: string]: LexiconDef; }; } interface LexiconPermission { type: "permission"; // NOTE: blob, account, and identity are not supported in lexicon schema context resource: "repo" | "rpc" | "blob" | "account" | "identity"; collection?: string[]; action?: string[]; lxm?: string[]; aud?: string; inheritAud?: boolean; } interface LexiconDef { type: string; description?: string; key?: string; record?: LexiconObject; parameters?: LexiconObject; input?: { encoding: string; schema?: LexiconObject }; output?: { encoding: string; schema?: LexiconObject }; errors?: Array<{ name: string; description?: string }>; properties?: { [key: string]: LexiconProperty }; required?: string[]; nullable?: string[]; maxLength?: number; minLength?: number; maxGraphemes?: number; minGraphemes?: number; items?: LexiconProperty; refs?: string[]; closed?: boolean; enum?: string[]; const?: string; default?: string | number | boolean; minimum?: number; maximum?: number; accept?: string[]; maxSize?: number; knownValues?: string[]; format?: string; // Permission-set fields title?: string; "title:lang"?: { [lang: string]: string }; detail?: string; "detail:lang"?: { [lang: string]: string }; permissions?: LexiconPermission[]; } interface LexiconObject { type: string; description?: string; ref?: string; refs?: string[]; closed?: boolean; properties?: { [key: string]: LexiconProperty }; required?: string[]; nullable?: string[]; } interface LexiconProperty { type: string; description?: string; ref?: string; refs?: string[]; closed?: boolean; format?: string; items?: LexiconProperty; minLength?: number; maxLength?: number; maxGraphemes?: number; minGraphemes?: number; minimum?: number; maximum?: number; enum?: string[]; const?: string | boolean | number; default?: string | number | boolean; knownValues?: string[]; accept?: string[]; maxSize?: number; } const TypeBadge = (props: { type: string; format?: string; refType?: string }) => { const navigate = useNavigate(); const displayType = props.refType ? props.refType.replace(/^#/, "") : props.format ? `${props.type}:${props.format}` : props.type; const isLocalRef = () => props.refType?.startsWith("#"); const isExternalRef = () => props.refType && !props.refType.startsWith("#"); const handleClick = async () => { if (isLocalRef()) { const defName = props.refType!.slice(1); window.history.replaceState(null, "", `#schema:${defName}`); const element = document.getElementById(`def-${defName}`); if (element) { element.scrollIntoView({ behavior: "instant", block: "start" }); } } else if (isExternalRef()) { try { const [nsid, anchor] = props.refType!.split("#"); const authority = await resolveLexiconAuthority(nsid as Nsid); const hash = anchor ? `#schema:${anchor}` : "#schema"; navigate(`/at://${authority}/com.atproto.lexicon.schema/${nsid}${hash}`); } catch (err) { console.error("Failed to resolve lexicon authority:", err); } } }; return ( {displayType} } > ); }; const UnionBadges = (props: { refs: string[] }) => (
{(refType) => }
); const ConstraintsList = (props: { property: LexiconProperty }) => { const valueClass = "text-neutral-600 dark:text-neutral-400"; return (
minLength: {props.property.minLength} maxLength: {props.property.maxLength} maxGraphemes: {props.property.maxGraphemes} minGraphemes: {props.property.minGraphemes} min: {props.property.minimum} max: {props.property.maximum} maxSize: {props.property.maxSize} accept: [{props.property.accept!.join(", ")}] enum: [{props.property.enum!.join(", ")}] const: {props.property.const?.toString()} default: {JSON.stringify(props.property.default)} knownValues: [{props.property.knownValues!.join(", ")}] closed: true
); }; const PropertyRow = (props: { name: string; property: LexiconProperty; required?: boolean; hideNameType?: boolean; }) => { return (
{props.name} union required
items: union

{props.property.description}

); }; const NsidLink = (props: { nsid: string }) => { const navigate = useNavigate(); const handleClick = async () => { try { const authority = await resolveLexiconAuthority(props.nsid as Nsid); navigate(`/at://${authority}/com.atproto.lexicon.schema/${props.nsid}#schema`); } catch (err) { console.error("Failed to resolve lexicon authority:", err); } }; return ( ); }; const resourceColor = (resource: string) => RESOURCE_COLORS[resource as keyof typeof RESOURCE_COLORS] || RESOURCE_COLORS.default; const SchemaSection = (props: { title: string; encoding: string; schema?: LexiconObject }) => { return (

{props.title}

Encoding: {props.encoding}
Schema:
Schema (union):
0}>
{([name, property]) => ( )}
); }; const PermissionRow = (props: { permission: LexiconPermission; index: number }) => { return (
#{props.index + 1} {props.permission.resource}
{/* Collections (for repo resource) */} 0}>
Collections:
{(col) => }
{/* Actions */} 0}>
Actions:
{(action) => ( {action} )}
{/* LXM (for rpc resource) */} 0}>
Lexicon Methods:
{(method) => }
{/* Audience */}
Audience: {props.permission.aud}
{/* Inherit Audience */}
Inherit Audience: true
); }; const DefSection = (props: { name: string; def: LexiconDef }) => { const defTypeColor = () => DEF_TYPE_COLORS[props.def.type as keyof typeof DEF_TYPE_COLORS] || DEF_TYPE_COLORS.default; const hasDefContent = () => props.def.refs || props.def.items || hasConstraints(props.def); return (
{props.name === "main" ? "Main Definition" : props.name} {props.def.type.replace("-", " ")}

{props.def.description}

{/* Record key */}
Record Key: {props.def.key}
{/* Permission-set: Title and Detail */}
Title {props.def.title}
Localized Titles
{([lang, text]) => (
{lang} {text}
)}
Detail

{props.def.detail}

Localized Details
{([lang, text]) => (
{lang}

{text}

)}
{/* Permission-set: Permissions list */} p.resource === "repo" || p.resource === "rpc") .length > 0 } >

Permissions

p.resource === "repo" || p.resource === "rpc", )} > {(permission, index) => }
{/* Properties (for record/object types) */} 0} >

Properties

{([name, property]) => ( )}
{/* Parameters (for query/procedure) */} 0 } >

Parameters

{([name, property]) => ( )}
{/* Input */} {/* Output */} {/* Errors */} 0}>

Errors

{(error) => (
{error.name}

{error.description}

)}
{/* Other Definitions */}
); }; export const LexiconSchemaView = (props: { schema: LexiconSchema; authority?: AtprotoDid }) => { const location = useLocation(); // Handle scrolling to a definition when hash is like #schema:definitionName createEffect(() => { const hash = location.hash; if (hash.startsWith("#schema:")) { const defName = hash.slice(8); requestAnimationFrame(() => { const element = document.getElementById(`def-${defName}`); if (element) element.scrollIntoView({ behavior: "instant", block: "start" }); }); } }); return (
{/* Header */}

{props.schema.id}

Lexicon version: {props.schema.lexicon}

{props.schema.description}

{/* Definitions */}
{([name, def]) => }
); };