@djangocfg/ui-tools 2.1.289 → 2.1.291

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.
Files changed (98) hide show
  1. package/README.md +14 -3
  2. package/dist/{DocsLayout-YDR7DSMM.cjs → DocsLayout-IKH7BLSU.cjs} +1537 -682
  3. package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
  4. package/dist/{DocsLayout-TKJQ5W5E.mjs → DocsLayout-JPXFUKAR.mjs} +1429 -574
  5. package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
  6. package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
  7. package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
  9. package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
  10. package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
  11. package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
  12. package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
  13. package/dist/chunk-EFWOJPA6.mjs.map +1 -0
  14. package/dist/index.cjs +18 -10
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +35 -1
  17. package/dist/index.d.ts +35 -1
  18. package/dist/index.mjs +13 -5
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +20 -15
  21. package/src/components/markdown/MarkdownMessage.tsx +46 -0
  22. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +42 -1
  23. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +87 -178
  24. package/src/tools/OpenapiViewer/README.md +114 -6
  25. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
  26. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +6 -0
  27. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
  39. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
  40. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
  41. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
  42. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
  43. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
  44. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
  45. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
  46. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
  47. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
  48. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
  49. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
  50. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
  51. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
  52. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
  53. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
  54. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
  55. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +8 -2
  56. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
  57. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
  58. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
  59. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
  60. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
  61. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
  62. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
  63. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
  64. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
  65. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
  66. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
  67. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
  68. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
  69. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
  70. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
  71. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
  72. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
  73. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
  74. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
  75. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
  76. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
  77. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
  78. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
  79. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +41 -71
  80. package/src/tools/OpenapiViewer/types.ts +10 -0
  81. package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
  82. package/src/tools/OpenapiViewer/utils/index.ts +3 -0
  83. package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
  84. package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
  85. package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
  86. package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
  87. package/src/tools/PrettyCode/index.tsx +13 -0
  88. package/src/tools/PrettyCode/lazy.tsx +5 -0
  89. package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
  90. package/dist/DocsLayout-TKJQ5W5E.mjs.map +0 -1
  91. package/dist/DocsLayout-YDR7DSMM.cjs.map +0 -1
  92. package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
  93. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
  94. package/dist/chunk-IULI4XII.cjs.map +0 -1
  95. package/dist/chunk-VZGQC3NG.mjs.map +0 -1
  96. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -273
  97. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -439
  98. package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
@@ -1,58 +1,20 @@
1
- import { deduplicateEndpoints, dereferenceSchema, resolveBaseUrl, usePlaygroundContext, toMarkdown, toCompactJson, toRawJson, formatBytes, MarkdownMessage, endpointToMarkdown, relativePath, isValidJson, resolveAbsolute, findApiKeyById, parseRequestHeaders, PrettyCode_default, UrlBuilder, joinUrl } from './chunk-VZGQC3NG.mjs';
1
+ import { deduplicateEndpoints, dereferenceSchema, resolveBaseUrl, usePlaygroundContext, toMarkdown, toCompactJson, toRawJson, formatBytes, MarkdownMessage, CODE_SAMPLE_TARGETS, buildHarRequest, resolveAbsolute, renderSnippet, PrettyCode_default, relativePath, endpointToMarkdown, isValidJson, findApiKeyById, parseRequestHeaders, UrlBuilder, sampleSchemaJson, joinUrl } from './chunk-EFWOJPA6.mjs';
2
2
  import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
3
3
  import './chunk-SSUOENAZ.mjs';
4
4
  import { __name } from './chunk-CGILA3WO.mjs';
5
- import React6, { useRef, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import React12, { createContext, useRef, useCallback, useEffect, useMemo, useState, useContext } from 'react';
6
6
  import { groupBy, orderBy, partition, sortBy, keyBy } from 'lodash-es';
7
- import { Tooltip, TooltipTrigger, TooltipContent, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Combobox, Input, CopyButton, Switch, Textarea, SidePanel, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, Skeleton, TooltipProvider } from '@djangocfg/ui-core/components';
7
+ import { Tooltip, TooltipTrigger, TooltipContent, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Input, Combobox, SafeTooltipProvider, CopyButton, Switch, Textarea, SidePanel, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, Skeleton, TooltipProvider } from '@djangocfg/ui-core/components';
8
8
  import { toast, useMediaQuery } from '@djangocfg/ui-core/hooks';
9
9
  import consola from 'consola';
10
- import { ChevronRight, Sparkles, ChevronDown, Check, Search, Link2, Play, Minus, Plus, RotateCcw, Send, Key, Terminal, Loader2, WifiOff, AlertCircle } from 'lucide-react';
10
+ import { ChevronRight, Sparkles, ChevronDown, Check, Search, X, Link2, FileCode2, ChevronsDownUp, ChevronsUpDown, Play, Minus, Plus, RotateCcw, Send, Key, Terminal, Info, ShieldCheck, Loader2, WifiOff, AlertCircle } from 'lucide-react';
11
11
  import { cn } from '@djangocfg/ui-core/lib';
12
12
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
+ import { create } from 'zustand';
14
+ import { persist, createJSONStorage } from 'zustand/middleware';
13
15
 
14
- function exampleFromSchema(schema, depth = 0) {
15
- if (!schema || depth > 8) return null;
16
- if (schema.example !== void 0) return schema.example;
17
- if (schema.default !== void 0) return schema.default;
18
- if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
19
- switch (schema.type) {
20
- case "object": {
21
- const out = {};
22
- const props = schema.properties ?? {};
23
- for (const [k, v] of Object.entries(props)) {
24
- out[k] = exampleFromSchema(v, depth + 1);
25
- }
26
- return out;
27
- }
28
- case "array":
29
- return [exampleFromSchema(schema.items, depth + 1)];
30
- case "integer":
31
- case "number":
32
- return 0;
33
- case "boolean":
34
- return false;
35
- case "string":
36
- if (schema.format === "date-time") return (/* @__PURE__ */ new Date()).toISOString();
37
- if (schema.format === "date") return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
38
- if (schema.format === "email") return "user@example.com";
39
- if (schema.format === "uri" || schema.format === "url") return "https://example.com";
40
- if (schema.format === "uuid") return "00000000-0000-0000-0000-000000000000";
41
- return "";
42
- default:
43
- if (schema.properties) {
44
- const out = {};
45
- for (const [k, v] of Object.entries(schema.properties)) {
46
- out[k] = exampleFromSchema(v, depth + 1);
47
- }
48
- return out;
49
- }
50
- return null;
51
- }
52
- }
53
- __name(exampleFromSchema, "exampleFromSchema");
54
16
  var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
55
- var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId) => {
17
+ var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId, specRoot) => {
56
18
  const endpoints = [];
57
19
  if (!schema.paths) return [];
58
20
  for (const [path, methods] of Object.entries(schema.paths)) {
@@ -76,9 +38,17 @@ var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId) => {
76
38
  const responses = [];
77
39
  if (op.responses) {
78
40
  for (const [code, response] of Object.entries(op.responses)) {
41
+ const respContent = response.content;
42
+ const contentKeys = respContent ? Object.keys(respContent) : [];
43
+ const chosenContentType = respContent?.["application/json"] ? "application/json" : contentKeys[0];
44
+ const chosen = chosenContentType ? respContent?.[chosenContentType] : void 0;
45
+ const respSchema = chosen?.schema;
79
46
  responses.push({
80
47
  code,
81
- description: response.description || `Response ${code}`
48
+ description: response.description || `Response ${code}`,
49
+ contentType: chosenContentType,
50
+ schema: respSchema,
51
+ example: respSchema ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot) : void 0
82
52
  });
83
53
  }
84
54
  }
@@ -91,7 +61,7 @@ var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId) => {
91
61
  type: rawSchema?.type || "object",
92
62
  description: op.requestBody.description,
93
63
  schema: rawSchema,
94
- example: rawSchema ? JSON.stringify(exampleFromSchema(rawSchema), null, 2) : void 0
64
+ example: rawSchema ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot) : void 0
95
65
  };
96
66
  }
97
67
  const endpoint = {
@@ -163,8 +133,8 @@ function useOpenApiSchema({
163
133
  [currentSchema?.baseUrl, configBaseUrl, currentOpenApiSchema]
164
134
  );
165
135
  const endpoints = useMemo(
166
- () => dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId) : [],
167
- [dereferencedSchema, resolvedBaseUrl, currentSchemaId]
136
+ () => dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema ?? void 0) : [],
137
+ [dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema]
168
138
  );
169
139
  const categories = useMemo(() => getCategories(endpoints), [endpoints]);
170
140
  const schemaInfo = useMemo(() => {
@@ -266,7 +236,7 @@ function useOpenApiSchema({
266
236
  description: raw.info.description,
267
237
  servers: raw.servers
268
238
  } : null;
269
- const eps = deref ? extractEndpoints(deref, resolved, src.id) : [];
239
+ const eps = deref ? extractEndpoints(deref, resolved, src.id, raw ?? void 0) : [];
270
240
  const state = loadStates.get(src.id) ?? { loading: !raw, error: null };
271
241
  return {
272
242
  source: src,
@@ -599,7 +569,7 @@ function CollapsibleSection({
599
569
  children,
600
570
  defaultOpen = false
601
571
  }) {
602
- const [open, setOpen] = React6.useState(defaultOpen);
572
+ const [open, setOpen] = React12.useState(defaultOpen);
603
573
  return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
604
574
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
605
575
  /* @__PURE__ */ jsxs(
@@ -620,75 +590,6 @@ function CollapsibleSection({
620
590
  ] });
621
591
  }
622
592
  __name(CollapsibleSection, "CollapsibleSection");
623
-
624
- // src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts
625
- function longestCommonPrefix(paths) {
626
- if (paths.length === 0) return "";
627
- if (paths.length === 1) return "";
628
- const segments = paths.map((p) => p.split("/"));
629
- const minLen = Math.min(...segments.map((s) => s.length));
630
- const shared = [];
631
- for (let i = 0; i < minLen; i++) {
632
- const first = segments[0][i];
633
- if (segments.every((s) => s[i] === first)) {
634
- shared.push(first);
635
- } else {
636
- break;
637
- }
638
- }
639
- const joined = shared.join("/");
640
- return joined;
641
- }
642
- __name(longestCommonPrefix, "longestCommonPrefix");
643
- function sidebarLabel(ep, groupCommonPrefix) {
644
- if (ep.summary) return ep.summary;
645
- if (groupCommonPrefix && ep.path.startsWith(groupCommonPrefix)) {
646
- const tail = ep.path.slice(groupCommonPrefix.length) || "/";
647
- return tail;
648
- }
649
- return relativePath2(ep.path);
650
- }
651
- __name(sidebarLabel, "sidebarLabel");
652
- function relativePath2(full) {
653
- try {
654
- return new URL(full).pathname;
655
- } catch {
656
- return full;
657
- }
658
- }
659
- __name(relativePath2, "relativePath");
660
- function sidebarTooltip(ep) {
661
- return `${ep.method} ${relativePath2(ep.path)}`;
662
- }
663
- __name(sidebarTooltip, "sidebarTooltip");
664
-
665
- // src/tools/OpenapiViewer/components/DocsLayout/grouping.ts
666
- var METHOD_ORDER = {
667
- GET: 0,
668
- POST: 1,
669
- PUT: 2,
670
- PATCH: 3,
671
- DELETE: 4
672
- };
673
- var methodRank = /* @__PURE__ */ __name((ep) => METHOD_ORDER[ep.method] ?? 99, "methodRank");
674
- function groupEndpoints(list) {
675
- const byCategory = groupBy(list, "category");
676
- const all = Object.entries(byCategory).map(([category, endpoints]) => ({
677
- category,
678
- endpoints: orderBy(endpoints, ["path", methodRank], ["asc", "asc"]),
679
- commonPrefix: longestCommonPrefix(endpoints.map((e) => e.path))
680
- }));
681
- const [other, named] = partition(all, (g) => g.category === "Other");
682
- return [...sortBy(named, (g) => g.category.toLowerCase()), ...other];
683
- }
684
- __name(groupEndpoints, "groupEndpoints");
685
- function buildSchemaSections(sources, endpointsBySchema) {
686
- return sources.map((source) => ({
687
- source,
688
- groups: groupEndpoints(endpointsBySchema[source.id] ?? [])
689
- }));
690
- }
691
- __name(buildSchemaSections, "buildSchemaSections");
692
593
  var FLAVOUR_LABELS = {
693
594
  markdown: {
694
595
  title: "Markdown for LLM",
@@ -755,40 +656,150 @@ function SchemaCopyMenu({ schema, endpoints, baseUrl, variant = "button" }) {
755
656
  "Copy for AI",
756
657
  /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-60" })
757
658
  ] }) }),
758
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-72", children: [
759
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-[10px] uppercase tracking-wider text-muted-foreground/70", children: "Copy schema" }),
760
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
761
- flavours.map((f) => {
762
- const label = FLAVOUR_LABELS[f];
763
- const size = sizeCache[f];
764
- const isDone = justCopied === f;
765
- return /* @__PURE__ */ jsxs(
766
- DropdownMenuItem,
767
- {
768
- onClick: (e) => {
769
- e.preventDefault();
770
- void handleCopy(f);
771
- },
772
- className: "flex flex-col items-start gap-0.5 py-2 cursor-pointer",
773
- children: [
774
- /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
775
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium flex-1", children: label.title }),
776
- isDone ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-emerald-500", children: [
777
- /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
778
- " Copied"
779
- ] }) : size ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground/70 tabular-nums", children: size }) : null
780
- ] }),
781
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70 leading-snug", children: label.hint })
782
- ]
783
- },
784
- f
785
- );
786
- })
787
- ] })
659
+ /* @__PURE__ */ jsxs(
660
+ DropdownMenuContent,
661
+ {
662
+ side: "right",
663
+ align: "start",
664
+ sideOffset: 6,
665
+ collisionPadding: 8,
666
+ className: "w-60 max-w-[calc(100vw-16px)]",
667
+ children: [
668
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-[10px] uppercase tracking-wider text-muted-foreground/70", children: "Copy schema" }),
669
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
670
+ flavours.map((f) => {
671
+ const label = FLAVOUR_LABELS[f];
672
+ const size = sizeCache[f];
673
+ const isDone = justCopied === f;
674
+ return /* @__PURE__ */ jsxs(
675
+ DropdownMenuItem,
676
+ {
677
+ onClick: (e) => {
678
+ e.preventDefault();
679
+ void handleCopy(f);
680
+ },
681
+ className: "flex flex-col items-start gap-0.5 py-2 cursor-pointer",
682
+ children: [
683
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
684
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium flex-1", children: label.title }),
685
+ isDone ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-emerald-500", children: [
686
+ /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
687
+ " Copied"
688
+ ] }) : size ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground/70 tabular-nums", children: size }) : null
689
+ ] }),
690
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70 leading-snug line-clamp-1", children: label.hint })
691
+ ]
692
+ },
693
+ f
694
+ );
695
+ })
696
+ ]
697
+ }
698
+ )
788
699
  ] });
