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 (
{props.property.description}
{props.def.description}
{props.def.detail}
{text}
{error.description}