@oaspect/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/dist/api-reference.js +476 -0
- package/dist/api-reference.js.map +1 -0
- package/dist/auth-panel.js +116 -0
- package/dist/auth-panel.js.map +1 -0
- package/dist/code-block.js +105 -0
- package/dist/code-block.js.map +1 -0
- package/dist/i18n/ar.js +89 -0
- package/dist/i18n/ar.js.map +1 -0
- package/dist/i18n/context.js +50 -0
- package/dist/i18n/context.js.map +1 -0
- package/dist/i18n/en.js +89 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.js +37 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/tr.js +89 -0
- package/dist/i18n/tr.js.map +1 -0
- package/dist/icons.js +55 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/intro.js +132 -0
- package/dist/intro.js.map +1 -0
- package/dist/lazy.js +41 -0
- package/dist/lazy.js.map +1 -0
- package/dist/logo.js +12 -0
- package/dist/logo.js.map +1 -0
- package/dist/markdown.js +170 -0
- package/dist/markdown.js.map +1 -0
- package/dist/method-badge.js +37 -0
- package/dist/method-badge.js.map +1 -0
- package/dist/models.js +27 -0
- package/dist/models.js.map +1 -0
- package/dist/oaspect.css +1703 -0
- package/dist/operation.js +267 -0
- package/dist/operation.js.map +1 -0
- package/dist/samples.js +129 -0
- package/dist/samples.js.map +1 -0
- package/dist/schema-tree.js +160 -0
- package/dist/schema-tree.js.map +1 -0
- package/dist/send.js +58 -0
- package/dist/send.js.map +1 -0
- package/dist/sidebar.js +125 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/spec-context.js +23 -0
- package/dist/spec-context.js.map +1 -0
- package/dist/storage.js +30 -0
- package/dist/storage.js.map +1 -0
- package/dist/try-it.js +277 -0
- package/dist/try-it.js.map +1 -0
- package/package.json +66 -0
- package/tailwind.css +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema-tree.jsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { childrenOf, constraintsOf, modelAnchor, resolveSchema, typeLabel } from \"@oaspect/core\";\nimport { useLocalized, useT } from \"./i18n/context\";\nimport Markdown from \"./markdown\";\nimport { useSpec } from \"./spec-context\";\nimport { ChevronIcon } from \"./icons\";\n\n// Chips hold technical values (patterns, dates, enums): always LTR and\n// isolated so an RTL UI does not reorder their characters.\nfunction Chip({ children, className = \"\" }) {\n return (\n <span dir=\"ltr\" className={`rounded-md bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground ${className}`}>\n {children}\n </span>\n );\n}\n\nconst formatValue = (value) => (typeof value === \"string\" ? `\"${value}\"` : JSON.stringify(value));\n\n// Type line, flags, description, enum and constraints for one field.\nexport function FieldDetails({ schema, name, description, required, deprecated, extra }) {\n const spec = useSpec();\n const t = useT();\n const localized = useLocalized();\n const constraints = constraintsOf(schema);\n const text = description ?? localized(schema, \"description\");\n\n return (\n <>\n <span dir=\"ltr\" className=\"font-mono text-xs text-muted-foreground\">\n {name && schema.properties ? (\n <a href={`#${modelAnchor(name)}`} className=\"hover:text-primary\">\n {typeLabel(spec, schema, name)}\n </a>\n ) : (\n typeLabel(spec, schema, name)\n )}\n </span>\n {required && <span className=\"text-[11px] font-medium text-rose-700 dark:text-rose-400\">required</span>}\n {schema.nullable && <Chip>nullable</Chip>}\n {schema.readOnly && <Chip>read-only</Chip>}\n {schema.writeOnly && <Chip>write-only</Chip>}\n {(deprecated || schema.deprecated) && (\n <Chip className=\"!bg-amber-500/15 !text-amber-700 dark:!text-amber-300\">deprecated</Chip>\n )}\n {extra}\n\n {(text || schema.enum || constraints.length > 0 || schema.pattern || schema.default !== undefined || schema.example !== undefined) && (\n <div className=\"basis-full space-y-2 pt-1\">\n <Markdown source={text} className=\"text-muted-foreground\" />\n {schema.enum && (\n <div className=\"flex flex-wrap items-center gap-1 text-xs text-muted-foreground\">\n <span>{t(\"schema.values\")}</span>\n {schema.enum.map((value) => (\n <Chip key={String(value)}>{formatValue(value)}</Chip>\n ))}\n </div>\n )}\n {(constraints.length > 0 || schema.pattern) && (\n <div className=\"flex flex-wrap gap-1\">\n {constraints.map((item) => (\n <Chip key={item}>{item}</Chip>\n ))}\n {schema.pattern && <Chip>pattern: {schema.pattern}</Chip>}\n </div>\n )}\n {schema.default !== undefined && (\n <div className=\"text-xs text-muted-foreground\">\n {t(\"schema.default\")} <Chip>{formatValue(schema.default)}</Chip>\n </div>\n )}\n {schema.example !== undefined && (\n <div className=\"text-xs text-muted-foreground\">\n {t(\"schema.example\")} <Chip>{formatValue(schema.example)}</Chip>\n </div>\n )}\n </div>\n )}\n </>\n );\n}\n\nfunction PropertyRow({ name, raw, required, depth, seen }) {\n const spec = useSpec();\n const { schema, name: refName } = resolveSchema(spec, raw);\n const circular = refName && seen.has(refName);\n const nested = circular ? null : childrenOf(spec, schema);\n const [open, setOpen] = useState(false);\n const t = useT();\n\n return (\n <div className=\"border-t border-border py-3 first:border-t-0\">\n <div className=\"flex flex-wrap items-baseline gap-x-2 gap-y-1\">\n {nested ? (\n <button\n type=\"button\"\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n className=\"flex items-baseline gap-1 font-mono text-sm font-semibold hover:text-primary\"\n >\n <span className={`inline-flex self-center transition ${open ? \"rotate-90\" : \"rtl:-scale-x-100\"}`}>\n <ChevronIcon className=\"size-3\" />\n </span>\n {name}\n </button>\n ) : (\n <span className=\"font-mono text-sm font-semibold\">{name}</span>\n )}\n <FieldDetails\n schema={schema}\n name={refName}\n required={required}\n extra={circular && <Chip>↻ {t(\"schema.circular\")}</Chip>}\n />\n </div>\n {nested && open && (\n <div className=\"ms-1 mt-3 border-s-2 border-border ps-4\">\n <SchemaBody schema={schema} nested={nested} depth={depth + 1} seen={refName ? new Set(seen).add(refName) : seen} />\n </div>\n )}\n </div>\n );\n}\n\nfunction SchemaBody({ schema, nested, depth, seen }) {\n const spec = useSpec();\n const [variant, setVariant] = useState(0);\n const t = useT();\n\n if (nested.kind === \"properties\") {\n const required = new Set(schema.required ?? []);\n return (\n <div>\n {Object.entries(schema.properties).map(([key, value]) => (\n <PropertyRow key={key} name={key} raw={value} required={required.has(key)} depth={depth} seen={seen} />\n ))}\n </div>\n );\n }\n\n if (nested.kind === \"items\" || nested.kind === \"map\") {\n const inner = childrenOf(spec, nested.schema);\n return (\n <div>\n <p className=\"pb-2 text-xs text-muted-foreground\">\n {nested.kind === \"items\" ? t(\"schema.arrayItem\") : t(\"schema.mapValue\")}: <span dir=\"ltr\" className=\"font-mono\">{nested.name ?? typeLabel(spec, nested.schema)}</span>\n </p>\n <SchemaBody\n schema={nested.schema}\n nested={inner}\n depth={depth}\n seen={nested.name ? new Set(seen).add(nested.name) : seen}\n />\n </div>\n );\n }\n\n // oneOf / anyOf\n const current = nested.variants[variant] ?? nested.variants[0];\n const inner = childrenOf(spec, current.schema);\n return (\n <div>\n <div className=\"flex flex-wrap items-center gap-1 pb-2\">\n <span className=\"text-xs text-muted-foreground\">{nested.kind}:</span>\n {nested.variants.map((item, index) => (\n <button\n key={index}\n type=\"button\"\n onClick={() => setVariant(index)}\n className={`rounded-md border px-2 py-0.5 font-mono text-[11px] transition ${\n index === variant ? \"border-primary text-primary\" : \"border-border text-muted-foreground hover:text-foreground\"\n }`}\n >\n {item.name ?? item.schema.title ?? typeLabel(spec, item.schema)}\n </button>\n ))}\n </div>\n {inner ? (\n <SchemaBody schema={current.schema} nested={inner} depth={depth} seen={seen} />\n ) : (\n <div className=\"flex flex-wrap items-baseline gap-2 py-2\">\n <FieldDetails schema={current.schema} name={current.name} />\n </div>\n )}\n </div>\n );\n}\n\n// Renders a schema as an expandable field tree (objects, arrays, oneOf...).\n// `modelName` seeds cycle detection when rendering a component schema directly.\nexport default function SchemaTree({ schema: raw, modelName }) {\n const spec = useSpec();\n const localized = useLocalized();\n const resolved = resolveSchema(spec, raw);\n const { schema } = resolved;\n const name = resolved.name ?? modelName;\n const nested = childrenOf(spec, schema);\n const seen = name ? new Set([name]) : new Set();\n\n if (!nested) {\n return (\n <div className=\"flex flex-wrap items-baseline gap-2 py-2\">\n <FieldDetails schema={schema} name={name} />\n </div>\n );\n }\n\n return (\n <div>\n {schema.description && <Markdown source={localized(schema, \"description\")} className=\"pb-2 text-muted-foreground\" />}\n <SchemaBody schema={schema} nested={nested} depth={0} seen={seen} />\n </div>\n );\n}\n"],"mappings":"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","names":["inner"]}
|
package/dist/send.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function toFormData(form, files) {
|
|
2
|
+
const data = new FormData();
|
|
3
|
+
form.forEach((field, index) => {
|
|
4
|
+
if (field.file) data.append(field.name, files[index], field.file.name);
|
|
5
|
+
else data.append(field.name, field.value ?? "");
|
|
6
|
+
});
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
async function toBase64(file) {
|
|
10
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
11
|
+
let binary = "";
|
|
12
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) {
|
|
13
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
14
|
+
}
|
|
15
|
+
return btoa(binary);
|
|
16
|
+
}
|
|
17
|
+
async function sendDirect(request, files) {
|
|
18
|
+
const started = performance.now();
|
|
19
|
+
const response = await fetch(request.url, {
|
|
20
|
+
method: request.method,
|
|
21
|
+
headers: request.headers,
|
|
22
|
+
body: request.form ? toFormData(request.form, files) : request.body
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
status: response.status,
|
|
26
|
+
statusText: response.statusText,
|
|
27
|
+
duration: Math.round(performance.now() - started),
|
|
28
|
+
headers: [...response.headers.entries()],
|
|
29
|
+
body: await response.text()
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function sendViaProxy(proxyUrl, request, files) {
|
|
33
|
+
const payload = { ...request };
|
|
34
|
+
if (request.form) {
|
|
35
|
+
payload.form = await Promise.all(
|
|
36
|
+
request.form.map(
|
|
37
|
+
async (field, index) => field.file ? { name: field.name, file: { ...field.file, data: await toBase64(files[index]) } } : field
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const response = await fetch(proxyUrl, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify(payload)
|
|
45
|
+
});
|
|
46
|
+
const result = await response.json();
|
|
47
|
+
if (result.error) throw new Error(result.error);
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function sendRequest(request, { proxyUrl, files = [] } = {}) {
|
|
51
|
+
return proxyUrl ? sendViaProxy(proxyUrl, request, files) : sendDirect(request, files);
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
sendDirect,
|
|
55
|
+
sendRequest,
|
|
56
|
+
sendViaProxy
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=send.js.map
|
package/dist/send.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/send.js"],"sourcesContent":["// Sending requests from the viewer: straight from the browser (needs CORS on\n// the API) or through the host's proxy endpoint (proxyUrl). Used by the Try\n// dialog and by OAuth2 token requests.\n\n// FormData for multipart requests: text parts plus the chosen File objects\n// (aligned with request.form).\nfunction toFormData(form, files) {\n const data = new FormData();\n form.forEach((field, index) => {\n if (field.file) data.append(field.name, files[index], field.file.name);\n else data.append(field.name, field.value ?? \"\");\n });\n return data;\n}\n\nasync function toBase64(file) {\n const bytes = new Uint8Array(await file.arrayBuffer());\n let binary = \"\";\n for (let offset = 0; offset < bytes.length; offset += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));\n }\n return btoa(binary);\n}\n\n// Sends the request straight from the browser (needs CORS on the API).\nexport async function sendDirect(request, files) {\n const started = performance.now();\n const response = await fetch(request.url, {\n method: request.method,\n headers: request.headers,\n body: request.form ? toFormData(request.form, files) : request.body,\n });\n return {\n status: response.status,\n statusText: response.statusText,\n duration: Math.round(performance.now() - started),\n headers: [...response.headers.entries()],\n body: await response.text(),\n };\n}\n\n// Relays the request through the host's proxy endpoint (see proxyUrl).\n// Multipart files travel base64-encoded inside the JSON payload.\nexport async function sendViaProxy(proxyUrl, request, files) {\n const payload = { ...request };\n if (request.form) {\n payload.form = await Promise.all(\n request.form.map(async (field, index) =>\n field.file ? { name: field.name, file: { ...field.file, data: await toBase64(files[index]) } } : field,\n ),\n );\n }\n const response = await fetch(proxyUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n const result = await response.json();\n if (result.error) throw new Error(result.error);\n return result;\n}\n\n/** Sends `request` through the proxy when `proxyUrl` is set, else directly. */\nexport function sendRequest(request, { proxyUrl, files = [] } = {}) {\n return proxyUrl ? sendViaProxy(proxyUrl, request, files) : sendDirect(request, files);\n}\n"],"mappings":"AAMA,SAAS,WAAW,MAAM,OAAO;AAC/B,QAAM,OAAO,IAAI,SAAS;AAC1B,OAAK,QAAQ,CAAC,OAAO,UAAU;AAC7B,QAAI,MAAM,KAAM,MAAK,OAAO,MAAM,MAAM,MAAM,KAAK,GAAG,MAAM,KAAK,IAAI;AAAA,QAChE,MAAK,OAAO,MAAM,MAAM,MAAM,SAAS,EAAE;AAAA,EAChD,CAAC;AACD,SAAO;AACT;AAEA,eAAe,SAAS,MAAM;AAC5B,QAAM,QAAQ,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;AACrD,MAAI,SAAS;AACb,WAAS,SAAS,GAAG,SAAS,MAAM,QAAQ,UAAU,OAAQ;AAC5D,cAAU,OAAO,aAAa,GAAG,MAAM,SAAS,QAAQ,SAAS,KAAM,CAAC;AAAA,EAC1E;AACA,SAAO,KAAK,MAAM;AACpB;AAGA,eAAsB,WAAW,SAAS,OAAO;AAC/C,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,WAAW,MAAM,MAAM,QAAQ,KAAK;AAAA,IACxC,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ;AAAA,IACjB,MAAM,QAAQ,OAAO,WAAW,QAAQ,MAAM,KAAK,IAAI,QAAQ;AAAA,EACjE,CAAC;AACD,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB,YAAY,SAAS;AAAA,IACrB,UAAU,KAAK,MAAM,YAAY,IAAI,IAAI,OAAO;AAAA,IAChD,SAAS,CAAC,GAAG,SAAS,QAAQ,QAAQ,CAAC;AAAA,IACvC,MAAM,MAAM,SAAS,KAAK;AAAA,EAC5B;AACF;AAIA,eAAsB,aAAa,UAAU,SAAS,OAAO;AAC3D,QAAM,UAAU,EAAE,GAAG,QAAQ;AAC7B,MAAI,QAAQ,MAAM;AAChB,YAAQ,OAAO,MAAM,QAAQ;AAAA,MAC3B,QAAQ,KAAK;AAAA,QAAI,OAAO,OAAO,UAC7B,MAAM,OAAO,EAAE,MAAM,MAAM,MAAM,MAAM,EAAE,GAAG,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM,KAAK,CAAC,EAAE,EAAE,IAAI;AAAA,MACnG;AAAA,IACF;AAAA,EACF;AACA,QAAM,WAAW,MAAM,MAAM,UAAU;AAAA,IACrC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,EAC9B,CAAC;AACD,QAAM,SAAS,MAAM,SAAS,KAAK;AACnC,MAAI,OAAO,MAAO,OAAM,IAAI,MAAM,OAAO,KAAK;AAC9C,SAAO;AACT;AAGO,SAAS,YAAY,SAAS,EAAE,UAAU,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG;AAClE,SAAO,WAAW,aAAa,UAAU,SAAS,KAAK,IAAI,WAAW,SAAS,KAAK;AACtF;","names":[]}
|
package/dist/sidebar.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useLocalized, useT } from "./i18n/context.js";
|
|
5
|
+
import MethodBadge from "./method-badge.js";
|
|
6
|
+
import { ChevronIcon } from "./icons.js";
|
|
7
|
+
function matches(operation, query, localized) {
|
|
8
|
+
return [
|
|
9
|
+
localized(operation, "summary"),
|
|
10
|
+
operation.summary,
|
|
11
|
+
localized(operation, "description"),
|
|
12
|
+
operation.description,
|
|
13
|
+
operation.path,
|
|
14
|
+
operation.method,
|
|
15
|
+
operation.operationId,
|
|
16
|
+
tagNames(operation),
|
|
17
|
+
...operation.parameters.map((parameter) => parameter.name)
|
|
18
|
+
].filter(Boolean).some((value) => String(value).toLowerCase().includes(query));
|
|
19
|
+
}
|
|
20
|
+
const tagNames = (operation) => operation.tags.join(" ");
|
|
21
|
+
const cssEscape = (value) => globalThis.CSS?.escape ? CSS.escape(value) : String(value).replace(/["\\]/g, "\\$&");
|
|
22
|
+
function matchesSchema(item, query, localized) {
|
|
23
|
+
return [item.name, localized(item.schema, "description"), item.schema.description, item.schema.title].filter(Boolean).some((value) => String(value).toLowerCase().includes(query));
|
|
24
|
+
}
|
|
25
|
+
function Sidebar({ model, activeAnchor, searchRef, onNavigate, showModels = true }) {
|
|
26
|
+
const [query, setQuery] = useState("");
|
|
27
|
+
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
28
|
+
const [modelsOpen, setModelsOpen] = useState(false);
|
|
29
|
+
const listRef = useRef(null);
|
|
30
|
+
const t = useT();
|
|
31
|
+
const localized = useLocalized();
|
|
32
|
+
const normalized = query.trim().toLowerCase();
|
|
33
|
+
const groups = useMemo(
|
|
34
|
+
() => model.webhooks?.length ? [...model.tags, { name: "webhooks", label: t("webhooks.title"), anchor: "webhooks", operations: model.webhooks }] : model.tags,
|
|
35
|
+
[model.tags, model.webhooks, t]
|
|
36
|
+
);
|
|
37
|
+
const tags = useMemo(() => {
|
|
38
|
+
if (!normalized) return groups;
|
|
39
|
+
return groups.map((tag) => ({ ...tag, operations: tag.operations.filter((op) => matches(op, normalized, localized)) })).filter((tag) => tag.operations.length > 0);
|
|
40
|
+
}, [groups, normalized, localized]);
|
|
41
|
+
const schemas = useMemo(
|
|
42
|
+
() => normalized ? model.schemas.filter((item) => matchesSchema(item, normalized, localized)) : model.schemas,
|
|
43
|
+
[model.schemas, normalized, localized]
|
|
44
|
+
);
|
|
45
|
+
const activeTag = groups.find(
|
|
46
|
+
(tag) => tag.anchor === activeAnchor || tag.operations.some((op) => op.anchor === activeAnchor)
|
|
47
|
+
)?.name;
|
|
48
|
+
const inModels = activeAnchor?.startsWith("model/");
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
listRef.current?.querySelector(`[data-nav="${cssEscape(activeAnchor ?? "")}"]`)?.scrollIntoView({ block: "nearest" });
|
|
51
|
+
}, [activeAnchor]);
|
|
52
|
+
function toggle(name) {
|
|
53
|
+
setExpanded((current) => {
|
|
54
|
+
const next = new Set(current);
|
|
55
|
+
if (next.has(name)) next.delete(name);
|
|
56
|
+
else next.add(name);
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const linkClass = (anchor) => `flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition ${anchor === activeAnchor ? "bg-primary/10 font-medium text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`;
|
|
61
|
+
return /* @__PURE__ */ jsxs("nav", { className: "flex h-full flex-col", "aria-label": t("sidebar.label"), children: [
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
"input",
|
|
65
|
+
{
|
|
66
|
+
ref: searchRef,
|
|
67
|
+
type: "search",
|
|
68
|
+
value: query,
|
|
69
|
+
onChange: (event) => setQuery(event.target.value),
|
|
70
|
+
placeholder: t("sidebar.search"),
|
|
71
|
+
className: "w-full rounded-lg border border-border bg-background py-1.5 ps-3 pe-12 text-sm outline-none focus:border-primary"
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsx("kbd", { className: "pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 rounded border border-border px-1.5 text-[10px] text-muted-foreground", children: "\u2318K" })
|
|
75
|
+
] }) }),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "min-h-0 flex-1 space-y-0.5 overflow-y-auto px-3 pb-6", children: [
|
|
77
|
+
!normalized && /* @__PURE__ */ jsx("a", { href: "#introduction", "data-nav": "introduction", onClick: onNavigate, className: linkClass("introduction"), children: t("sidebar.introduction") }),
|
|
78
|
+
tags.map((tag) => {
|
|
79
|
+
const open = Boolean(normalized) || expanded.has(tag.name) || tag.name === activeTag;
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
81
|
+
/* @__PURE__ */ jsxs(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
type: "button",
|
|
85
|
+
onClick: () => toggle(tag.name),
|
|
86
|
+
"aria-expanded": open,
|
|
87
|
+
className: "flex w-full items-center justify-between rounded-md px-2 py-1.5 text-start text-sm font-medium hover:bg-muted",
|
|
88
|
+
children: [
|
|
89
|
+
tag.label ?? localized(tag, "name"),
|
|
90
|
+
/* @__PURE__ */ jsx("span", { className: `text-muted-foreground transition ${open ? "rotate-90" : "rtl:-scale-x-100"}`, children: /* @__PURE__ */ jsx(ChevronIcon, {}) })
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
open && /* @__PURE__ */ jsx("div", { className: "mb-2 ms-2 space-y-0.5 border-s border-border ps-2", children: tag.operations.map((op) => /* @__PURE__ */ jsxs("a", { href: `#${op.anchor}`, "data-nav": op.anchor, onClick: onNavigate, className: linkClass(op.anchor), children: [
|
|
95
|
+
/* @__PURE__ */ jsx(MethodBadge, { method: op.method, size: "sm" }),
|
|
96
|
+
/* @__PURE__ */ jsx("span", { dir: "auto", title: localized(op, "summary") || op.path, className: `truncate text-start ${op.deprecated ? "line-through" : ""}`, children: localized(op, "summary") || op.path })
|
|
97
|
+
] }, op.anchor)) })
|
|
98
|
+
] }, tag.name);
|
|
99
|
+
}),
|
|
100
|
+
showModels && schemas.length > 0 && /* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
|
|
101
|
+
/* @__PURE__ */ jsxs(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
onClick: () => setModelsOpen(!modelsOpen),
|
|
106
|
+
className: "flex w-full items-center justify-between rounded-md px-2 py-1.5 text-start text-xs font-semibold uppercase tracking-wider text-muted-foreground hover:bg-muted",
|
|
107
|
+
children: [
|
|
108
|
+
t("models.title"),
|
|
109
|
+
" (",
|
|
110
|
+
schemas.length,
|
|
111
|
+
")",
|
|
112
|
+
/* @__PURE__ */ jsx("span", { className: `transition ${modelsOpen || normalized || inModels ? "rotate-90" : "rtl:-scale-x-100"}`, children: /* @__PURE__ */ jsx(ChevronIcon, {}) })
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
(modelsOpen || normalized || inModels) && /* @__PURE__ */ jsx("div", { className: "ms-2 space-y-0.5 border-s border-border ps-2", children: schemas.map((item) => /* @__PURE__ */ jsx("a", { href: `#${item.anchor}`, "data-nav": item.anchor, onClick: onNavigate, className: linkClass(item.anchor), children: /* @__PURE__ */ jsx("span", { dir: "ltr", className: "truncate font-mono text-xs", children: item.name }) }, item.name)) })
|
|
117
|
+
] }),
|
|
118
|
+
normalized && tags.length === 0 && schemas.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-2 py-4 text-sm text-muted-foreground", children: t("sidebar.noResults") })
|
|
119
|
+
] })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
Sidebar as default
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=sidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sidebar.jsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useLocalized, useT } from \"./i18n/context\";\nimport MethodBadge from \"./method-badge\";\nimport { ChevronIcon } from \"./icons\";\n\n// Matches the translated and the authored text (so English search terms\n// keep working in another UI language), descriptions and parameter names.\n// Descriptions are searched as plain text; \":::lang\" markers do not matter.\nfunction matches(operation, query, localized) {\n return [\n localized(operation, \"summary\"),\n operation.summary,\n localized(operation, \"description\"),\n operation.description,\n operation.path,\n operation.method,\n operation.operationId,\n tagNames(operation),\n ...operation.parameters.map((parameter) => parameter.name),\n ]\n .filter(Boolean)\n .some((value) => String(value).toLowerCase().includes(query));\n}\n\nconst tagNames = (operation) => operation.tags.join(\" \");\n\n// CSS.escape where available (not in every DOM implementation).\nconst cssEscape = (value) => (globalThis.CSS?.escape ? CSS.escape(value) : String(value).replace(/[\"\\\\]/g, \"\\\\$&\"));\n\nfunction matchesSchema(item, query, localized) {\n return [item.name, localized(item.schema, \"description\"), item.schema.description, item.schema.title]\n .filter(Boolean)\n .some((value) => String(value).toLowerCase().includes(query));\n}\n\nexport default function Sidebar({ model, activeAnchor, searchRef, onNavigate, showModels = true }) {\n const [query, setQuery] = useState(\"\");\n const [expanded, setExpanded] = useState(() => new Set());\n const [modelsOpen, setModelsOpen] = useState(false);\n const listRef = useRef(null);\n const t = useT();\n const localized = useLocalized();\n const normalized = query.trim().toLowerCase();\n\n // Webhooks show up as one more group after the tags.\n const groups = useMemo(\n () => (model.webhooks?.length ? [...model.tags, { name: \"webhooks\", label: t(\"webhooks.title\"), anchor: \"webhooks\", operations: model.webhooks }] : model.tags),\n [model.tags, model.webhooks, t],\n );\n\n const tags = useMemo(() => {\n if (!normalized) return groups;\n return groups\n .map((tag) => ({ ...tag, operations: tag.operations.filter((op) => matches(op, normalized, localized)) }))\n .filter((tag) => tag.operations.length > 0);\n }, [groups, normalized, localized]);\n\n const schemas = useMemo(\n () => (normalized ? model.schemas.filter((item) => matchesSchema(item, normalized, localized)) : model.schemas),\n [model.schemas, normalized, localized],\n );\n\n const activeTag = groups.find(\n (tag) => tag.anchor === activeAnchor || tag.operations.some((op) => op.anchor === activeAnchor),\n )?.name;\n const inModels = activeAnchor?.startsWith(\"model/\");\n\n useEffect(() => {\n listRef.current\n ?.querySelector(`[data-nav=\"${cssEscape(activeAnchor ?? \"\")}\"]`)\n ?.scrollIntoView({ block: \"nearest\" });\n }, [activeAnchor]);\n\n function toggle(name) {\n setExpanded((current) => {\n const next = new Set(current);\n if (next.has(name)) next.delete(name);\n else next.add(name);\n return next;\n });\n }\n\n const linkClass = (anchor) =>\n `flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition ${\n anchor === activeAnchor ? \"bg-primary/10 font-medium text-primary\" : \"text-muted-foreground hover:bg-muted hover:text-foreground\"\n }`;\n\n return (\n <nav className=\"flex h-full flex-col\" aria-label={t(\"sidebar.label\")}>\n <div className=\"p-3\">\n <div className=\"relative\">\n <input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t(\"sidebar.search\")}\n className=\"w-full rounded-lg border border-border bg-background py-1.5 ps-3 pe-12 text-sm outline-none focus:border-primary\"\n />\n <kbd className=\"pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 rounded border border-border px-1.5 text-[10px] text-muted-foreground\">\n ⌘K\n </kbd>\n </div>\n </div>\n\n <div ref={listRef} className=\"min-h-0 flex-1 space-y-0.5 overflow-y-auto px-3 pb-6\">\n {!normalized && (\n <a href=\"#introduction\" data-nav=\"introduction\" onClick={onNavigate} className={linkClass(\"introduction\")}>\n {t(\"sidebar.introduction\")}\n </a>\n )}\n\n {tags.map((tag) => {\n const open = Boolean(normalized) || expanded.has(tag.name) || tag.name === activeTag;\n return (\n <div key={tag.name}>\n <button\n type=\"button\"\n onClick={() => toggle(tag.name)}\n aria-expanded={open}\n className=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-start text-sm font-medium hover:bg-muted\"\n >\n {tag.label ?? localized(tag, \"name\")}\n <span className={`text-muted-foreground transition ${open ? \"rotate-90\" : \"rtl:-scale-x-100\"}`}>\n <ChevronIcon />\n </span>\n </button>\n {open && (\n <div className=\"mb-2 ms-2 space-y-0.5 border-s border-border ps-2\">\n {tag.operations.map((op) => (\n <a key={op.anchor} href={`#${op.anchor}`} data-nav={op.anchor} onClick={onNavigate} className={linkClass(op.anchor)}>\n <MethodBadge method={op.method} size=\"sm\" />\n <span dir=\"auto\" title={localized(op, \"summary\") || op.path} className={`truncate text-start ${op.deprecated ? \"line-through\" : \"\"}`}>{localized(op, \"summary\") || op.path}</span>\n </a>\n ))}\n </div>\n )}\n </div>\n );\n })}\n\n {showModels && schemas.length > 0 && (\n <div className=\"pt-4\">\n <button\n type=\"button\"\n onClick={() => setModelsOpen(!modelsOpen)}\n className=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-start text-xs font-semibold uppercase tracking-wider text-muted-foreground hover:bg-muted\"\n >\n {t(\"models.title\")} ({schemas.length})\n <span className={`transition ${modelsOpen || normalized || inModels ? \"rotate-90\" : \"rtl:-scale-x-100\"}`}>\n <ChevronIcon />\n </span>\n </button>\n {(modelsOpen || normalized || inModels) && (\n <div className=\"ms-2 space-y-0.5 border-s border-border ps-2\">\n {schemas.map((item) => (\n <a key={item.name} href={`#${item.anchor}`} data-nav={item.anchor} onClick={onNavigate} className={linkClass(item.anchor)}>\n <span dir=\"ltr\" className=\"truncate font-mono text-xs\">{item.name}</span>\n </a>\n ))}\n </div>\n )}\n </div>\n )}\n\n {normalized && tags.length === 0 && schemas.length === 0 && (\n <p className=\"px-2 py-4 text-sm text-muted-foreground\">{t(\"sidebar.noResults\")}</p>\n )}\n </div>\n </nav>\n );\n}\n"],"mappings":";AA4FQ,SACE,KADF;AA1FR,SAAS,WAAW,SAAS,QAAQ,gBAAgB;AACrD,SAAS,cAAc,YAAY;AACnC,OAAO,iBAAiB;AACxB,SAAS,mBAAmB;AAK5B,SAAS,QAAQ,WAAW,OAAO,WAAW;AAC5C,SAAO;AAAA,IACL,UAAU,WAAW,SAAS;AAAA,IAC9B,UAAU;AAAA,IACV,UAAU,WAAW,aAAa;AAAA,IAClC,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,SAAS,SAAS;AAAA,IAClB,GAAG,UAAU,WAAW,IAAI,CAAC,cAAc,UAAU,IAAI;AAAA,EAC3D,EACG,OAAO,OAAO,EACd,KAAK,CAAC,UAAU,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,KAAK,CAAC;AAChE;AAEA,MAAM,WAAW,CAAC,cAAc,UAAU,KAAK,KAAK,GAAG;AAGvD,MAAM,YAAY,CAAC,UAAW,WAAW,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,EAAE,QAAQ,UAAU,MAAM;AAEjH,SAAS,cAAc,MAAM,OAAO,WAAW;AAC7C,SAAO,CAAC,KAAK,MAAM,UAAU,KAAK,QAAQ,aAAa,GAAG,KAAK,OAAO,aAAa,KAAK,OAAO,KAAK,EACjG,OAAO,OAAO,EACd,KAAK,CAAC,UAAU,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,KAAK,CAAC;AAChE;AAEe,SAAR,QAAyB,EAAE,OAAO,cAAc,WAAW,YAAY,aAAa,KAAK,GAAG;AACjG,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,MAAM,oBAAI,IAAI,CAAC;AACxD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,UAAU,OAAO,IAAI;AAC3B,QAAM,IAAI,KAAK;AACf,QAAM,YAAY,aAAa;AAC/B,QAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAG5C,QAAM,SAAS;AAAA,IACb,MAAO,MAAM,UAAU,SAAS,CAAC,GAAG,MAAM,MAAM,EAAE,MAAM,YAAY,OAAO,EAAE,gBAAgB,GAAG,QAAQ,YAAY,YAAY,MAAM,SAAS,CAAC,IAAI,MAAM;AAAA,IAC1J,CAAC,MAAM,MAAM,MAAM,UAAU,CAAC;AAAA,EAChC;AAEA,QAAM,OAAO,QAAQ,MAAM;AACzB,QAAI,CAAC,WAAY,QAAO;AACxB,WAAO,OACJ,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,YAAY,IAAI,WAAW,OAAO,CAAC,OAAO,QAAQ,IAAI,YAAY,SAAS,CAAC,EAAE,EAAE,EACxG,OAAO,CAAC,QAAQ,IAAI,WAAW,SAAS,CAAC;AAAA,EAC9C,GAAG,CAAC,QAAQ,YAAY,SAAS,CAAC;AAElC,QAAM,UAAU;AAAA,IACd,MAAO,aAAa,MAAM,QAAQ,OAAO,CAAC,SAAS,cAAc,MAAM,YAAY,SAAS,CAAC,IAAI,MAAM;AAAA,IACvG,CAAC,MAAM,SAAS,YAAY,SAAS;AAAA,EACvC;AAEA,QAAM,YAAY,OAAO;AAAA,IACvB,CAAC,QAAQ,IAAI,WAAW,gBAAgB,IAAI,WAAW,KAAK,CAAC,OAAO,GAAG,WAAW,YAAY;AAAA,EAChG,GAAG;AACH,QAAM,WAAW,cAAc,WAAW,QAAQ;AAElD,YAAU,MAAM;AACd,YAAQ,SACJ,cAAc,cAAc,UAAU,gBAAgB,EAAE,CAAC,IAAI,GAC7D,eAAe,EAAE,OAAO,UAAU,CAAC;AAAA,EACzC,GAAG,CAAC,YAAY,CAAC;AAEjB,WAAS,OAAO,MAAM;AACpB,gBAAY,CAAC,YAAY;AACvB,YAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,UAAI,KAAK,IAAI,IAAI,EAAG,MAAK,OAAO,IAAI;AAAA,UAC/B,MAAK,IAAI,IAAI;AAClB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,CAAC,WACjB,qEACE,WAAW,eAAe,2CAA2C,4DACvE;AAEF,SACE,qBAAC,SAAI,WAAU,wBAAuB,cAAY,EAAE,eAAe,GACjE;AAAA,wBAAC,SAAI,WAAU,OACb,+BAAC,SAAI,WAAU,YACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,UAChD,aAAa,EAAE,gBAAgB;AAAA,UAC/B,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,oBAAC,SAAI,WAAU,qIAAoI,qBAEnJ;AAAA,OACF,GACF;AAAA,IAEA,qBAAC,SAAI,KAAK,SAAS,WAAU,wDAC1B;AAAA,OAAC,cACA,oBAAC,OAAE,MAAK,iBAAgB,YAAS,gBAAe,SAAS,YAAY,WAAW,UAAU,cAAc,GACrG,YAAE,sBAAsB,GAC3B;AAAA,MAGD,KAAK,IAAI,CAAC,QAAQ;AACjB,cAAM,OAAO,QAAQ,UAAU,KAAK,SAAS,IAAI,IAAI,IAAI,KAAK,IAAI,SAAS;AAC3E,eACE,qBAAC,SACC;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,OAAO,IAAI,IAAI;AAAA,cAC9B,iBAAe;AAAA,cACf,WAAU;AAAA,cAET;AAAA,oBAAI,SAAS,UAAU,KAAK,MAAM;AAAA,gBACnC,oBAAC,UAAK,WAAW,oCAAoC,OAAO,cAAc,kBAAkB,IAC1F,8BAAC,eAAY,GACf;AAAA;AAAA;AAAA,UACF;AAAA,UACC,QACC,oBAAC,SAAI,WAAU,qDACZ,cAAI,WAAW,IAAI,CAAC,OACnB,qBAAC,OAAkB,MAAM,IAAI,GAAG,MAAM,IAAI,YAAU,GAAG,QAAQ,SAAS,YAAY,WAAW,UAAU,GAAG,MAAM,GAChH;AAAA,gCAAC,eAAY,QAAQ,GAAG,QAAQ,MAAK,MAAK;AAAA,YAC1C,oBAAC,UAAK,KAAI,QAAO,OAAO,UAAU,IAAI,SAAS,KAAK,GAAG,MAAM,WAAW,uBAAuB,GAAG,aAAa,iBAAiB,EAAE,IAAK,oBAAU,IAAI,SAAS,KAAK,GAAG,MAAK;AAAA,eAFrK,GAAG,MAGX,CACD,GACH;AAAA,aApBM,IAAI,IAsBd;AAAA,MAEJ,CAAC;AAAA,MAEA,cAAc,QAAQ,SAAS,KAC9B,qBAAC,SAAI,WAAU,QACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,CAAC,UAAU;AAAA,YACxC,WAAU;AAAA,YAET;AAAA,gBAAE,cAAc;AAAA,cAAE;AAAA,cAAG,QAAQ;AAAA,cAAO;AAAA,cACrC,oBAAC,UAAK,WAAW,cAAc,cAAc,cAAc,WAAW,cAAc,kBAAkB,IACpG,8BAAC,eAAY,GACf;AAAA;AAAA;AAAA,QACF;AAAA,SACE,cAAc,cAAc,aAC5B,oBAAC,SAAI,WAAU,gDACZ,kBAAQ,IAAI,CAAC,SACZ,oBAAC,OAAkB,MAAM,IAAI,KAAK,MAAM,IAAI,YAAU,KAAK,QAAQ,SAAS,YAAY,WAAW,UAAU,KAAK,MAAM,GACtH,8BAAC,UAAK,KAAI,OAAM,WAAU,8BAA8B,eAAK,MAAK,KAD5D,KAAK,IAEb,CACD,GACH;AAAA,SAEJ;AAAA,MAGD,cAAc,KAAK,WAAW,KAAK,QAAQ,WAAW,KACrD,oBAAC,OAAE,WAAU,2CAA2C,YAAE,mBAAmB,GAAE;AAAA,OAEnF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const SpecContext = createContext(null);
|
|
4
|
+
function useSpec() {
|
|
5
|
+
return useContext(SpecContext);
|
|
6
|
+
}
|
|
7
|
+
const ModelContext = createContext(null);
|
|
8
|
+
function useModel() {
|
|
9
|
+
return useContext(ModelContext);
|
|
10
|
+
}
|
|
11
|
+
const SettingsContext = createContext(null);
|
|
12
|
+
function useSettings() {
|
|
13
|
+
return useContext(SettingsContext);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
ModelContext,
|
|
17
|
+
SettingsContext,
|
|
18
|
+
SpecContext,
|
|
19
|
+
useModel,
|
|
20
|
+
useSettings,
|
|
21
|
+
useSpec
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=spec-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spec-context.js"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\nexport const SpecContext = createContext(null);\n\nexport function useSpec() {\n return useContext(SpecContext);\n}\n\n// The built model (tags, operations, webhooks…), e.g. to resolve links.\nexport const ModelContext = createContext(null);\n\nexport function useModel() {\n return useContext(ModelContext);\n}\n\n// Viewer-wide request settings (selected server, auth token) shared by the\n// code samples and the request runner.\nexport const SettingsContext = createContext(null);\n\nexport function useSettings() {\n return useContext(SettingsContext);\n}\n"],"mappings":";AAEA,SAAS,eAAe,kBAAkB;AAEnC,MAAM,cAAc,cAAc,IAAI;AAEtC,SAAS,UAAU;AACxB,SAAO,WAAW,WAAW;AAC/B;AAGO,MAAM,eAAe,cAAc,IAAI;AAEvC,SAAS,WAAW;AACzB,SAAO,WAAW,YAAY;AAChC;AAIO,MAAM,kBAAkB,cAAc,IAAI;AAE1C,SAAS,cAAc;AAC5B,SAAO,WAAW,eAAe;AACnC;","names":[]}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function access(kind) {
|
|
2
|
+
try {
|
|
3
|
+
return kind === "session" ? window.sessionStorage : window.localStorage;
|
|
4
|
+
} catch {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function createStorage(prefix = "oaspect") {
|
|
9
|
+
const key = (name) => `${prefix}:${name}`;
|
|
10
|
+
return {
|
|
11
|
+
get(name, fallback = null, kind = "local") {
|
|
12
|
+
try {
|
|
13
|
+
return access(kind)?.getItem(key(name)) ?? fallback;
|
|
14
|
+
} catch {
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
set(name, value, kind = "local") {
|
|
19
|
+
try {
|
|
20
|
+
if (value === null || value === void 0 || value === "") access(kind)?.removeItem(key(name));
|
|
21
|
+
else access(kind)?.setItem(key(name), value);
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
createStorage
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage.js"],"sourcesContent":["// Per-browser preferences under a host-chosen prefix (\"oaspect:token\").\n// Storage can be unavailable (private mode, blocked cookies); every access is\n// guarded and settings then simply last for the session.\n\nfunction access(kind) {\n try {\n return kind === \"session\" ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\n\nexport function createStorage(prefix = \"oaspect\") {\n const key = (name) => `${prefix}:${name}`;\n\n return {\n get(name, fallback = null, kind = \"local\") {\n try {\n return access(kind)?.getItem(key(name)) ?? fallback;\n } catch {\n return fallback;\n }\n },\n set(name, value, kind = \"local\") {\n try {\n if (value === null || value === undefined || value === \"\") access(kind)?.removeItem(key(name));\n else access(kind)?.setItem(key(name), value);\n } catch {\n // Not persisted; nothing else to do.\n }\n },\n };\n}\n"],"mappings":"AAIA,SAAS,OAAO,MAAM;AACpB,MAAI;AACF,WAAO,SAAS,YAAY,OAAO,iBAAiB,OAAO;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAc,SAAS,WAAW;AAChD,QAAM,MAAM,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI;AAEvC,SAAO;AAAA,IACL,IAAI,MAAM,WAAW,MAAM,OAAO,SAAS;AACzC,UAAI;AACF,eAAO,OAAO,IAAI,GAAG,QAAQ,IAAI,IAAI,CAAC,KAAK;AAAA,MAC7C,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,IAAI,MAAM,OAAO,OAAO,SAAS;AAC/B,UAAI;AACF,YAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO,IAAI,GAAG,WAAW,IAAI,IAAI,CAAC;AAAA,YACxF,QAAO,IAAI,GAAG,QAAQ,IAAI,IAAI,GAAG,KAAK;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/try-it.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useT } from "./i18n/context.js";
|
|
5
|
+
import { buildRequest, defaultBody, defaultValues, isMultipart } from "@oaspect/core";
|
|
6
|
+
import CodeBlock, { CodeEditor } from "./code-block.js";
|
|
7
|
+
import MethodBadge, { statusTone } from "./method-badge.js";
|
|
8
|
+
import { withAuth } from "./samples.js";
|
|
9
|
+
import { sendDirect, sendViaProxy } from "./send.js";
|
|
10
|
+
import { useSettings, useSpec } from "./spec-context.js";
|
|
11
|
+
import { CloseIcon } from "./icons.js";
|
|
12
|
+
const SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
13
|
+
const LOCATIONS = [
|
|
14
|
+
["path", "Path"],
|
|
15
|
+
["query", "Query"],
|
|
16
|
+
["header", "Header"],
|
|
17
|
+
["cookie", "Cookie"]
|
|
18
|
+
];
|
|
19
|
+
function prettyBody(text) {
|
|
20
|
+
try {
|
|
21
|
+
return { code: JSON.stringify(JSON.parse(text), null, 2), language: "json" };
|
|
22
|
+
} catch {
|
|
23
|
+
return { code: text, language: "text" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function TryIt({ operation, onClose }) {
|
|
27
|
+
const spec = useSpec();
|
|
28
|
+
const { server, authFor, proxyUrl, storage } = useSettings();
|
|
29
|
+
const proxyEnabled = Boolean(proxyUrl);
|
|
30
|
+
const dialog = useRef(null);
|
|
31
|
+
const t = useT();
|
|
32
|
+
const [values, setValues] = useState(() => defaultValues(spec, operation));
|
|
33
|
+
const [{ contentType, body, form = [] }, setBody] = useState(() => defaultBody(spec, operation));
|
|
34
|
+
const [chosen, setChosen] = useState({});
|
|
35
|
+
const multipart = isMultipart(contentType);
|
|
36
|
+
const [state, setState] = useState({ status: "idle" });
|
|
37
|
+
const [skipConfirm, setSkipConfirm] = useState(() => storage.get("skip-write-confirm", null, "session") === "1");
|
|
38
|
+
const [preferredMode, setMode] = useState(() => storage.get("request-mode") === "direct" ? "direct" : "proxy");
|
|
39
|
+
const mode = proxyEnabled ? preferredMode : "direct";
|
|
40
|
+
function changeMode(next) {
|
|
41
|
+
setMode(next);
|
|
42
|
+
storage.set("request-mode", next);
|
|
43
|
+
}
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const node = dialog.current;
|
|
46
|
+
if (node && !node.open) node.showModal();
|
|
47
|
+
}, []);
|
|
48
|
+
const sent = form.map((field, index) => ({ field, file: chosen[index] })).filter(({ field, file }) => !field.file || file).map(({ field, file }) => ({ field: file ? { name: field.name, file: { name: file.name, type: file.type || void 0 } } : field, file }));
|
|
49
|
+
const request = buildRequest(operation, { server, ...withAuth(values, authFor(operation)), body, form: sent.map((item) => item.field), contentType });
|
|
50
|
+
const files = sent.map((item) => item.file ?? null);
|
|
51
|
+
function setFormValue(index, value) {
|
|
52
|
+
setBody((current) => ({ ...current, form: current.form.map((field, i) => i === index ? { ...field, value } : field) }));
|
|
53
|
+
}
|
|
54
|
+
function setValue(location, name, value) {
|
|
55
|
+
setValues((current) => ({ ...current, [location]: { ...current[location], [name]: value } }));
|
|
56
|
+
}
|
|
57
|
+
function send(event) {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
if (!SAFE_METHODS.has(request.method) && storage.get("skip-write-confirm", null, "session") !== "1") {
|
|
60
|
+
setState({ status: "confirm" });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
execute();
|
|
64
|
+
}
|
|
65
|
+
function confirmSend() {
|
|
66
|
+
if (skipConfirm) storage.set("skip-write-confirm", "1", "session");
|
|
67
|
+
execute();
|
|
68
|
+
}
|
|
69
|
+
async function execute() {
|
|
70
|
+
setState({ status: "loading" });
|
|
71
|
+
try {
|
|
72
|
+
const result = mode === "proxy" ? await sendViaProxy(proxyUrl, request, files) : await sendDirect(request, files);
|
|
73
|
+
setState({ ...result, code: result.status, status: "done" });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
setState({ status: "error", message: error.message });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
"dialog",
|
|
80
|
+
{
|
|
81
|
+
ref: dialog,
|
|
82
|
+
onClose,
|
|
83
|
+
onClick: (event) => event.target === dialog.current && onClose(),
|
|
84
|
+
className: "m-auto h-[min(90dvh,56rem)] w-[min(96vw,72rem)] overflow-hidden rounded-2xl border border-border bg-background p-0 text-foreground backdrop:bg-black/50 backdrop:backdrop-blur-sm",
|
|
85
|
+
children: /* @__PURE__ */ jsxs("form", { onSubmit: send, className: "flex h-full flex-col", children: [
|
|
86
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-center gap-3 border-b border-border px-5 py-3", children: [
|
|
87
|
+
/* @__PURE__ */ jsx(MethodBadge, { method: operation.method }),
|
|
88
|
+
/* @__PURE__ */ jsx("code", { dir: "ltr", className: "min-w-0 flex-1 truncate text-start font-mono text-sm", title: request.url, children: request.url }),
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
type: "submit",
|
|
93
|
+
disabled: state.status === "loading",
|
|
94
|
+
className: "rounded-lg bg-primary px-4 py-1.5 text-sm font-semibold text-primary-foreground hover:opacity-90 disabled:opacity-60",
|
|
95
|
+
children: state.status === "loading" ? t("tryIt.sending") : t("tryIt.send")
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
onClick: onClose,
|
|
103
|
+
"aria-label": t("dialog.close"),
|
|
104
|
+
className: "rounded-lg p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
105
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { className: "size-4" })
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
] }),
|
|
109
|
+
/* @__PURE__ */ jsxs("div", { className: "grid min-h-0 flex-1 lg:grid-cols-2", children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 space-y-6 overflow-y-auto border-border p-5 lg:border-e", children: [
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
112
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("tryIt.settingsHint") }),
|
|
113
|
+
/* @__PURE__ */ jsx("div", { className: "flex rounded-lg border border-border p-0.5 text-xs", role: "group", "aria-label": t("tryIt.mode.label"), children: [
|
|
114
|
+
["proxy", "Proxy"],
|
|
115
|
+
["direct", t("tryIt.mode.direct")]
|
|
116
|
+
].map(([key, label]) => /* @__PURE__ */ jsx(
|
|
117
|
+
"button",
|
|
118
|
+
{
|
|
119
|
+
type: "button",
|
|
120
|
+
onClick: () => changeMode(key),
|
|
121
|
+
disabled: key === "proxy" && !proxyEnabled,
|
|
122
|
+
"aria-pressed": mode === key,
|
|
123
|
+
title: key === "direct" ? t("tryIt.mode.directHint") : proxyEnabled ? t("tryIt.mode.proxyHint") : t("tryIt.mode.proxyDisabled"),
|
|
124
|
+
className: `rounded-md px-2 py-1 font-medium transition disabled:cursor-not-allowed disabled:opacity-40 ${mode === key ? "bg-foreground text-background" : "text-muted-foreground enabled:hover:text-foreground"}`,
|
|
125
|
+
children: label
|
|
126
|
+
},
|
|
127
|
+
key
|
|
128
|
+
)) })
|
|
129
|
+
] }),
|
|
130
|
+
LOCATIONS.map(([location, label]) => {
|
|
131
|
+
const params = operation.parameters.filter((param) => param.in === location);
|
|
132
|
+
if (params.length === 0) return null;
|
|
133
|
+
return /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
|
|
134
|
+
/* @__PURE__ */ jsx("legend", { className: "mb-2 text-sm font-semibold", children: label }),
|
|
135
|
+
params.map((param) => /* @__PURE__ */ jsxs("label", { className: "grid grid-cols-[minmax(0,11rem)_minmax(0,1fr)] items-center gap-3", children: [
|
|
136
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate font-mono text-xs", title: param.name, children: [
|
|
137
|
+
param.name,
|
|
138
|
+
param.required && /* @__PURE__ */ jsx("span", { className: "text-rose-600", children: " *" })
|
|
139
|
+
] }),
|
|
140
|
+
param.schema?.enum ? /* @__PURE__ */ jsxs(
|
|
141
|
+
"select",
|
|
142
|
+
{
|
|
143
|
+
value: values[location]?.[param.name] ?? "",
|
|
144
|
+
onChange: (event) => setValue(location, param.name, event.target.value),
|
|
145
|
+
className: "rounded-md border border-border bg-background px-2 py-1 text-xs",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
|
|
148
|
+
param.schema.enum.map((option) => /* @__PURE__ */ jsx("option", { value: String(option), children: String(option) }, String(option)))
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
) : /* @__PURE__ */ jsx(
|
|
152
|
+
"input",
|
|
153
|
+
{
|
|
154
|
+
value: values[location]?.[param.name] ?? "",
|
|
155
|
+
required: param.required,
|
|
156
|
+
onChange: (event) => setValue(location, param.name, event.target.value),
|
|
157
|
+
dir: "ltr",
|
|
158
|
+
className: "rounded-md border border-border bg-background px-2 py-1 font-mono text-xs outline-none focus:border-primary"
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
] }, param.name))
|
|
162
|
+
] }, location);
|
|
163
|
+
}),
|
|
164
|
+
contentType && multipart && /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
|
|
165
|
+
/* @__PURE__ */ jsxs("legend", { className: "mb-2 flex w-full items-center justify-between text-sm font-semibold", children: [
|
|
166
|
+
"Body ",
|
|
167
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] font-normal text-muted-foreground", children: contentType })
|
|
168
|
+
] }),
|
|
169
|
+
form.map((field, index) => /* @__PURE__ */ jsxs("label", { className: "grid grid-cols-[minmax(0,11rem)_minmax(0,1fr)] items-center gap-3", children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate font-mono text-xs", title: field.name, children: [
|
|
171
|
+
field.name,
|
|
172
|
+
field.file && /* @__PURE__ */ jsx("span", { className: "ms-1 rounded bg-muted px-1 text-[10px] text-muted-foreground", children: "file" })
|
|
173
|
+
] }),
|
|
174
|
+
field.file ? /* @__PURE__ */ jsx(
|
|
175
|
+
"input",
|
|
176
|
+
{
|
|
177
|
+
type: "file",
|
|
178
|
+
onChange: (event) => setChosen((current) => ({ ...current, [index]: event.target.files?.[0] })),
|
|
179
|
+
className: "text-xs file:me-2 file:rounded-md file:border file:border-border file:bg-background file:px-2 file:py-1 file:text-xs"
|
|
180
|
+
}
|
|
181
|
+
) : /* @__PURE__ */ jsx(
|
|
182
|
+
"input",
|
|
183
|
+
{
|
|
184
|
+
value: field.value ?? "",
|
|
185
|
+
onChange: (event) => setFormValue(index, event.target.value),
|
|
186
|
+
dir: "ltr",
|
|
187
|
+
className: "rounded-md border border-border bg-background px-2 py-1 font-mono text-xs outline-none focus:border-primary"
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
] }, `${field.name}-${index}`)),
|
|
191
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("tryIt.fileHint") })
|
|
192
|
+
] }),
|
|
193
|
+
contentType && !multipart && /* @__PURE__ */ jsxs("label", { className: "block space-y-2", children: [
|
|
194
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between text-sm font-semibold", children: [
|
|
195
|
+
"Body ",
|
|
196
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] font-normal text-muted-foreground", children: contentType })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsx(CodeEditor, { value: body, onChange: (value) => setBody((current) => ({ ...current, body: value })) })
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 space-y-4 overflow-y-auto p-5", children: [
|
|
202
|
+
state.status === "confirm" && /* @__PURE__ */ jsxs("div", { role: "alertdialog", "aria-labelledby": "write-confirm-title", className: "space-y-3 rounded-xl border border-amber-500/40 bg-amber-500/10 p-4 text-sm", children: [
|
|
203
|
+
/* @__PURE__ */ jsx("p", { id: "write-confirm-title", className: "font-semibold text-amber-800 dark:text-amber-200", children: t("tryIt.confirm.title") }),
|
|
204
|
+
/* @__PURE__ */ jsx("p", { className: "text-amber-900/80 dark:text-amber-100/80", children: t("tryIt.confirm.body", {
|
|
205
|
+
method: request.method,
|
|
206
|
+
host: (() => {
|
|
207
|
+
try {
|
|
208
|
+
return new URL(request.url).host;
|
|
209
|
+
} catch {
|
|
210
|
+
return request.url;
|
|
211
|
+
}
|
|
212
|
+
})()
|
|
213
|
+
}) }),
|
|
214
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-amber-900/80 dark:text-amber-100/80", children: [
|
|
215
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: skipConfirm, onChange: (event) => setSkipConfirm(event.target.checked) }),
|
|
216
|
+
t("tryIt.confirm.skip")
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
219
|
+
/* @__PURE__ */ jsx(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
type: "button",
|
|
223
|
+
onClick: confirmSend,
|
|
224
|
+
autoFocus: true,
|
|
225
|
+
className: "rounded-lg bg-amber-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-amber-700",
|
|
226
|
+
children: t("tryIt.confirm.send")
|
|
227
|
+
}
|
|
228
|
+
),
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
"button",
|
|
231
|
+
{
|
|
232
|
+
type: "button",
|
|
233
|
+
onClick: () => setState({ status: "idle" }),
|
|
234
|
+
className: "rounded-lg border border-border px-3 py-1.5 text-xs font-medium hover:bg-muted",
|
|
235
|
+
children: t("tryIt.confirm.cancel")
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
] })
|
|
239
|
+
] }),
|
|
240
|
+
state.status === "idle" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("tryIt.idle") }),
|
|
241
|
+
state.status === "loading" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("tryIt.waiting") }),
|
|
242
|
+
state.status === "error" && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-rose-500/30 bg-rose-500/10 p-3 text-sm text-rose-700 dark:text-rose-300", children: [
|
|
243
|
+
t("tryIt.failed", { message: state.message }),
|
|
244
|
+
mode === "direct" && ` \u2014 ${t("tryIt.corsHint")}`
|
|
245
|
+
] }),
|
|
246
|
+
state.status === "done" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
247
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-3", children: [
|
|
248
|
+
/* @__PURE__ */ jsx("span", { className: `font-mono text-lg font-bold ${statusTone(String(state.code))}`, children: state.code }),
|
|
249
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: state.statusText }),
|
|
250
|
+
/* @__PURE__ */ jsxs("span", { dir: "ltr", className: "ms-auto text-xs text-muted-foreground", children: [
|
|
251
|
+
state.duration,
|
|
252
|
+
" ms"
|
|
253
|
+
] })
|
|
254
|
+
] }),
|
|
255
|
+
/* @__PURE__ */ jsx(CodeBlock, { maxHeight: "none", ...prettyBody(state.body), toolbar: /* @__PURE__ */ jsx("span", { className: "px-2 text-xs text-white/60", children: "Body" }) }),
|
|
256
|
+
/* @__PURE__ */ jsxs("details", { className: "text-xs", children: [
|
|
257
|
+
/* @__PURE__ */ jsxs("summary", { className: "cursor-pointer font-semibold", children: [
|
|
258
|
+
"Headers (",
|
|
259
|
+
state.headers.length,
|
|
260
|
+
")"
|
|
261
|
+
] }),
|
|
262
|
+
/* @__PURE__ */ jsx("dl", { className: "mt-2 grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1 font-mono", children: state.headers.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
263
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: key }),
|
|
264
|
+
/* @__PURE__ */ jsx("dd", { className: "break-all", children: value })
|
|
265
|
+
] }, key)) })
|
|
266
|
+
] })
|
|
267
|
+
] })
|
|
268
|
+
] })
|
|
269
|
+
] })
|
|
270
|
+
] })
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
export {
|
|
275
|
+
TryIt as default
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=try-it.js.map
|