789
700
  }
790
701
  __name(SchemaCopyMenu, "SchemaCopyMenu");
791
- var METHOD_FILTERS = ["ALL", "GET", "POST", "PUT", "PATCH", "DELETE"];
702
+ function BrandHeader({ info, endpoints, rawSchema, resolvedBaseUrl }) {
703
+ const apiTitle = info?.title ?? "API Reference";
704
+ const copyReady = rawSchema !== null && rawSchema !== void 0 && endpoints.length > 0;
705
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 flex items-start gap-2", children: [
706
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
707
+ /* @__PURE__ */ jsx(
708
+ "div",
709
+ {
710
+ className: "text-[13px] font-semibold text-foreground leading-tight truncate",
711
+ title: apiTitle,
712
+ children: apiTitle
713
+ }
714
+ ),
715
+ info?.version && /* @__PURE__ */ jsxs("div", { className: "font-mono text-[10px] text-muted-foreground/60 leading-tight mt-0.5", children: [
716
+ "v",
717
+ info.version
718
+ ] })
719
+ ] }),
720
+ copyReady && /* @__PURE__ */ jsx(
721
+ SchemaCopyMenu,
722
+ {
723
+ schema: rawSchema ?? null,
724
+ endpoints,
725
+ baseUrl: resolvedBaseUrl,
726
+ variant: "icon"
727
+ }
728
+ )
729
+ ] });
730
+ }
731
+ __name(BrandHeader, "BrandHeader");
732
+
733
+ // src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts
734
+ function longestCommonPrefix(paths) {
735
+ if (paths.length === 0) return "";
736
+ if (paths.length === 1) return "";
737
+ const segments = paths.map((p) => p.split("/"));
738
+ const minLen = Math.min(...segments.map((s) => s.length));
739
+ const shared = [];
740
+ for (let i = 0; i < minLen; i++) {
741
+ const first = segments[0][i];
742
+ if (segments.every((s) => s[i] === first)) {
743
+ shared.push(first);
744
+ } else {
745
+ break;
746
+ }
747
+ }
748
+ const joined = shared.join("/");
749
+ return joined;
750
+ }
751
+ __name(longestCommonPrefix, "longestCommonPrefix");
752
+ function sidebarLabel(ep, groupCommonPrefix) {
753
+ if (ep.summary) return ep.summary;
754
+ if (groupCommonPrefix && ep.path.startsWith(groupCommonPrefix)) {
755
+ const tail = ep.path.slice(groupCommonPrefix.length) || "/";
756
+ return tail;
757
+ }
758
+ return relativePath2(ep.path);
759
+ }
760
+ __name(sidebarLabel, "sidebarLabel");
761
+ function relativePath2(full) {
762
+ try {
763
+ return new URL(full).pathname;
764
+ } catch {
765
+ return full;
766
+ }
767
+ }
768
+ __name(relativePath2, "relativePath");
769
+ function sidebarTooltip(ep) {
770
+ return `${ep.method} ${relativePath2(ep.path)}`;
771
+ }
772
+ __name(sidebarTooltip, "sidebarTooltip");
773
+
774
+ // src/tools/OpenapiViewer/components/DocsLayout/grouping.ts
775
+ var METHOD_ORDER = {
776
+ GET: 0,
777
+ POST: 1,
778
+ PUT: 2,
779
+ PATCH: 3,
780
+ DELETE: 4
781
+ };
782
+ var methodRank = /* @__PURE__ */ __name((ep) => METHOD_ORDER[ep.method] ?? 99, "methodRank");
783
+ function groupEndpoints(list) {
784
+ const byCategory = groupBy(list, "category");
785
+ const all = Object.entries(byCategory).map(([category, endpoints]) => ({
786
+ category,
787
+ endpoints: orderBy(endpoints, ["path", methodRank], ["asc", "asc"]),
788
+ commonPrefix: longestCommonPrefix(endpoints.map((e) => e.path))
789
+ }));
790
+ const [other, named] = partition(all, (g) => g.category === "Other");
791
+ return [...sortBy(named, (g) => g.category.toLowerCase()), ...other];
792
+ }
793
+ __name(groupEndpoints, "groupEndpoints");
794
+ function buildSchemaSections(sources, endpointsBySchema) {
795
+ return sources.map((source) => ({
796
+ source,
797
+ groups: groupEndpoints(endpointsBySchema[source.id] ?? [])
798
+ }));
799
+ }
800
+ __name(buildSchemaSections, "buildSchemaSections");
801
+
802
+ // src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts
792
803
  function filterEndpoints(list, query, method) {
793
804
  let out = list;
794
805
  if (method !== "ALL") {
@@ -824,14 +835,19 @@ function buildCategory(group, activeEndpointId, schemaId, keyPrefix) {
824
835
  };
825
836
  }
826
837
  __name(buildCategory, "buildCategory");
827
- var emptyTextFor = /* @__PURE__ */ __name((query, method, defaultText) => {
838
+ function emptyTextFor(query, method, defaultText) {
828
839
  if (query && method !== "ALL") return `No ${method} endpoints match "${query}"`;
829
840
  if (query) return `No endpoints match "${query}"`;
830
841
  if (method !== "ALL") return `No ${method} endpoints`;
831
842
  return defaultText;
832
- }, "emptyTextFor");
843
+ }
844
+ __name(emptyTextFor, "emptyTextFor");
833
845
  function buildFlatVM(endpoints, selectedVersion, query, method, activeEndpointId) {
834
- const filtered = filterEndpoints(deduplicateEndpoints(endpoints, selectedVersion), query, method);
846
+ const filtered = filterEndpoints(
847
+ deduplicateEndpoints(endpoints, selectedVersion),
848
+ query,
849
+ method
850
+ );
835
851
  const groups = groupEndpoints(filtered);
836
852
  return {
837
853
  kind: "flat",
@@ -844,13 +860,19 @@ function buildSectionsVM(schemas, endpointsBySchema, selectedVersion, query, met
844
860
  const filteredMap = {};
845
861
  for (const src of schemas) {
846
862
  const raw = endpointsBySchema[src.id] ?? [];
847
- filteredMap[src.id] = filterEndpoints(deduplicateEndpoints(raw, selectedVersion), query, method);
863
+ filteredMap[src.id] = filterEndpoints(
864
+ deduplicateEndpoints(raw, selectedVersion),
865
+ query,
866
+ method
867
+ );
848
868
  }
849
869
  const rawSections = buildSchemaSections(schemas, filteredMap);
850
870
  const sections = rawSections.filter((s) => s.groups.length > 0).map((s) => ({
851
871
  sourceId: s.source.id,
852
872
  sourceName: s.source.name,
853
- categories: s.groups.map((g) => buildCategory(g, activeEndpointId, s.source.id, `${s.source.id}-`))
873
+ categories: s.groups.map(
874
+ (g) => buildCategory(g, activeEndpointId, s.source.id, `${s.source.id}-`)
875
+ )
854
876
  }));
855
877
  return {
856
878
  kind: "sections",
@@ -859,159 +881,11 @@ function buildSectionsVM(schemas, endpointsBySchema, selectedVersion, query, met
859
881
  };
860
882
  }
861
883
  __name(buildSectionsVM, "buildSectionsVM");
862
- function DocsSidebar({
863
- info,
864
- endpoints,
865
- schemas,
866
- currentSchemaId,
867
- onSchemaChange,
868
- activeEndpointId,
869
- selectedVersion,
870
- onNavigate,
871
- grouping = "selector",
872
- endpointsBySchema,
873
- rawSchema,
874
- resolvedBaseUrl
875
- }) {
876
- const [search, setSearch] = useState("");
877
- const [debounced, setDebounced] = useState("");
878
- const [methodFilter, setMethodFilter] = useState("ALL");
879
- useEffect(() => {
880
- const id = setTimeout(() => setDebounced(search), 120);
881
- return () => clearTimeout(id);
882
- }, [search]);
883
- const body = useMemo(() => {
884
- if (grouping === "sections") {
885
- return buildSectionsVM(
886
- schemas,
887
- endpointsBySchema ?? {},
888
- selectedVersion,
889
- debounced,
890
- methodFilter,
891
- activeEndpointId
892
- );
893
- }
894
- return buildFlatVM(endpoints, selectedVersion, debounced, methodFilter, activeEndpointId);
895
- }, [
896
- grouping,
897
- schemas,
898
- endpointsBySchema,
899
- endpoints,
900
- selectedVersion,
901
- debounced,
902
- methodFilter,
903
- activeEndpointId
904
- ]);
905
- const schemaOptions = useMemo(
906
- () => schemas.map((s) => ({ value: s.id, label: s.name })),
907
- [schemas]
908
- );
909
- const hasMultipleSchemas = schemas.length > 1;
910
- const apiTitle = info?.title ?? "API Reference";
911
- const showCombobox = grouping === "selector" && hasMultipleSchemas;
912
- const copyReady = rawSchema !== null && rawSchema !== void 0 && endpoints.length > 0;
913
- return /* @__PURE__ */ jsxs("aside", { className: "flex flex-col h-full min-h-0 border-r bg-muted/10", children: [
914
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 h-12 flex items-center gap-2", children: [
915
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
916
- /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold text-foreground truncate", children: apiTitle }),
917
- info?.version && /* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] text-muted-foreground/70 shrink-0", children: [
918
- "v",
919
- info.version
920
- ] })
921
- ] }),
922
- copyReady && /* @__PURE__ */ jsx(
923
- SchemaCopyMenu,
924
- {
925
- schema: rawSchema ?? null,
926
- endpoints,
927
- baseUrl: resolvedBaseUrl,
928
- variant: "icon"
929
- }
930
- )
931
- ] }),
932
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-3 space-y-2", children: [
933
- showCombobox && /* @__PURE__ */ jsx(
934
- Combobox,
935
- {
936
- options: schemaOptions,
937
- value: currentSchemaId ?? "",
938
- onValueChange: (id) => id && onSchemaChange(id),
939
- placeholder: "Select API",
940
- searchPlaceholder: "Search APIs\u2026",
941
- emptyText: "No APIs found",
942
- className: "w-full h-8 text-xs"
943
- }
944
- ),
945
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
946
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
947
- /* @__PURE__ */ jsx(
948
- Input,
949
- {
950
- placeholder: "Search endpoints\u2026",
951
- value: search,
952
- onChange: (e) => setSearch(e.target.value),
953
- className: "pl-8 h-8 text-xs"
954
- }
955
- )
956
- ] }),
957
- /* @__PURE__ */ jsx(MethodChips, { value: methodFilter, onChange: setMethodFilter })
958
- ] }),
959
- /* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsx(SidebarBody, { body, onNavigate }) })
960
- ] });
961
- }
962
- __name(DocsSidebar, "DocsSidebar");
963
- function MethodChips({
964
- value,
965
- onChange
966
- }) {
967
- return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 flex-wrap", children: METHOD_FILTERS.map((m) => {
968
- const active = value === m;
969
- return /* @__PURE__ */ jsx(
970
- "button",
971
- {
972
- type: "button",
973
- onClick: () => onChange(m),
974
- "aria-pressed": active,
975
- className: cn(
976
- "px-2 py-0.5 rounded font-mono text-[10px] font-semibold tracking-wide transition-colors border",
977
- active ? "bg-primary/15 border-primary/40 text-foreground" : "bg-transparent border-border/40 text-muted-foreground hover:text-foreground hover:border-border"
978
- ),
979
- children: m
980
- },
981
- m
982
- );
983
- }) });
984
- }
985
- __name(MethodChips, "MethodChips");
986
- function SidebarBody({ body, onNavigate }) {
987
- if (body.kind === "flat") {
988
- if (body.categories.length === 0) {
989
- return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
990
- }
991
- return /* @__PURE__ */ jsx("nav", { className: "py-2", children: body.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key)) });
992
- }
993
- if (body.sections.length === 0) {
994
- return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
995
- }
996
- return /* @__PURE__ */ jsx("nav", { className: "py-2", children: body.sections.map((section) => /* @__PURE__ */ jsxs("div", { className: "mb-5 last:mb-2", children: [
997
- /* @__PURE__ */ jsx("div", { className: "px-4 py-2 sticky top-0 z-[1] bg-muted/30 backdrop-blur-[2px] border-b border-border/30", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold uppercase tracking-[0.12em] text-foreground/80", children: section.sourceName }) }),
998
- section.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key))
999
- ] }, section.sourceId)) });
1000
- }
1001
- __name(SidebarBody, "SidebarBody");
1002
- var CategoryBlock = React6.memo(/* @__PURE__ */ __name(function CategoryBlock2({
1003
- category,
1004
- onNavigate
1005
- }) {
1006
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-2", children: [
1007
- /* @__PURE__ */ jsx("div", { className: "px-4 py-1.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/50 select-none", children: category.category }),
1008
- /* @__PURE__ */ jsx("div", { children: category.rows.map((row) => /* @__PURE__ */ jsx(EndpointRow, { row, onNavigate }, row.key)) })
1009
- ] });
1010
- }, "CategoryBlock"));
1011
- var EndpointRow = React6.memo(/* @__PURE__ */ __name(function EndpointRow2({
1012
- row,
1013
- onNavigate
884
+ var EndpointRow = React12.memo(/* @__PURE__ */ __name(function EndpointRow2({
885
+ row,
886
+ onNavigate
1014
887
  }) {
888
+ const displayLabel = row.label.replace(/\.$/, "");
1015
889
  return /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 350, children: [
1016
890
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1017
891
  "button",
@@ -1019,21 +893,21 @@ var EndpointRow = React6.memo(/* @__PURE__ */ __name(function EndpointRow2({
1019
893
  onClick: () => onNavigate(row.anchor, row.schemaId),
1020
894
  "aria-current": row.isActive ? "location" : void 0,
1021
895
  className: cn(
1022
- "relative group w-full text-left flex items-start gap-2 pl-4 pr-3 py-1.5 transition-colors",
896
+ "relative w-full text-left grid grid-cols-[52px_minmax(0,1fr)] items-baseline gap-2 pl-3 pr-3 py-1 transition-colors",
1023
897
  row.isActive ? "bg-primary/10 text-foreground" : "hover:bg-muted/40 text-foreground/75 hover:text-foreground"
1024
898
  ),
1025
899
  children: [
1026
900
  row.isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded-r bg-primary" }),
1027
- /* @__PURE__ */ jsx("span", { className: "shrink-0 mt-[1px]", children: /* @__PURE__ */ jsx(MethodBadge, { method: row.method }) }),
901
+ /* @__PURE__ */ jsx("span", { className: "justify-self-start", children: /* @__PURE__ */ jsx(MethodBadge, { method: row.method }) }),
1028
902
  /* @__PURE__ */ jsx(
1029
903
  "span",
1030
904
  {
1031
905
  className: cn(
1032
- "line-clamp-2 leading-snug flex-1 min-w-0",
906
+ "line-clamp-2 leading-snug min-w-0",
1033
907
  row.useMono ? "font-mono text-[11px] break-all" : "text-[12px]",
1034
908
  row.isActive && "text-foreground font-medium"
1035
909
  ),
1036
- children: row.label
910
+ children: displayLabel
1037
911
  }
1038
912
  )
1039
913
  ]
@@ -1042,6 +916,202 @@ var EndpointRow = React6.memo(/* @__PURE__ */ __name(function EndpointRow2({
1042
916
  /* @__PURE__ */ jsx(TooltipContent, { side: "right", align: "center", className: "font-mono text-[11px]", children: row.tooltip })
1043
917
  ] });
1044
918
  }, "EndpointRow"));
919
+ var CategoryBlock = React12.memo(/* @__PURE__ */ __name(function CategoryBlock2({
920
+ category,
921
+ onNavigate
922
+ }) {
923
+ return /* @__PURE__ */ jsxs("div", { className: "mb-2.5 last:mb-1", children: [
924
+ /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/50 select-none", children: category.category }),
925
+ /* @__PURE__ */ jsx("div", { children: category.rows.map((row) => /* @__PURE__ */ jsx(EndpointRow, { row, onNavigate }, row.key)) })
926
+ ] });
927
+ }, "CategoryBlock"));
928
+ function SchemaSection({ section, onNavigate }) {
929
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-2", children: [
930
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 sticky top-0 z-[1] bg-background/95 backdrop-blur-[2px] border-b border-border/40", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold uppercase tracking-[0.12em] text-foreground/80", children: section.sourceName }) }),
931
+ section.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key))
932
+ ] });
933
+ }
934
+ __name(SchemaSection, "SchemaSection");
935
+ function SidebarBody({ body, onNavigate }) {
936
+ if (body.kind === "flat") {
937
+ if (body.categories.length === 0) {
938
+ return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
939
+ }
940
+ return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key)) });
941
+ }
942
+ if (body.sections.length === 0) {
943
+ return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
944
+ }
945
+ return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.sections.map((section) => /* @__PURE__ */ jsx(SchemaSection, { section, onNavigate }, section.sourceId)) });
946
+ }
947
+ __name(SidebarBody, "SidebarBody");
948
+
949
+ // src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts
950
+ var METHOD_FILTERS = ["ALL", "GET", "POST", "PUT", "PATCH", "DELETE"];
951
+ function MethodChips({ value, onChange }) {
952
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1 pb-px", children: METHOD_FILTERS.map((m) => {
953
+ const active = value === m;
954
+ return /* @__PURE__ */ jsx(
955
+ "button",
956
+ {
957
+ type: "button",
958
+ onClick: () => onChange(m),
959
+ "aria-pressed": active,
960
+ className: cn(
961
+ "shrink-0 px-2 h-6 rounded font-mono text-[10px] font-semibold tracking-wide uppercase transition-colors",
962
+ active ? "bg-foreground text-background" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted"
963
+ ),
964
+ children: m
965
+ },
966
+ m
967
+ );
968
+ }) });
969
+ }
970
+ __name(MethodChips, "MethodChips");
971
+ function SearchInput({ value, onChange, placeholder }) {
972
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
973
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
974
+ /* @__PURE__ */ jsx(
975
+ Input,
976
+ {
977
+ placeholder: placeholder ?? "Search endpoints\u2026",
978
+ value,
979
+ onChange: (e) => onChange(e.target.value),
980
+ className: "pl-8 pr-7 h-8 text-xs"
981
+ }
982
+ ),
983
+ value && /* @__PURE__ */ jsx(
984
+ "button",
985
+ {
986
+ type: "button",
987
+ onClick: () => onChange(""),
988
+ "aria-label": "Clear search",
989
+ className: cn(
990
+ "absolute right-1.5 top-1/2 -translate-y-1/2 h-5 w-5 rounded",
991
+ "inline-flex items-center justify-center",
992
+ "text-muted-foreground/50 hover:text-foreground hover:bg-muted transition-colors"
993
+ ),
994
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
995
+ }
996
+ )
997
+ ] });
998
+ }
999
+ __name(SearchInput, "SearchInput");
1000
+ function Toolbar({
1001
+ schemas,
1002
+ currentSchemaId,
1003
+ onSchemaChange,
1004
+ showSchemaSelector,
1005
+ search,
1006
+ onSearchChange,
1007
+ methodFilter,
1008
+ onMethodFilterChange
1009
+ }) {
1010
+ const schemaOptions = React12.useMemo(
1011
+ () => schemas.map((s) => ({ value: s.id, label: s.name })),
1012
+ [schemas]
1013
+ );
1014
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 space-y-2", children: [
1015
+ showSchemaSelector && /* @__PURE__ */ jsx(
1016
+ Combobox,
1017
+ {
1018
+ options: schemaOptions,
1019
+ value: currentSchemaId ?? "",
1020
+ onValueChange: (id) => id && onSchemaChange(id),
1021
+ placeholder: "Select API",
1022
+ searchPlaceholder: "Search APIs\u2026",
1023
+ emptyText: "No APIs found",
1024
+ className: "w-full h-8 text-xs"
1025
+ }
1026
+ ),
1027
+ /* @__PURE__ */ jsx(SearchInput, { value: search, onChange: onSearchChange }),
1028
+ /* @__PURE__ */ jsx(MethodChips, { value: methodFilter, onChange: onMethodFilterChange })
1029
+ ] });
1030
+ }
1031
+ __name(Toolbar, "Toolbar");
1032
+ function useDebouncedValue(value, delayMs = 120) {
1033
+ const [debounced, setDebounced] = useState(value);
1034
+ useEffect(() => {
1035
+ const id = setTimeout(() => setDebounced(value), delayMs);
1036
+ return () => clearTimeout(id);
1037
+ }, [value, delayMs]);
1038
+ return debounced;
1039
+ }
1040
+ __name(useDebouncedValue, "useDebouncedValue");
1041
+ function DocsSidebar({
1042
+ info,
1043
+ endpoints,
1044
+ schemas,
1045
+ currentSchemaId,
1046
+ onSchemaChange,
1047
+ activeEndpointId,
1048
+ selectedVersion,
1049
+ onNavigate,
1050
+ grouping = "selector",
1051
+ endpointsBySchema,
1052
+ rawSchema,
1053
+ resolvedBaseUrl
1054
+ }) {
1055
+ const [search, setSearch] = useState("");
1056
+ const [methodFilter, setMethodFilter] = useState("ALL");
1057
+ const debouncedSearch = useDebouncedValue(search);
1058
+ const body = useMemo(() => {
1059
+ if (grouping === "sections") {
1060
+ return buildSectionsVM(
1061
+ schemas,
1062
+ endpointsBySchema ?? {},
1063
+ selectedVersion,
1064
+ debouncedSearch,
1065
+ methodFilter,
1066
+ activeEndpointId
1067
+ );
1068
+ }
1069
+ return buildFlatVM(
1070
+ endpoints,
1071
+ selectedVersion,
1072
+ debouncedSearch,
1073
+ methodFilter,
1074
+ activeEndpointId
1075
+ );
1076
+ }, [
1077
+ grouping,
1078
+ schemas,
1079
+ endpointsBySchema,
1080
+ endpoints,
1081
+ selectedVersion,
1082
+ debouncedSearch,
1083
+ methodFilter,
1084
+ activeEndpointId
1085
+ ]);
1086
+ const hasMultipleSchemas = schemas.length > 1;
1087
+ const showSchemaSelector = grouping === "selector" && hasMultipleSchemas;
1088
+ return /* @__PURE__ */ jsxs("aside", { className: "flex flex-col h-full min-h-0 border-r bg-muted/10", children: [
1089
+ /* @__PURE__ */ jsx(
1090
+ BrandHeader,
1091
+ {
1092
+ info,
1093
+ endpoints,
1094
+ rawSchema,
1095
+ resolvedBaseUrl
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ jsx(
1099
+ Toolbar,
1100
+ {
1101
+ schemas,
1102
+ currentSchemaId,
1103
+ onSchemaChange,
1104
+ showSchemaSelector,
1105
+ search,
1106
+ onSearchChange: setSearch,
1107
+ methodFilter,
1108
+ onMethodFilterChange: setMethodFilter
1109
+ }
1110
+ ),
1111
+ /* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsx(SidebarBody, { body, onNavigate }) })
1112
+ ] });
1113
+ }
1114
+ __name(DocsSidebar, "DocsSidebar");
1045
1115
 
1046
1116
  // src/tools/OpenapiViewer/utils/scrollParent.ts
1047
1117
  function getScrollParent(el) {
@@ -1079,6 +1149,7 @@ function scrollTargetTo(target, top) {
1079
1149
  }
1080
1150
  __name(scrollTargetTo, "scrollTargetTo");
1081
1151
  function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }) {
1152
+ const baseUrlRows = resolvedBaseUrl ? [{ url: resolvedBaseUrl, description: info.servers?.[0]?.description }] : (info.servers ?? []).map((s) => ({ url: s.url, description: s.description }));
1082
1153
  return /* @__PURE__ */ jsxs("section", { className: "pb-10 mb-10 border-b", children: [
1083
1154
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
1084
1155
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap min-w-0", children: [
@@ -1098,105 +1169,775 @@ function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }) {
1098
1169
  )
1099
1170
  ] }),
1100
1171
  info.description && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: info.description }) }),
1101
- info.servers && info.servers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
1172
+ baseUrlRows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
1102
1173
  /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: "Base URL" }),
1103
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: info.servers.map((s, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1104
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs px-2 py-1 rounded bg-muted border", children: s.url }),
1105
- s.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: s.description })
1106
- ] }, `${s.url}-${i}`)) })
1174
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: baseUrlRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1175
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs px-2 py-1 rounded bg-muted border", children: row.url }),
1176
+ row.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.description })
1177
+ ] }, `${row.url}-${i}`)) })
1107
1178
  ] })
1108
1179
  ] });
1109
1180
  }
1110
1181
  __name(ApiIntroSection, "ApiIntroSection");
1182
+ var EndpointDocContext = createContext(null);
1183
+ function EndpointDocProvider({ endpointId, method, children }) {
1184
+ const value = useMemo(() => ({ endpointId, method }), [endpointId, method]);
1185
+ return /* @__PURE__ */ jsx(EndpointDocContext.Provider, { value, children });
1186
+ }
1187
+ __name(EndpointDocProvider, "EndpointDocProvider");
1188
+ function useEndpointDocContext() {
1189
+ const ctx = useContext(EndpointDocContext);
1190
+ if (!ctx) {
1191
+ throw new Error(
1192
+ "[OpenapiViewer] useEndpointDocContext must be used inside <EndpointDocProvider>."
1193
+ );
1194
+ }
1195
+ return ctx;
1196
+ }
1197
+ __name(useEndpointDocContext, "useEndpointDocContext");
1198
+ var sectionKey = /* @__PURE__ */ __name((endpointId, sectionId) => `${endpointId}:${sectionId}`, "sectionKey");
1199
+ var initialState = {
1200
+ openSections: {},
1201
+ activeCodeTab: {}
1202
+ };
1203
+ var useEndpointDocStore = create()(
1204
+ persist(
1205
+ (set) => ({
1206
+ ...initialState,
1207
+ toggleSection: /* @__PURE__ */ __name((endpointId, sectionId) => set((state) => {
1208
+ const key = sectionKey(endpointId, sectionId);
1209
+ const current = state.openSections[key];
1210
+ return {
1211
+ openSections: {
1212
+ ...state.openSections,
1213
+ // If there's no explicit override yet, the user's
1214
+ // first click means "flip from the default". We
1215
+ // assume the default was ``true`` for the most
1216
+ // common case (bodies/responses) and ``false``
1217
+ // otherwise; the Section component tracks this
1218
+ // via its ``defaultOpen`` prop and never calls
1219
+ // toggle on sections whose defaults match the
1220
+ // next state.
1221
+ [key]: current === void 0 ? false : !current
1222
+ }
1223
+ };
1224
+ }), "toggleSection"),
1225
+ setSectionOpen: /* @__PURE__ */ __name((endpointId, sectionId, open) => set((state) => ({
1226
+ openSections: {
1227
+ ...state.openSections,
1228
+ [sectionKey(endpointId, sectionId)]: open
1229
+ }
1230
+ })), "setSectionOpen"),
1231
+ setCodeTab: /* @__PURE__ */ __name((endpointId, tab) => set((state) => ({
1232
+ activeCodeTab: {
1233
+ ...state.activeCodeTab,
1234
+ [endpointId]: tab
1235
+ }
1236
+ })), "setCodeTab"),
1237
+ expandAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
1238
+ const next = { ...state.openSections };
1239
+ for (const sid of sectionIds) {
1240
+ next[sectionKey(endpointId, sid)] = true;
1241
+ }
1242
+ return { openSections: next };
1243
+ }), "expandAll"),
1244
+ collapseAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
1245
+ const next = { ...state.openSections };
1246
+ for (const sid of sectionIds) {
1247
+ next[sectionKey(endpointId, sid)] = false;
1248
+ }
1249
+ return { openSections: next };
1250
+ }), "collapseAll")
1251
+ }),
1252
+ {
1253
+ name: "openapi-viewer:endpoint-doc",
1254
+ storage: createJSONStorage(() => {
1255
+ if (typeof window === "undefined") {
1256
+ return {
1257
+ getItem: /* @__PURE__ */ __name(() => null, "getItem"),
1258
+ setItem: /* @__PURE__ */ __name(() => {
1259
+ }, "setItem"),
1260
+ removeItem: /* @__PURE__ */ __name(() => {
1261
+ }, "removeItem")
1262
+ };
1263
+ }
1264
+ return window.sessionStorage;
1265
+ }),
1266
+ // Only persist user overrides, not the functions. Zustand
1267
+ // serialises everything by default and logs a warning on
1268
+ // non-serialisable values; partialize keeps the payload lean.
1269
+ partialize: /* @__PURE__ */ __name((state) => ({
1270
+ openSections: state.openSections,
1271
+ activeCodeTab: state.activeCodeTab
1272
+ }), "partialize")
1273
+ }
1274
+ )
1275
+ );
1276
+
1277
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts
1278
+ function useIsSectionOpen(endpointId, sectionId, defaultOpen) {
1279
+ return useEndpointDocStore((s) => {
1280
+ const explicit = s.openSections[sectionKey(endpointId, sectionId)];
1281
+ return explicit === void 0 ? defaultOpen : explicit;
1282
+ });
1283
+ }
1284
+ __name(useIsSectionOpen, "useIsSectionOpen");
1285
+ function useActiveCodeTab(endpointId, fallback = "curl") {
1286
+ return useEndpointDocStore((s) => s.activeCodeTab[endpointId] ?? fallback);
1287
+ }
1288
+ __name(useActiveCodeTab, "useActiveCodeTab");
1289
+ function LanguageTabs({ activeId, onChange }) {
1290
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1", children: CODE_SAMPLE_TARGETS.map((t) => /* @__PURE__ */ jsx(
1291
+ "button",
1292
+ {
1293
+ type: "button",
1294
+ onClick: () => onChange(t.id),
1295
+ className: cn(
1296
+ "shrink-0 h-7 px-2.5 rounded text-xs font-medium transition-colors",
1297
+ activeId === t.id ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
1298
+ ),
1299
+ children: t.label
1300
+ },
1301
+ t.id
1302
+ )) });
1303
+ }
1304
+ __name(LanguageTabs, "LanguageTabs");
1305
+ function useCodeSnippet({
1306
+ endpoint,
1307
+ body,
1308
+ parameters,
1309
+ headers,
1310
+ baseUrl,
1311
+ activeId
1312
+ }) {
1313
+ const effectiveBody = body ?? endpoint.requestBody?.example;
1314
+ const har = useMemo(() => {
1315
+ const h = buildHarRequest({
1316
+ endpoint,
1317
+ body: effectiveBody,
1318
+ parameters,
1319
+ headers,
1320
+ baseUrl
1321
+ });
1322
+ return baseUrl ? h : { ...h, url: resolveAbsolute(h.url) };
1323
+ }, [endpoint, effectiveBody, parameters, headers, baseUrl]);
1324
+ return useMemo(() => {
1325
+ const target = CODE_SAMPLE_TARGETS.find((t) => t.id === activeId);
1326
+ const code = renderSnippet(har, activeId);
1327
+ return {
1328
+ snippet: code ?? `// Snippet for ${target.label} is unavailable for this request.`,
1329
+ prism: target.prism
1330
+ };
1331
+ }, [har, activeId]);
1332
+ }
1333
+ __name(useCodeSnippet, "useCodeSnippet");
1334
+ function CodeSamples({ endpoint, body, parameters, headers, baseUrl }) {
1335
+ const { endpointId } = useEndpointDocContext();
1336
+ const activeId = useActiveCodeTab(endpointId);
1337
+ const setCodeTab = useEndpointDocStore((s) => s.setCodeTab);
1338
+ const { snippet, prism } = useCodeSnippet({
1339
+ endpoint,
1340
+ body,
1341
+ parameters,
1342
+ headers,
1343
+ baseUrl,
1344
+ activeId
1345
+ });
1346
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
1347
+ /* @__PURE__ */ jsx(LanguageTabs, { activeId, onChange: (id) => setCodeTab(endpointId, id) }),
1348
+ /* @__PURE__ */ jsx(
1349
+ PrettyCode_default,
1350
+ {
1351
+ data: snippet,
1352
+ language: prism,
1353
+ isCompact: true,
1354
+ maxLines: 20
1355
+ }
1356
+ )
1357
+ ] });
1358
+ }
1359
+ __name(CodeSamples, "CodeSamples");
1360
+ function IconButton({ label, onClick, children, active }) {
1361
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
1362
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ onClick,
1367
+ "aria-label": label,
1368
+ className: cn(
1369
+ "shrink-0 h-6 w-6 inline-flex items-center justify-center rounded",
1370
+ "text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors",
1371
+ active && "text-emerald-500 hover:text-emerald-500"
1372
+ ),
1373
+ children
1374
+ }
1375
+ ) }),
1376
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", className: "text-[11px]", children: label })
1377
+ ] });
1378
+ }
1379
+ __name(IconButton, "IconButton");
1380
+ function MetaActions({ anchor, endpointMarkdown, presentSections }) {
1381
+ const { endpointId } = useEndpointDocContext();
1382
+ const expandAll = useEndpointDocStore((s) => s.expandAll);
1383
+ const collapseAll = useEndpointDocStore((s) => s.collapseAll);
1384
+ const openSections = useEndpointDocStore((s) => s.openSections);
1385
+ const [justCopied, setJustCopied] = useState(null);
1386
+ const flash = useCallback((which) => {
1387
+ setJustCopied(which);
1388
+ setTimeout(() => setJustCopied(null), 1200);
1389
+ }, []);
1390
+ const mostlyOpen = useMemo(() => {
1391
+ if (presentSections.length === 0) return false;
1392
+ let openCount = 0;
1393
+ for (const sid of presentSections) {
1394
+ if (openSections[sectionKey(endpointId, sid)]) openCount += 1;
1395
+ }
1396
+ return openCount > presentSections.length / 2;
1397
+ }, [openSections, presentSections, endpointId]);
1398
+ const copyLink = useCallback(() => {
1399
+ if (typeof window === "undefined") return;
1400
+ const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
1401
+ void navigator.clipboard?.writeText(url).then(() => flash("link"));
1402
+ }, [anchor, flash]);
1403
+ const copyMarkdown = useCallback(() => {
1404
+ if (typeof window === "undefined") return;
1405
+ void navigator.clipboard?.writeText(endpointMarkdown).then(() => flash("md"));
1406
+ }, [endpointMarkdown, flash]);
1407
+ const toggleAll = useCallback(() => {
1408
+ if (mostlyOpen) collapseAll(endpointId, presentSections);
1409
+ else expandAll(endpointId, presentSections);
1410
+ }, [mostlyOpen, collapseAll, expandAll, endpointId, presentSections]);
1411
+ return /* @__PURE__ */ jsx(SafeTooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1412
+ /* @__PURE__ */ jsx(
1413
+ IconButton,
1414
+ {
1415
+ label: justCopied === "link" ? "Copied!" : "Copy link to endpoint",
1416
+ onClick: copyLink,
1417
+ active: justCopied === "link",
1418
+ children: justCopied === "link" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
1419
+ }
1420
+ ),
1421
+ /* @__PURE__ */ jsx(
1422
+ IconButton,
1423
+ {
1424
+ label: justCopied === "md" ? "Copied!" : "Copy as Markdown (for AI)",
1425
+ onClick: copyMarkdown,
1426
+ active: justCopied === "md",
1427
+ children: justCopied === "md" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileCode2, { className: "h-3.5 w-3.5" })
1428
+ }
1429
+ ),
1430
+ presentSections.length >= 2 && /* @__PURE__ */ jsx(
1431
+ IconButton,
1432
+ {
1433
+ label: mostlyOpen ? "Collapse all sections" : "Expand all sections",
1434
+ onClick: toggleAll,
1435
+ children: mostlyOpen ? /* @__PURE__ */ jsx(ChevronsDownUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3.5 w-3.5" })
1436
+ }
1437
+ )
1438
+ ] }) });
1439
+ }
1440
+ __name(MetaActions, "MetaActions");
1441
+ function PathDisplay({ path }) {
1442
+ return /* @__PURE__ */ jsx(
1443
+ "code",
1444
+ {
1445
+ className: "block font-mono text-lg md:text-xl font-semibold text-foreground leading-tight",
1446
+ style: { overflowWrap: "anywhere", wordBreak: "break-word" },
1447
+ children: relativePath(path)
1448
+ }
1449
+ );
1450
+ }
1451
+ __name(PathDisplay, "PathDisplay");
1452
+ function EndpointHeader({
1453
+ endpoint,
1454
+ anchor,
1455
+ isLoadedInPlayground,
1456
+ onTryIt,
1457
+ presentSections
1458
+ }) {
1459
+ const endpointMd = useMemo(() => endpointToMarkdown(endpoint), [endpoint]);
1460
+ return /* @__PURE__ */ jsxs("header", { className: "space-y-3", children: [
1461
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
1462
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
1463
+ /* @__PURE__ */ jsx(MethodBadge, { method: endpoint.method }),
1464
+ /* @__PURE__ */ jsx(
1465
+ MetaActions,
1466
+ {
1467
+ anchor,
1468
+ endpointMarkdown: endpointMd,
1469
+ presentSections
1470
+ }
1471
+ )
1472
+ ] }),
1473
+ /* @__PURE__ */ jsxs(
1474
+ Button,
1475
+ {
1476
+ size: "sm",
1477
+ variant: isLoadedInPlayground ? "secondary" : "default",
1478
+ onClick: onTryIt,
1479
+ className: "ml-auto h-7 text-xs gap-1.5 px-2.5",
1480
+ children: [
1481
+ /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
1482
+ isLoadedInPlayground ? "Loaded" : "Try it"
1483
+ ]
1484
+ }
1485
+ )
1486
+ ] }),
1487
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(PathDisplay, { path: endpoint.path }) }),
1488
+ endpoint.description && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-sm", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: endpoint.description }) })
1489
+ ] });
1490
+ }
1491
+ __name(EndpointHeader, "EndpointHeader");
1492
+ function ParamRow({ param }) {
1493
+ return /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
1494
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1495
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: param.name }),
1496
+ param.required && /* @__PURE__ */ jsx(
1497
+ "span",
1498
+ {
1499
+ title: "Required",
1500
+ className: "text-[9px] text-destructive font-bold leading-none",
1501
+ children: "*"
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: param.type })
1505
+ ] }),
1506
+ param.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: param.description })
1507
+ ] });
1508
+ }
1509
+ __name(ParamRow, "ParamRow");
1510
+ function ParamGroup({ label, params }) {
1511
+ if (params.length === 0) return null;
1512
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1513
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-muted-foreground/60 px-1", children: label }),
1514
+ /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: params.map((p) => /* @__PURE__ */ jsx(ParamRow, { param: p }, `${label}-${p.name}`)) })
1515
+ ] });
1516
+ }
1517
+ __name(ParamGroup, "ParamGroup");
1518
+ function Parameters({ pathParams, queryParams }) {
1519
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1520
+ /* @__PURE__ */ jsx(ParamGroup, { label: "Path", params: pathParams }),
1521
+ /* @__PURE__ */ jsx(ParamGroup, { label: "Query", params: queryParams })
1522
+ ] });
1523
+ }
1524
+ __name(Parameters, "Parameters");
1111
1525
 
1112
- // src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts
1113
- var MAX_DEPTH = 2;
1526
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts
1527
+ var MAX_DEPTH = 5;
1528
+ function mergeAllOf(branches) {
1529
+ const properties = {};
1530
+ const required = [];
1531
+ for (const b of branches) {
1532
+ if (b.properties) Object.assign(properties, b.properties);
1533
+ if (Array.isArray(b.required)) required.push(...b.required);
1534
+ }
1535
+ return { type: "object", properties, required };
1536
+ }
1537
+ __name(mergeAllOf, "mergeAllOf");
1114
1538
  function describeType(node) {
1115
- if (!node.type && node.properties) return "object";
1116
- const base = node.type || "any";
1117
- if (base === "array") {
1118
- const itemType = node.items ? describeType(node.items) : "any";
1119
- return `array<${itemType}>`;
1539
+ if (node.type === "array") {
1540
+ const itemLabel = node.items ? describeType(node.items).label : "any";
1541
+ return { label: `array<${itemLabel}>`, kind: "array" };
1542
+ }
1543
+ if (node.type === "object" || node.properties) {
1544
+ return { label: "object", kind: "object" };
1120
1545
  }
1546
+ const base = node.type || "any";
1121
1547
  if (Array.isArray(node.enum) && node.enum.length > 0) {
1122
- return `${base} enum`;
1548
+ return { label: `${base} enum`, kind: "primitive" };
1549
+ }
1550
+ if (node.format) {
1551
+ return { label: `${base} (${node.format})`, kind: "primitive" };
1123
1552
  }
1124
- if (node.format) return `${base} (${node.format})`;
1125
- return base;
1553
+ return { label: base, kind: "primitive" };
1126
1554
  }
1127
1555
  __name(describeType, "describeType");
1128
- function schemaToFields(schema, prefix = "", depth = 0) {
1129
- if (!schema || depth > MAX_DEPTH) return [];
1130
- if (schema.type === "array") {
1131
- if (!schema.items) {
1132
- return [{ name: prefix || "[]", type: "array", required: false }];
1556
+ function resolveCombinators(node) {
1557
+ if (Array.isArray(node.allOf) && node.allOf.length > 0) {
1558
+ return { ...mergeAllOf(node.allOf), description: node.description };
1559
+ }
1560
+ if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
1561
+ return { ...node.oneOf[0], description: node.description ?? node.oneOf[0].description };
1562
+ }
1563
+ if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
1564
+ return { ...node.anyOf[0], description: node.description ?? node.anyOf[0].description };
1565
+ }
1566
+ return node;
1567
+ }
1568
+ __name(resolveCombinators, "resolveCombinators");
1569
+ function buildNode(name, schema, isRequired, depth) {
1570
+ const resolved = resolveCombinators(schema);
1571
+ const { label, kind } = describeType(resolved);
1572
+ const node = {
1573
+ name,
1574
+ type: label,
1575
+ kind,
1576
+ required: isRequired,
1577
+ description: resolved.description
1578
+ };
1579
+ if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
1580
+ node.enumValues = resolved.enum.map((v) => String(v));
1581
+ }
1582
+ if (depth >= MAX_DEPTH) return node;
1583
+ if (kind === "object" && resolved.properties) {
1584
+ const required = new Set(resolved.required ?? []);
1585
+ node.children = Object.entries(resolved.properties).map(
1586
+ ([key, child]) => buildNode(key, child, required.has(key), depth + 1)
1587
+ );
1588
+ } else if (kind === "array" && resolved.items) {
1589
+ node.children = [buildNode("[]", resolved.items, false, depth + 1)];
1590
+ }
1591
+ return node;
1592
+ }
1593
+ __name(buildNode, "buildNode");
1594
+ function buildSchemaTree(schema) {
1595
+ if (!schema) return [];
1596
+ const root = buildNode("", schema, false, 0);
1597
+ if (root.children && root.children.length > 0) return root.children;
1598
+ if (root.kind === "primitive" || !root.children && root.name === "") {
1599
+ return [{ ...root, name: root.name || "(body)" }];
1600
+ }
1601
+ return [];
1602
+ }
1603
+ __name(buildSchemaTree, "buildSchemaTree");
1604
+ function FieldRow({ field, depth, showTreeLine = true }) {
1605
+ const isExpandable = (field.kind === "object" || field.kind === "array") && Array.isArray(field.children) && field.children.length > 0;
1606
+ const [open, setOpen] = useState(depth < 2);
1607
+ const padLeft = showTreeLine ? depth * 14 : 0;
1608
+ return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
1609
+ /* @__PURE__ */ jsxs(
1610
+ "div",
1611
+ {
1612
+ className: cn(
1613
+ "grid grid-cols-[16px_minmax(0,1fr)] items-baseline gap-2 px-3 py-2",
1614
+ isExpandable && "cursor-pointer hover:bg-muted/30"
1615
+ ),
1616
+ style: { paddingLeft: 12 + padLeft },
1617
+ onClick: () => isExpandable && setOpen((v) => !v),
1618
+ role: isExpandable ? "button" : void 0,
1619
+ "aria-expanded": isExpandable ? open : void 0,
1620
+ children: [
1621
+ /* @__PURE__ */ jsx(
1622
+ ChevronRight,
1623
+ {
1624
+ className: cn(
1625
+ "h-3.5 w-3.5 text-muted-foreground/50 shrink-0 transition-transform",
1626
+ !isExpandable && "opacity-0",
1627
+ open && "rotate-90"
1628
+ )
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
1632
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1633
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: field.name }),
1634
+ field.required && /* @__PURE__ */ jsx(
1635
+ "span",
1636
+ {
1637
+ title: "Required",
1638
+ className: "text-[9px] text-destructive font-bold leading-none",
1639
+ children: "*"
1640
+ }
1641
+ ),
1642
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: field.type })
1643
+ ] }),
1644
+ field.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: field.description }),
1645
+ field.enumValues && field.enumValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 pt-0.5", children: field.enumValues.map((v) => /* @__PURE__ */ jsx(
1646
+ "code",
1647
+ {
1648
+ className: "inline-flex items-center rounded border border-border/60 bg-muted/40 px-1.5 py-px font-mono text-[10px] text-muted-foreground",
1649
+ children: v
1650
+ },
1651
+ v
1652
+ )) })
1653
+ ] })
1654
+ ]
1655
+ }
1656
+ ),
1657
+ isExpandable && open && /* @__PURE__ */ jsx("div", { children: field.children.map((child, i) => /* @__PURE__ */ jsx(
1658
+ FieldRow,
1659
+ {
1660
+ field: child,
1661
+ depth: depth + 1
1662
+ },
1663
+ `${child.name}-${i}`
1664
+ )) })
1665
+ ] });
1666
+ }
1667
+ __name(FieldRow, "FieldRow");
1668
+ function SchemaFields({ schema }) {
1669
+ const tree = useMemo(() => buildSchemaTree(schema), [schema]);
1670
+ if (tree.length === 0) return null;
1671
+ return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: tree.map((node, i) => /* @__PURE__ */ jsx(FieldRow, { field: node, depth: 0 }, `${node.name}-${i}`)) });
1672
+ }
1673
+ __name(SchemaFields, "SchemaFields");
1674
+ function RequestBody({ body }) {
1675
+ const typeLabel = body.schema ? body.type === "array" ? `array<${body.schema.items?.type ?? "object"}>` : body.type : body.type;
1676
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1677
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1678
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/80", children: typeLabel }),
1679
+ body.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: body.description })
1680
+ ] }),
1681
+ body.schema && /* @__PURE__ */ jsx(SchemaFields, { schema: body.schema })
1682
+ ] });
1683
+ }
1684
+ __name(RequestBody, "RequestBody");
1685
+ var EXAMPLE_JSON_TREE_CONFIG = {
1686
+ maxAutoExpandDepth: 2,
1687
+ maxAutoExpandArrayItems: 5,
1688
+ maxAutoExpandObjectKeys: 8,
1689
+ maxStringLength: 160,
1690
+ collectionLimit: 25,
1691
+ showCollectionInfo: true,
1692
+ showExpandControls: false,
1693
+ showActionButtons: false,
1694
+ preserveKeyOrder: true,
1695
+ className: "border-0 rounded-none"
1696
+ };
1697
+ function ResponseBody({ example, contentType }) {
1698
+ const parsed = useMemo(() => {
1699
+ try {
1700
+ return JSON.parse(example);
1701
+ } catch {
1702
+ return null;
1133
1703
  }
1134
- const inner = schemaToFields(schema.items, prefix ? `${prefix}[]` : "[]", depth);
1135
- if (inner.length === 0) {
1136
- return [
1704
+ }, [example]);
1705
+ return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/20", children: [
1706
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50", children: [
1707
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: contentType ?? "application/json" }),
1708
+ /* @__PURE__ */ jsx(
1709
+ CopyButton,
1137
1710
  {
1138
- name: prefix || "[]",
1139
- type: describeType(schema),
1140
- required: false,
1141
- description: schema.description
1711
+ value: example,
1712
+ variant: "ghost",
1713
+ size: "sm",
1714
+ className: "h-6 px-2 text-[10px] text-muted-foreground",
1715
+ children: "Copy"
1142
1716
  }
1143
- ];
1144
- }
1145
- return inner;
1146
- }
1147
- if (schema.type !== "object" && !schema.properties) {
1148
- return [
1717
+ )
1718
+ ] }),
1719
+ parsed != null ? /* @__PURE__ */ jsx(
1720
+ JsonTree_default,
1149
1721
  {
1150
- name: prefix || "(body)",
1151
- type: describeType(schema),
1152
- required: false,
1153
- description: schema.description
1722
+ title: "",
1723
+ data: parsed,
1724
+ mode: "compact",
1725
+ config: EXAMPLE_JSON_TREE_CONFIG
1154
1726
  }
1155
- ];
1727
+ ) : /* @__PURE__ */ jsx("pre", { className: "p-3 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: example })
1728
+ ] });
1729
+ }
1730
+ __name(ResponseBody, "ResponseBody");
1731
+ function StatusTag({ code }) {
1732
+ const numeric = Number.parseInt(code, 10);
1733
+ const cls = !Number.isFinite(numeric) ? "bg-muted text-muted-foreground border-border" : numeric >= 500 ? "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25" : numeric >= 400 ? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25" : numeric >= 300 ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25" : "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
1734
+ return /* @__PURE__ */ jsx("span", { className: cn(
1735
+ "inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums",
1736
+ cls
1737
+ ), children: code });
1738
+ }
1739
+ __name(StatusTag, "StatusTag");
1740
+ function ResponseRow({ response }) {
1741
+ const hasExample = Boolean(response.example);
1742
+ const numeric = Number.parseInt(response.code, 10);
1743
+ const isSuccess = Number.isFinite(numeric) && numeric >= 200 && numeric < 300;
1744
+ const [open, setOpen] = useState(hasExample && isSuccess);
1745
+ if (!hasExample) {
1746
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2 bg-background", children: [
1747
+ /* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
1748
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0", children: response.description })
1749
+ ] });
1156
1750
  }
1157
- const required = new Set(schema.required ?? []);
1158
- const rows = [];
1159
- const props = schema.properties ?? {};
1160
- for (const [key, node] of Object.entries(props)) {
1161
- const fullName = prefix ? `${prefix}.${key}` : key;
1162
- const isRequired = required.has(key);
1163
- const isNestedExpandable = node.type === "object" && node.properties || node.type === "array" && node.items;
1164
- if (isNestedExpandable && depth < MAX_DEPTH) {
1165
- rows.push({
1166
- name: fullName,
1167
- type: describeType(node),
1168
- required: isRequired,
1169
- description: node.description
1170
- });
1171
- rows.push(...schemaToFields(node, fullName, depth + 1));
1172
- } else {
1173
- rows.push({
1174
- name: fullName,
1175
- type: describeType(node),
1176
- required: isRequired,
1177
- description: node.description
1751
+ return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
1752
+ /* @__PURE__ */ jsxs(
1753
+ "button",
1754
+ {
1755
+ type: "button",
1756
+ onClick: () => setOpen((v) => !v),
1757
+ className: "w-full flex items-center gap-3 px-3 py-2 text-left hover:bg-muted/40 cursor-pointer transition-colors",
1758
+ "aria-expanded": open,
1759
+ children: [
1760
+ /* @__PURE__ */ jsx(
1761
+ ChevronRight,
1762
+ {
1763
+ className: cn(
1764
+ "h-3.5 w-3.5 text-muted-foreground/60 transition-transform shrink-0",
1765
+ open && "rotate-90"
1766
+ )
1767
+ }
1768
+ ),
1769
+ /* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
1770
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0 flex-1", children: response.description })
1771
+ ]
1772
+ }
1773
+ ),
1774
+ open && /* @__PURE__ */ jsx(
1775
+ ResponseBody,
1776
+ {
1777
+ example: response.example,
1778
+ contentType: response.contentType
1779
+ }
1780
+ )
1781
+ ] });
1782
+ }
1783
+ __name(ResponseRow, "ResponseRow");
1784
+ function Responses({ responses }) {
1785
+ return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: responses.map((r) => /* @__PURE__ */ jsx(ResponseRow, { response: r }, r.code)) });
1786
+ }
1787
+ __name(Responses, "Responses");
1788
+
1789
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts
1790
+ var DEFAULTS_BY_METHOD = {
1791
+ GET: { parameters: true, responses: true },
1792
+ DELETE: { parameters: true, responses: true },
1793
+ POST: { requestBody: true, responses: true },
1794
+ PUT: { requestBody: true, responses: true },
1795
+ PATCH: { requestBody: true, responses: true }
1796
+ };
1797
+ function defaultSectionOpen(method, sectionId) {
1798
+ return DEFAULTS_BY_METHOD[method.toUpperCase()]?.[sectionId] ?? false;
1799
+ }
1800
+ __name(defaultSectionOpen, "defaultSectionOpen");
1801
+
1802
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts
1803
+ var ALL_SECTION_IDS = [
1804
+ "parameters",
1805
+ "requestBody",
1806
+ "responses",
1807
+ "codeSamples"
1808
+ ];
1809
+
1810
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts
1811
+ function parseSectionHash(hash) {
1812
+ const raw = hash.startsWith("#") ? hash.slice(1) : hash;
1813
+ if (!raw.startsWith("section=")) return null;
1814
+ const value = raw.slice("section=".length);
1815
+ const dot = value.lastIndexOf(".");
1816
+ if (dot <= 0 || dot === value.length - 1) return null;
1817
+ const endpointId = value.slice(0, dot);
1818
+ const sectionIdCandidate = value.slice(dot + 1);
1819
+ if (!ALL_SECTION_IDS.includes(sectionIdCandidate)) return null;
1820
+ return { endpointId, sectionId: sectionIdCandidate };
1821
+ }
1822
+ __name(parseSectionHash, "parseSectionHash");
1823
+ function buildSectionHash(endpointId, sectionId) {
1824
+ return `section=${endpointId}.${sectionId}`;
1825
+ }
1826
+ __name(buildSectionHash, "buildSectionHash");
1827
+ function useSectionHashRouter() {
1828
+ const setSectionOpen = useEndpointDocStore((s) => s.setSectionOpen);
1829
+ useEffect(() => {
1830
+ if (typeof window === "undefined") return;
1831
+ function apply() {
1832
+ const parsed = parseSectionHash(window.location.hash);
1833
+ if (!parsed) return;
1834
+ setSectionOpen(parsed.endpointId, parsed.sectionId, true);
1835
+ requestAnimationFrame(() => {
1836
+ const el = document.getElementById(parsed.endpointId);
1837
+ el?.scrollIntoView({ behavior: "smooth", block: "start" });
1178
1838
  });
1179
1839
  }
1180
- }
1181
- return rows;
1840
+ __name(apply, "apply");
1841
+ apply();
1842
+ window.addEventListener("hashchange", apply);
1843
+ return () => window.removeEventListener("hashchange", apply);
1844
+ }, [setSectionOpen]);
1182
1845
  }
1183
- __name(schemaToFields, "schemaToFields");
1184
- function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId }) {
1185
- const scopedSchemaId = schemaId ?? endpoint.schemaId ?? null;
1186
- const anchor = endpointAnchor(endpoint, scopedSchemaId);
1187
- const pathParams = endpoint.parameters?.filter((p) => endpoint.path.includes(`{${p.name}}`)) ?? [];
1188
- const queryParams = endpoint.parameters?.filter((p) => !endpoint.path.includes(`{${p.name}}`)) ?? [];
1846
+ __name(useSectionHashRouter, "useSectionHashRouter");
1847
+ function SectionHeader({ sectionId, title, badge, open, onToggle }) {
1848
+ const { endpointId } = useEndpointDocContext();
1189
1849
  const [copied, setCopied] = useState(false);
1190
- const copyAnchor = useCallback(() => {
1850
+ const copyHash = /* @__PURE__ */ __name((e) => {
1851
+ e.stopPropagation();
1191
1852
  if (typeof window === "undefined") return;
1192
- const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
1853
+ const hash = buildSectionHash(endpointId, sectionId);
1854
+ const url = `${window.location.origin}${window.location.pathname}#${hash}`;
1193
1855
  void navigator.clipboard?.writeText(url).then(() => {
1194
1856
  setCopied(true);
1195
1857
  setTimeout(() => setCopied(false), 1200);
1196
1858
  });
1197
- }, [anchor]);
1198
- const endpointMd = useMemo(() => endpointToMarkdown(endpoint), [endpoint]);
1859
+ }, "copyHash");
1199
1860
  return /* @__PURE__ */ jsxs(
1861
+ "div",
1862
+ {
1863
+ className: cn(
1864
+ "group/section w-full flex items-center gap-2 py-1.5 -ml-1 px-1 rounded cursor-pointer",
1865
+ "hover:bg-muted/30 transition-colors"
1866
+ ),
1867
+ onClick: onToggle,
1868
+ role: "button",
1869
+ "aria-expanded": open,
1870
+ tabIndex: 0,
1871
+ onKeyDown: (e) => {
1872
+ if (e.key === "Enter" || e.key === " ") {
1873
+ e.preventDefault();
1874
+ onToggle();
1875
+ }
1876
+ },
1877
+ children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ ChevronDown,
1880
+ {
1881
+ className: cn(
1882
+ "h-3.5 w-3.5 text-muted-foreground/50 transition-transform shrink-0",
1883
+ !open && "-rotate-90"
1884
+ )
1885
+ }
1886
+ ),
1887
+ /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground/80", children: title }),
1888
+ typeof badge === "number" && badge > 0 && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50 tabular-nums", children: badge }),
1889
+ /* @__PURE__ */ jsx(
1890
+ "button",
1891
+ {
1892
+ type: "button",
1893
+ onClick: copyHash,
1894
+ title: "Copy link to this section",
1895
+ className: cn(
1896
+ "ml-auto shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all",
1897
+ "opacity-0 group-hover/section:opacity-100 focus-visible:opacity-100",
1898
+ copied && "opacity-100 text-emerald-500"
1899
+ ),
1900
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" })
1901
+ }
1902
+ )
1903
+ ]
1904
+ }
1905
+ );
1906
+ }
1907
+ __name(SectionHeader, "SectionHeader");
1908
+ function Section({ id, title, badge, children }) {
1909
+ const { endpointId, method } = useEndpointDocContext();
1910
+ const defaultOpen = defaultSectionOpen(method, id);
1911
+ const open = useIsSectionOpen(endpointId, id, defaultOpen);
1912
+ const toggleSection = useEndpointDocStore((s) => s.toggleSection);
1913
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
1914
+ /* @__PURE__ */ jsx(
1915
+ SectionHeader,
1916
+ {
1917
+ sectionId: id,
1918
+ title,
1919
+ badge,
1920
+ open,
1921
+ onToggle: () => toggleSection(endpointId, id)
1922
+ }
1923
+ ),
1924
+ open && /* @__PURE__ */ jsx("div", { children })
1925
+ ] });
1926
+ }
1927
+ __name(Section, "Section");
1928
+ function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId }) {
1929
+ const scopedSchemaId = schemaId ?? endpoint.schemaId ?? null;
1930
+ const anchor = endpointAnchor(endpoint, scopedSchemaId);
1931
+ const pathParams = endpoint.parameters?.filter((p) => endpoint.path.includes(`{${p.name}}`)) ?? [];
1932
+ const queryParams = endpoint.parameters?.filter((p) => !endpoint.path.includes(`{${p.name}}`)) ?? [];
1933
+ const hasParameters = pathParams.length > 0 || queryParams.length > 0;
1934
+ const hasResponses = (endpoint.responses?.length ?? 0) > 0;
1935
+ const presentSections = [];
1936
+ if (hasParameters) presentSections.push("parameters");
1937
+ if (endpoint.requestBody) presentSections.push("requestBody");
1938
+ presentSections.push("codeSamples");
1939
+ if (hasResponses) presentSections.push("responses");
1940
+ return /* @__PURE__ */ jsx(EndpointDocProvider, { endpointId: anchor, method: endpoint.method, children: /* @__PURE__ */ jsxs(
1200
1941
  "section",
1201
1942
  {
1202
1943
  id: anchor,
@@ -1204,164 +1945,43 @@ function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId }) {
1204
1945
  "data-schema-id": scopedSchemaId ?? "",
1205
1946
  className: "scroll-mt-24 py-10 first:pt-0",
1206
1947
  children: [
1207
- /* @__PURE__ */ jsxs("header", { className: "space-y-4", children: [
1208
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 flex-wrap", children: [
1209
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2.5 min-w-0 flex-1 group/header", children: [
1210
- /* @__PURE__ */ jsx("span", { className: "shrink-0 translate-y-[2px]", children: /* @__PURE__ */ jsx(MethodBadge, { method: endpoint.method }) }),
1211
- /* @__PURE__ */ jsx(
1212
- "code",
1213
- {
1214
- className: "font-mono text-sm md:text-[15px] font-medium text-foreground leading-snug min-w-0",
1215
- style: { overflowWrap: "anywhere", wordBreak: "break-word" },
1216
- children: relativePath(endpoint.path)
1217
- }
1218
- ),
1219
- /* @__PURE__ */ jsx(
1220
- "button",
1221
- {
1222
- type: "button",
1223
- onClick: copyAnchor,
1224
- title: "Copy link to this section",
1225
- className: cn(
1226
- "shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all",
1227
- "opacity-0 group-hover/header:opacity-100",
1228
- copied && "opacity-100 text-emerald-500"
1229
- ),
1230
- children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
1231
- }
1232
- ),
1233
- /* @__PURE__ */ jsx(
1234
- CopyButton,
1235
- {
1236
- value: endpointMd,
1237
- title: "Copy endpoint as markdown (for AI)",
1238
- variant: "ghost",
1239
- size: "icon",
1240
- iconClassName: "h-3.5 w-3.5",
1241
- className: cn(
1242
- "shrink-0 h-6 w-6 text-muted-foreground/40 hover:text-foreground",
1243
- "opacity-0 group-hover/header:opacity-100 focus-visible:opacity-100"
1244
- )
1245
- }
1246
- )
1247
- ] }),
1248
- /* @__PURE__ */ jsxs(
1249
- Button,
1250
- {
1251
- size: "sm",
1252
- variant: isLoadedInPlayground ? "secondary" : "default",
1253
- onClick: onTryIt,
1254
- className: "shrink-0 h-8 text-xs gap-1.5 lg:flex hidden",
1255
- children: [
1256
- /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
1257
- isLoadedInPlayground ? "Loaded" : "Try it"
1258
- ]
1259
- }
1260
- )
1261
- ] }),
1262
- endpoint.description && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: endpoint.description }) }),
1263
- /* @__PURE__ */ jsxs(
1264
- Button,
1948
+ /* @__PURE__ */ jsx(
1949
+ EndpointHeader,
1950
+ {
1951
+ endpoint,
1952
+ anchor,
1953
+ isLoadedInPlayground,
1954
+ onTryIt,
1955
+ presentSections
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-5", children: [
1959
+ hasParameters && /* @__PURE__ */ jsx(
1960
+ Section,
1265
1961
  {
1266
- size: "sm",
1267
- variant: isLoadedInPlayground ? "secondary" : "default",
1268
- onClick: onTryIt,
1269
- className: "lg:hidden h-8 text-xs gap-1.5",
1270
- children: [
1271
- /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
1272
- isLoadedInPlayground ? "Loaded in playground" : "Try it"
1273
- ]
1962
+ id: "parameters",
1963
+ title: "Parameters",
1964
+ badge: pathParams.length + queryParams.length,
1965
+ children: /* @__PURE__ */ jsx(Parameters, { pathParams, queryParams })
1274
1966
  }
1275
- )
1276
- ] }),
1277
- (pathParams.length > 0 || queryParams.length > 0 || endpoint.requestBody || endpoint.responses?.length) && /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-8", children: [
1278
- pathParams.length > 0 && /* @__PURE__ */ jsx(ParamTable, { title: "Path parameters", params: pathParams }),
1279
- queryParams.length > 0 && /* @__PURE__ */ jsx(ParamTable, { title: "Query parameters", params: queryParams }),
1280
- endpoint.requestBody && /* @__PURE__ */ jsx(RequestBodySection, { body: endpoint.requestBody }),
1281
- endpoint.responses && endpoint.responses.length > 0 && /* @__PURE__ */ jsx(Subsection, { title: "Responses", children: /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: endpoint.responses.map((r) => /* @__PURE__ */ jsxs(
1282
- "div",
1967
+ ),
1968
+ endpoint.requestBody && /* @__PURE__ */ jsx(Section, { id: "requestBody", title: "Request body", children: /* @__PURE__ */ jsx(RequestBody, { body: endpoint.requestBody }) }),
1969
+ /* @__PURE__ */ jsx(Section, { id: "codeSamples", title: "Code samples", children: /* @__PURE__ */ jsx(CodeSamples, { endpoint }) }),
1970
+ hasResponses && /* @__PURE__ */ jsx(
1971
+ Section,
1283
1972
  {
1284
- className: "grid grid-cols-[72px_minmax(0,1fr)] items-center gap-3 px-3 py-2.5 bg-background",
1285
- children: [
1286
- /* @__PURE__ */ jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: r.code }) }),
1287
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words", children: r.description })
1288
- ]
1289
- },
1290
- r.code
1291
- )) }) })
1973
+ id: "responses",
1974
+ title: "Responses",
1975
+ badge: endpoint.responses.length,
1976
+ children: /* @__PURE__ */ jsx(Responses, { responses: endpoint.responses })
1977
+ }
1978
+ )
1292
1979
  ] })
1293
1980
  ]
1294
1981
  }
1295
- );
1982
+ ) });
1296
1983
  }
1297
1984
  __name(EndpointDoc, "EndpointDoc");
1298
- function RequestBodySection({ body }) {
1299
- const fields = useMemo(() => schemaToFields(body.schema), [body.schema]);
1300
- const typeLabel = body.schema ? body.type === "array" ? `array<${body.schema.items?.type ?? "object"}>` : body.type : body.type;
1301
- return /* @__PURE__ */ jsx(Subsection, { title: "Request body", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1302
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1303
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/80", children: typeLabel }),
1304
- body.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: body.description })
1305
- ] }),
1306
- fields.length > 0 && /* @__PURE__ */ jsx(FieldsTable, { fields })
1307
- ] }) });
1308
- }
1309
- __name(RequestBodySection, "RequestBodySection");
1310
- function FieldsTable({ fields }) {
1311
- return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: fields.map((f) => /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
1312
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1313
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: f.name }),
1314
- f.required && /* @__PURE__ */ jsx(
1315
- "span",
1316
- {
1317
- title: "Required",
1318
- className: "text-[9px] text-destructive font-bold leading-none",
1319
- children: "*"
1320
- }
1321
- ),
1322
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: f.type })
1323
- ] }),
1324
- f.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: f.description })
1325
- ] }, f.name)) });
1326
- }
1327
- __name(FieldsTable, "FieldsTable");
1328
- function Subsection({ title, children }) {
1329
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
1330
- /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground/70", children: title }),
1331
- children
1332
- ] });
1333
- }
1334
- __name(Subsection, "Subsection");
1335
- function ParamTable({
1336
- title,
1337
- params
1338
- }) {
1339
- return /* @__PURE__ */ jsx(Subsection, { title, children: /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: params.map((p) => /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
1340
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1341
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: p.name }),
1342
- p.required && /* @__PURE__ */ jsx(
1343
- "span",
1344
- {
1345
- title: "Required",
1346
- className: "text-[9px] text-destructive font-bold leading-none",
1347
- children: "*"
1348
- }
1349
- ),
1350
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: p.type })
1351
- ] }),
1352
- p.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: p.description }) : null
1353
- ] }, p.name)) }) });
1354
- }
1355
- __name(ParamTable, "ParamTable");
1356
- function StatusTag({ code }) {
1357
- const numeric = Number.parseInt(code, 10);
1358
- const cls = !Number.isFinite(numeric) ? "bg-muted text-muted-foreground border-border" : numeric >= 500 ? "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25" : numeric >= 400 ? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25" : numeric >= 300 ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25" : "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
1359
- return /* @__PURE__ */ jsx("span", { className: cn(
1360
- "inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums",
1361
- cls
1362
- ), children: code });
1363
- }
1364
- __name(StatusTag, "StatusTag");
1365
1985
  var readNavbarOffset = /* @__PURE__ */ __name(() => {
1366
1986
  if (typeof document === "undefined") return 0;
1367
1987
  const raw = getComputedStyle(document.documentElement).getPropertyValue("--navbar-height");
@@ -1407,10 +2027,11 @@ function buildSchemaSectionVM(entry, selectedVersion, loadedEndpoint) {
1407
2027
  };
1408
2028
  }
1409
2029
  __name(buildSchemaSectionVM, "buildSchemaSectionVM");
1410
- var DocsView = React6.forwardRef(/* @__PURE__ */ __name(function DocsView2(props, ref) {
2030
+ var DocsView = React12.forwardRef(/* @__PURE__ */ __name(function DocsView2(props, ref) {
1411
2031
  const scrollRef = useRef(null);
1412
2032
  const scrollTargetRef = useRef(null);
1413
2033
  const { onActiveChange } = props;
2034
+ useSectionHashRouter();
1414
2035
  const ensureScrollTarget = useCallback(() => {
1415
2036
  if (scrollTargetRef.current) return scrollTargetRef.current;
1416
2037
  if (!scrollRef.current) return null;
@@ -1431,7 +2052,7 @@ var DocsView = React6.forwardRef(/* @__PURE__ */ __name(function DocsView2(props
1431
2052
  },
1432
2053
  [ensureScrollTarget]
1433
2054
  );
1434
- React6.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
2055
+ React12.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
1435
2056
  useEffect(() => {
1436
2057
  const root = scrollRef.current;
1437
2058
  if (!root) return;
@@ -1535,7 +2156,7 @@ function SectionsBody({
1535
2156
  return /* @__PURE__ */ jsx("div", { ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12 space-y-16", children: sections.map((section) => /* @__PURE__ */ jsx(SchemaSectionView, { section, onTryEndpoint }, section.schemaId)) }) });
1536
2157
  }
1537
2158
  __name(SectionsBody, "SectionsBody");
1538
- var SchemaSectionView = React6.memo(/* @__PURE__ */ __name(function SchemaSectionView2({
2159
+ var SchemaSectionView = React12.memo(/* @__PURE__ */ __name(function SchemaSectionView2({
1539
2160
  section,
1540
2161
  onTryEndpoint
1541
2162
  }) {
@@ -1790,8 +2411,8 @@ function RawJsonField({
1790
2411
  value,
1791
2412
  onChange
1792
2413
  }) {
1793
- const [text, setText] = React6.useState(() => JSON.stringify(value ?? null, null, 2));
1794
- React6.useEffect(() => {
2414
+ const [text, setText] = React12.useState(() => JSON.stringify(value ?? null, null, 2));
2415
+ React12.useEffect(() => {
1795
2416
  setText(JSON.stringify(value ?? null, null, 2));
1796
2417
  }, [value]);
1797
2418
  return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
@@ -2140,8 +2761,8 @@ function RequestPanel() {
2140
2761
  __name(RequestPanel, "RequestPanel");
2141
2762
  function BodySection({ schema, bodyType, bodyDescription, value, onChange, isJsonValid }) {
2142
2763
  const hasSchema = !!schema;
2143
- const [mode, setMode] = React6.useState(hasSchema ? "form" : "json");
2144
- const parsed = React6.useMemo(() => {
2764
+ const [mode, setMode] = React12.useState(hasSchema ? "form" : "json");
2765
+ const parsed = React12.useMemo(() => {
2145
2766
  if (!value) return null;
2146
2767
  try {
2147
2768
  return JSON.parse(value);
@@ -2229,6 +2850,65 @@ function ModeButton({
2229
2850
  );
2230
2851
  }
2231
2852
  __name(ModeButton, "ModeButton");
2853
+ function looksLikeSpaShell(html) {
2854
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
2855
+ const bodyContent = (bodyMatch?.[1] ?? html).replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<!--[\s\S]*?-->/g, "").trim();
2856
+ if (bodyContent.length === 0) return true;
2857
+ const singleEmptyContainer = /^<(div|main|section)[^>]*>\s*<\/\1>$/i;
2858
+ if (singleEmptyContainer.test(bodyContent)) return true;
2859
+ return false;
2860
+ }
2861
+ __name(looksLikeSpaShell, "looksLikeSpaShell");
2862
+ function PreviewView({ html }) {
2863
+ const isSpaShell = useMemo(() => looksLikeSpaShell(html), [html]);
2864
+ if (!html) {
2865
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2866
+ }
2867
+ if (isSpaShell) {
2868
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 px-6 text-center gap-3 min-h-[400px]", children: [
2869
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center justify-center h-10 w-10 rounded-full bg-muted", children: /* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-muted-foreground" }) }),
2870
+ /* @__PURE__ */ jsxs("div", { className: "max-w-sm space-y-1.5", children: [
2871
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Looks like a single-page app shell" }),
2872
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: [
2873
+ "This page renders its content with JavaScript at runtime. Scripts are disabled in the sandbox, so Preview would show a blank page. Switch to ",
2874
+ /* @__PURE__ */ jsx("strong", { children: "Pretty" }),
2875
+ " or",
2876
+ " ",
2877
+ /* @__PURE__ */ jsx("strong", { children: "Raw" }),
2878
+ " to inspect the HTML source."
2879
+ ] })
2880
+ ] })
2881
+ ] });
2882
+ }
2883
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-[400px]", children: [
2884
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center gap-1.5 px-3 py-1.5 bg-muted/30 border-b text-[10px] text-muted-foreground/70", children: [
2885
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }),
2886
+ "Sandboxed preview \u2014 scripts, forms and popups are disabled"
2887
+ ] }),
2888
+ /* @__PURE__ */ jsx(
2889
+ "div",
2890
+ {
2891
+ className: "flex-1 min-h-[360px] p-2",
2892
+ style: {
2893
+ backgroundColor: "#fff",
2894
+ backgroundImage: "linear-gradient(45deg, #f3f4f6 25%, transparent 25%), linear-gradient(-45deg, #f3f4f6 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f3f4f6 75%), linear-gradient(-45deg, transparent 75%, #f3f4f6 75%)",
2895
+ backgroundSize: "16px 16px",
2896
+ backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px"
2897
+ },
2898
+ children: /* @__PURE__ */ jsx(
2899
+ "iframe",
2900
+ {
2901
+ title: "Response preview",
2902
+ srcDoc: html,
2903
+ sandbox: "",
2904
+ className: "w-full h-full min-h-[360px] bg-white border-0 rounded shadow-sm"
2905
+ }
2906
+ )
2907
+ }
2908
+ )
2909
+ ] });
2910
+ }
2911
+ __name(PreviewView, "PreviewView");
2232
2912
  var JSON_TREE_CONFIG = {
2233
2913
  maxAutoExpandDepth: 2,
2234
2914
  maxAutoExpandArrayItems: 10,
@@ -2241,26 +2921,198 @@ var JSON_TREE_CONFIG = {
2241
2921
  preserveKeyOrder: true,
2242
2922
  className: "border-0 rounded-none"
2243
2923
  };
2244
- function ResponsePanel() {
2245
- const { state } = usePlaygroundContext();
2246
- const { response, loading, selectedEndpoint } = state;
2247
- const { treeData, rawText } = useMemo(() => {
2248
- const d = response?.data;
2249
- if (d == null) return { treeData: null, rawText: "" };
2250
- if (typeof d === "string") {
2924
+ function PrettyView({ treeData, rawText, detected }) {
2925
+ if (detected.kind === "json" && treeData != null) {
2926
+ return /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG });
2927
+ }
2928
+ if (!rawText) {
2929
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2930
+ }
2931
+ return /* @__PURE__ */ jsx(
2932
+ PrettyCode_default,
2933
+ {
2934
+ data: rawText,
2935
+ language: detected.prism,
2936
+ variant: "plain",
2937
+ isCompact: true
2938
+ }
2939
+ );
2940
+ }
2941
+ __name(PrettyView, "PrettyView");
2942
+ function RawView({ rawText }) {
2943
+ if (!rawText) {
2944
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2945
+ }
2946
+ return /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText });
2947
+ }
2948
+ __name(RawView, "RawView");
2949
+ function StatusBar({ response, rawText, contentType }) {
2950
+ const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
2951
+ const duration = response.duration != null ? `${response.duration}ms` : "";
2952
+ const hasStatus = response.status != null;
2953
+ const hasCopy = Boolean(rawText);
2954
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
2955
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
2956
+ hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
2957
+ response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
2958
+ sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
2959
+ duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration }),
2960
+ contentType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono truncate", children: contentType })
2961
+ ] }),
2962
+ hasCopy && /* @__PURE__ */ jsx(
2963
+ CopyButton,
2964
+ {
2965
+ value: rawText,
2966
+ variant: "ghost",
2967
+ size: "sm",
2968
+ className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0",
2969
+ children: "Copy"
2970
+ }
2971
+ )
2972
+ ] });
2973
+ }
2974
+ __name(StatusBar, "StatusBar");
2975
+
2976
+ // src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts
2977
+ function normaliseContentType(raw) {
2978
+ if (!raw) return null;
2979
+ const semi = raw.indexOf(";");
2980
+ return (semi === -1 ? raw : raw.slice(0, semi)).trim().toLowerCase();
2981
+ }
2982
+ __name(normaliseContentType, "normaliseContentType");
2983
+ function readContentType(headers) {
2984
+ if (!headers) return null;
2985
+ if (typeof headers.get === "function") {
2986
+ return headers.get("content-type");
2987
+ }
2988
+ if (typeof headers === "object") {
2989
+ for (const [k, v] of Object.entries(headers)) {
2990
+ if (k.toLowerCase() === "content-type") {
2991
+ return typeof v === "string" ? v : null;
2992
+ }
2993
+ }
2994
+ }
2995
+ return null;
2996
+ }
2997
+ __name(readContentType, "readContentType");
2998
+ function kindFromContentType(mime) {
2999
+ if (!mime) return "text";
3000
+ if (mime === "application/json" || mime.endsWith("+json")) return "json";
3001
+ if (mime === "text/html" || mime === "application/xhtml+xml") return "html";
3002
+ if (mime === "application/xml" || mime === "text/xml" || mime.endsWith("+xml")) return "xml";
3003
+ if (mime === "text/css") return "css";
3004
+ if (mime === "application/javascript" || mime === "text/javascript" || mime === "application/x-javascript") return "javascript";
3005
+ return "text";
3006
+ }
3007
+ __name(kindFromContentType, "kindFromContentType");
3008
+ function kindFromBody(body) {
3009
+ const trimmed = body.trimStart();
3010
+ if (!trimmed) return null;
3011
+ if (trimmed.startsWith("<!DOCTYPE") || /^<html[\s>]/i.test(trimmed)) return "html";
3012
+ if (trimmed.startsWith("<?xml")) return "xml";
3013
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
3014
+ try {
3015
+ JSON.parse(trimmed);
3016
+ return "json";
3017
+ } catch {
3018
+ }
3019
+ }
3020
+ return null;
3021
+ }
3022
+ __name(kindFromBody, "kindFromBody");
3023
+ var PRISM_BY_KIND = {
3024
+ json: "json",
3025
+ // ``markup`` is Prism's HTML/XML grammar — there isn't a separate
3026
+ // ``html`` language. XML piggy-backs on the same tokeniser.
3027
+ html: "markup",
3028
+ xml: "markup",
3029
+ css: "css",
3030
+ javascript: "javascript",
3031
+ text: "markup"
3032
+ };
3033
+ function detectContent(headers, rawBody) {
3034
+ const contentType = normaliseContentType(readContentType(headers));
3035
+ const headerKind = kindFromContentType(contentType);
3036
+ const kind = headerKind === "text" ? kindFromBody(rawBody) ?? "text" : headerKind;
3037
+ return {
3038
+ kind,
3039
+ prism: PRISM_BY_KIND[kind],
3040
+ contentType
3041
+ };
3042
+ }
3043
+ __name(detectContent, "detectContent");
3044
+
3045
+ // src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts
3046
+ function useResponseView(data, headers) {
3047
+ return useMemo(() => {
3048
+ if (data == null) {
3049
+ return {
3050
+ treeData: null,
3051
+ rawText: "",
3052
+ detected: detectContent(headers, "")
3053
+ };
3054
+ }
3055
+ if (typeof data === "string") {
2251
3056
  try {
2252
- return { treeData: JSON.parse(d), rawText: d };
3057
+ return {
3058
+ treeData: JSON.parse(data),
3059
+ rawText: data,
3060
+ detected: detectContent(headers, data)
3061
+ };
2253
3062
  } catch {
2254
- return { treeData: null, rawText: d };
3063
+ return {
3064
+ treeData: null,
3065
+ rawText: data,
3066
+ detected: detectContent(headers, data)
3067
+ };
2255
3068
  }
2256
3069
  }
2257
- return { treeData: d, rawText: JSON.stringify(d, null, 2) };
2258
- }, [response?.data]);
2259
- const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
2260
- const duration = response?.duration != null ? `${response.duration}ms` : "";
2261
- const hasError = Boolean(response?.error);
2262
- const hasStatus = response?.status != null;
2263
- const hasCopy = Boolean(rawText);
3070
+ const stringified = (() => {
3071
+ try {
3072
+ return JSON.stringify(data, null, 2);
3073
+ } catch {
3074
+ return String(data);
3075
+ }
3076
+ })();
3077
+ return {
3078
+ treeData: data,
3079
+ rawText: stringified,
3080
+ detected: detectContent(headers, stringified)
3081
+ };
3082
+ }, [data, headers]);
3083
+ }
3084
+ __name(useResponseView, "useResponseView");
3085
+ var LABELS = {
3086
+ pretty: "Pretty",
3087
+ raw: "Raw",
3088
+ preview: "Preview"
3089
+ };
3090
+ function ViewTabs({ active, onChange, showPreview }) {
3091
+ const tabs = showPreview ? ["pretty", "raw", "preview"] : ["pretty", "raw"];
3092
+ return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-1.5 flex items-center gap-1", children: tabs.map((t) => /* @__PURE__ */ jsx(
3093
+ "button",
3094
+ {
3095
+ type: "button",
3096
+ onClick: () => onChange(t),
3097
+ className: cn(
3098
+ "h-6 px-2.5 rounded text-[11px] font-medium transition-colors",
3099
+ active === t ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
3100
+ ),
3101
+ children: LABELS[t]
3102
+ },
3103
+ t
3104
+ )) });
3105
+ }
3106
+ __name(ViewTabs, "ViewTabs");
3107
+ function ResponsePanel() {
3108
+ const { state } = usePlaygroundContext();
3109
+ const { response, loading, selectedEndpoint } = state;
3110
+ const { treeData, rawText, detected } = useResponseView(response?.data, response?.headers);
3111
+ const showPreview = detected.kind === "html";
3112
+ const [mode, setMode] = useState(showPreview ? "preview" : "pretty");
3113
+ useEffect(() => {
3114
+ setMode(showPreview ? "preview" : "pretty");
3115
+ }, [selectedEndpoint, response, showPreview]);
2264
3116
  if (loading) {
2265
3117
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
2266
3118
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
@@ -2269,6 +3121,8 @@ function ResponsePanel() {
2269
3121
  }
2270
3122
  if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
2271
3123
  if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
3124
+ const hasError = Boolean(response.error);
3125
+ const hasStatus = response.status != null;
2272
3126
  if (hasError && !hasStatus) {
2273
3127
  return /* @__PURE__ */ jsx(
2274
3128
  EmptyState,
@@ -2280,17 +3134,14 @@ function ResponsePanel() {
2280
3134
  );
2281
3135
  }
2282
3136
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2283
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
2284
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
2285
- hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
2286
- response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
2287
- sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
2288
- duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration })
2289
- ] }),
2290
- hasCopy && /* @__PURE__ */ jsx(CopyButton, { value: rawText, variant: "ghost", size: "sm", className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0", children: "Copy" })
2291
- ] }),
3137
+ /* @__PURE__ */ jsx(StatusBar, { response, rawText, contentType: detected.contentType }),
2292
3138
  hasError && /* @__PURE__ */ jsx("div", { className: "shrink-0 mx-4 mt-3 rounded border border-destructive/20 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: response.error }) }),
2293
- /* @__PURE__ */ jsx(ScrollArea, { children: treeData != null ? /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG }) : rawText ? /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText }) : /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" }) })
3139
+ /* @__PURE__ */ jsx(ViewTabs, { active: mode, onChange: setMode, showPreview }),
3140
+ /* @__PURE__ */ jsxs(ScrollArea, { children: [
3141
+ mode === "pretty" && /* @__PURE__ */ jsx(PrettyView, { treeData, rawText, detected }),
3142
+ mode === "raw" && /* @__PURE__ */ jsx(RawView, { rawText }),
3143
+ mode === "preview" && /* @__PURE__ */ jsx(PreviewView, { html: rawText })
3144
+ ] })
2294
3145
  ] });
2295
3146
  }
2296
3147
  __name(ResponsePanel, "ResponsePanel");
@@ -2364,12 +3215,14 @@ function SlideInPlayground({ open, onClose }) {
2364
3215
  showResponse ? "grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" : "grid-cols-1"
2365
3216
  ),
2366
3217
  children: [
2367
- /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(RequestPanel, {}) }),
3218
+ /* @__PURE__ */ jsxs(Panel, { children: [
3219
+ /* @__PURE__ */ jsx(RequestPanel, {}),
3220
+ ep && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
3221
+ ] }),
2368
3222
  showResponse && /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
2369
3223
  ]
2370
3224
  }
2371
- ),
2372
- ep && /* @__PURE__ */ jsx(SidePanel.Footer, { className: "px-4 py-3", children: /* @__PURE__ */ jsx(SendButton, {}) })
3225
+ )
2373
3226
  ] }) });
2374
3227
  }
2375
3228
  __name(SlideInPlayground, "SlideInPlayground");
@@ -2379,10 +3232,12 @@ function TryItSheet({ open, onOpenChange }) {
2379
3232
  return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl flex flex-col h-full p-0", children: [
2380
3233
  /* @__PURE__ */ jsx(ResponsiveSheetHeader, { className: "px-4 py-3 border-b shrink-0", children: /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-sm", children: "Playground" }) }),
2381
3234
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col divide-y", children: [
2382
- /* @__PURE__ */ jsx("div", { className: showResponse ? "flex-1 min-h-0 flex flex-col" : "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(RequestPanel, {}) }),
3235
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col", children: [
3236
+ /* @__PURE__ */ jsx(RequestPanel, {}),
3237
+ state.selectedEndpoint && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
3238
+ ] }),
2383
3239
  showResponse && /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
2384
- ] }),
2385
- state.selectedEndpoint && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background/95 backdrop-blur-sm", children: /* @__PURE__ */ jsx(SendButton, {}) })
3240
+ ] })
2386
3241
  ] }) });
2387
3242
  }
2388
3243
  __name(TryItSheet, "TryItSheet");
@@ -2598,5 +3453,5 @@ var DocsLayout = /* @__PURE__ */ __name(() => {
2598
3453
  }, "DocsLayout");
2599
3454
 
2600
3455
  export { DocsLayout };
2601
- //# sourceMappingURL=DocsLayout-TKJQ5W5E.mjs.map
2602
- //# sourceMappingURL=DocsLayout-TKJQ5W5E.mjs.map
3456
+ //# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map
3457
+ //# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map