@djangocfg/ui-tools 2.1.287 → 2.1.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
- package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
- package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
- package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
- package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
- package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
- package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
- package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
- package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
- package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
- package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
- package/dist/chunk-EFWOJPA6.mjs.map +1 -0
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -14
- package/src/components/markdown/MarkdownMessage.tsx +46 -0
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
- package/src/tools/OpenapiViewer/README.md +114 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
- package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
- package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
- package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
- package/src/tools/OpenapiViewer/types.ts +46 -1
- package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
- package/src/tools/OpenapiViewer/utils/index.ts +3 -0
- package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
- package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
- package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
- package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
- package/src/tools/PrettyCode/index.tsx +13 -0
- package/src/tools/PrettyCode/lazy.tsx +5 -0
- package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
- package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
- package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
- package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
- package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
- package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
- package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
- package/dist/chunk-IULI4XII.cjs.map +0 -1
- package/dist/chunk-VZGQC3NG.mjs.map +0 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code sample generation.
|
|
3
|
+
*
|
|
4
|
+
* In-house, browser-safe generator for a small catalogue of languages
|
|
5
|
+
* (curl / fetch / Node axios / Python requests / Go / PHP / Ruby /
|
|
6
|
+
* Java OkHttp). We used to wrap Kong's ``httpsnippet`` here but it is
|
|
7
|
+
* Node-only — the bundle referenced ``global`` / ``stream`` /
|
|
8
|
+
* ``string_decoder`` and crashed in Vite dev. Writing our own keeps the
|
|
9
|
+
* chunk small (a few kB vs ~1.5 MB) and avoids runtime polyfills.
|
|
10
|
+
*
|
|
11
|
+
* Adding more targets later is one function plus a row in the catalogue.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { HarRequest } from './operationToHar';
|
|
15
|
+
|
|
16
|
+
// Prism language ids used by ``prism-react-renderer``. ``bash``,
|
|
17
|
+
// ``ruby``, ``java`` and ``php`` aren't in the library's default
|
|
18
|
+
// bundle, but our ``registerPrismLanguages`` module pulls their
|
|
19
|
+
// grammars from ``prismjs`` at load time so they work as first-class
|
|
20
|
+
// languages here.
|
|
21
|
+
export const CODE_SAMPLE_TARGETS = [
|
|
22
|
+
{ id: 'curl', label: 'cURL', prism: 'bash' },
|
|
23
|
+
{ id: 'fetch', label: 'JavaScript', prism: 'javascript' },
|
|
24
|
+
{ id: 'axios', label: 'Node (axios)', prism: 'javascript' },
|
|
25
|
+
{ id: 'python', label: 'Python', prism: 'python' },
|
|
26
|
+
{ id: 'go', label: 'Go', prism: 'go' },
|
|
27
|
+
{ id: 'php', label: 'PHP', prism: 'php' },
|
|
28
|
+
{ id: 'ruby', label: 'Ruby', prism: 'ruby' },
|
|
29
|
+
{ id: 'java', label: 'Java', prism: 'java' },
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type CodeSampleTargetId = typeof CODE_SAMPLE_TARGETS[number]['id'];
|
|
33
|
+
|
|
34
|
+
// ─── Body literal helpers ─────────────────────────────────────────────────────
|
|
35
|
+
//
|
|
36
|
+
// Pre-formatted JSON bodies (``JSON.stringify(value, null, 2)``) carry
|
|
37
|
+
// real newlines that we want the snippet to preserve — a one-line
|
|
38
|
+
// escaped string like ``"{\n \"id\": 10,\n …}"`` reads as a wall of
|
|
39
|
+
// escape sequences. Each helper below returns a multi-line string
|
|
40
|
+
// literal in the target language's native syntax so the snippet reads
|
|
41
|
+
// like hand-written code.
|
|
42
|
+
|
|
43
|
+
/** Go raw string literal — uses backticks. Falls back to a double-
|
|
44
|
+
* quoted escaped literal when the body itself contains a backtick. */
|
|
45
|
+
function goRawString(s: string): string {
|
|
46
|
+
return s.includes('`') ? JSON.stringify(s) : `\`${s}\``;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Python triple-quoted string — uses ``"""``. Falls back to escaped
|
|
50
|
+
* form when the body already contains that sequence. */
|
|
51
|
+
function pythonTripleQuote(s: string): string {
|
|
52
|
+
return s.includes('"""') ? JSON.stringify(s) : `"""${s}"""`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Ruby squiggly heredoc — ``<<~JSON`` style strips common leading
|
|
56
|
+
* whitespace. Safe unless the body literally contains ``JSON`` on a
|
|
57
|
+
* line by itself, which is vanishingly rare. */
|
|
58
|
+
function rubyHeredoc(s: string): string {
|
|
59
|
+
return `<<~JSON\n${s}\nJSON`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** PHP heredoc — ``<<<JSON`` style. Same caveat as Ruby's. Trailing
|
|
63
|
+
* ``JSON;`` must sit at column 0 (enforced by this template). */
|
|
64
|
+
function phpHeredoc(s: string): string {
|
|
65
|
+
return `<<<JSON\n${s}\nJSON`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Java 15+ text block — ``"""``. Falls back to an escaped literal
|
|
69
|
+
* when the body contains that sequence. */
|
|
70
|
+
function javaTextBlock(s: string): string {
|
|
71
|
+
if (s.includes('"""')) return JSON.stringify(s);
|
|
72
|
+
// Text blocks need the opening ``"""`` on its own line — the
|
|
73
|
+
// parser strips the newline that immediately follows the delimiter.
|
|
74
|
+
return `"""\n${s}\n"""`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fullUrl(har: HarRequest): string {
|
|
78
|
+
if (!har.queryString.length) return har.url;
|
|
79
|
+
const qs = har.queryString
|
|
80
|
+
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
|
|
81
|
+
.join('&');
|
|
82
|
+
const sep = har.url.includes('?') ? '&' : '?';
|
|
83
|
+
return `${har.url}${sep}${qs}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function shellEscape(value: string): string {
|
|
87
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderCurl(har: HarRequest): string {
|
|
91
|
+
const lines: string[] = [`curl -X ${har.method} ${shellEscape(fullUrl(har))}`];
|
|
92
|
+
for (const h of har.headers) {
|
|
93
|
+
lines.push(` -H ${shellEscape(`${h.name}: ${h.value}`)}`);
|
|
94
|
+
}
|
|
95
|
+
if (har.postData?.text) {
|
|
96
|
+
lines.push(` -d ${shellEscape(har.postData.text)}`);
|
|
97
|
+
}
|
|
98
|
+
return lines.join(' \\\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function jsHeadersLiteral(har: HarRequest, indent: string): string {
|
|
102
|
+
if (!har.headers.length) return '{}';
|
|
103
|
+
const entries = har.headers
|
|
104
|
+
.map((h) => `${indent} ${JSON.stringify(h.name)}: ${JSON.stringify(h.value)}`)
|
|
105
|
+
.join(',\n');
|
|
106
|
+
return `{\n${entries},\n${indent}}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** JS template literal — backtick string that preserves newlines and
|
|
110
|
+
* escapes backticks / ``${`` sequences. Makes multi-line JSON body
|
|
111
|
+
* readable inside the fetch() options. */
|
|
112
|
+
function jsTemplateLiteral(s: string): string {
|
|
113
|
+
if (/[`$]/.test(s)) return JSON.stringify(s);
|
|
114
|
+
return `\`${s}\``;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderFetch(har: HarRequest): string {
|
|
118
|
+
const url = fullUrl(har);
|
|
119
|
+
const options: string[] = [` method: ${JSON.stringify(har.method)}`];
|
|
120
|
+
if (har.headers.length) {
|
|
121
|
+
options.push(` headers: ${jsHeadersLiteral(har, ' ')}`);
|
|
122
|
+
}
|
|
123
|
+
if (har.postData?.text) {
|
|
124
|
+
options.push(` body: ${jsTemplateLiteral(har.postData.text)}`);
|
|
125
|
+
}
|
|
126
|
+
return `const response = await fetch(${JSON.stringify(url)}, {\n${options.join(',\n')},\n});\nconst data = await response.json();`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderAxios(har: HarRequest): string {
|
|
130
|
+
const url = fullUrl(har);
|
|
131
|
+
const config: string[] = [
|
|
132
|
+
` method: ${JSON.stringify(har.method.toLowerCase())}`,
|
|
133
|
+
` url: ${JSON.stringify(url)}`,
|
|
134
|
+
];
|
|
135
|
+
if (har.headers.length) {
|
|
136
|
+
config.push(` headers: ${jsHeadersLiteral(har, ' ')}`);
|
|
137
|
+
}
|
|
138
|
+
if (har.postData?.text) {
|
|
139
|
+
// Axios auto-serializes objects; we pass the raw JSON string as data.
|
|
140
|
+
config.push(` data: ${har.postData.text}`);
|
|
141
|
+
}
|
|
142
|
+
return `import axios from 'axios';\n\nconst { data } = await axios({\n${config.join(',\n')},\n});`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function renderPython(har: HarRequest): string {
|
|
146
|
+
const lines: string[] = [`import requests`, ``];
|
|
147
|
+
lines.push(`url = ${JSON.stringify(fullUrl(har))}`);
|
|
148
|
+
if (har.headers.length) {
|
|
149
|
+
const headerEntries = har.headers
|
|
150
|
+
.map((h) => ` ${JSON.stringify(h.name)}: ${JSON.stringify(h.value)}`)
|
|
151
|
+
.join(',\n');
|
|
152
|
+
lines.push(`headers = {\n${headerEntries},\n}`);
|
|
153
|
+
}
|
|
154
|
+
if (har.postData?.text) {
|
|
155
|
+
// ``json=payload`` on ``requests`` expects a Python object, not
|
|
156
|
+
// a JSON string — so we leave the body as a raw dict literal
|
|
157
|
+
// (which Python parses identically to the JSON source for the
|
|
158
|
+
// shapes we generate). No wrapping helper needed here.
|
|
159
|
+
lines.push(`payload = ${har.postData.text}`);
|
|
160
|
+
}
|
|
161
|
+
const args = [`url`];
|
|
162
|
+
if (har.headers.length) args.push(`headers=headers`);
|
|
163
|
+
if (har.postData?.text) args.push(`json=payload`);
|
|
164
|
+
lines.push(``, `response = requests.${har.method.toLowerCase()}(${args.join(', ')})`);
|
|
165
|
+
lines.push(`data = response.json()`);
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function renderGo(har: HarRequest): string {
|
|
170
|
+
const url = fullUrl(har);
|
|
171
|
+
const lines: string[] = [
|
|
172
|
+
`package main`,
|
|
173
|
+
``,
|
|
174
|
+
`import (`,
|
|
175
|
+
` "fmt"`,
|
|
176
|
+
` "io"`,
|
|
177
|
+
];
|
|
178
|
+
if (har.postData?.text) lines.push(` "strings"`);
|
|
179
|
+
lines.push(` "net/http"`);
|
|
180
|
+
lines.push(`)`, ``, `func main() {`);
|
|
181
|
+
if (har.postData?.text) {
|
|
182
|
+
lines.push(` payload := strings.NewReader(${goRawString(har.postData.text)})`);
|
|
183
|
+
lines.push(` req, _ := http.NewRequest(${JSON.stringify(har.method)}, ${JSON.stringify(url)}, payload)`);
|
|
184
|
+
} else {
|
|
185
|
+
lines.push(` req, _ := http.NewRequest(${JSON.stringify(har.method)}, ${JSON.stringify(url)}, nil)`);
|
|
186
|
+
}
|
|
187
|
+
for (const h of har.headers) {
|
|
188
|
+
lines.push(` req.Header.Add(${JSON.stringify(h.name)}, ${JSON.stringify(h.value)})`);
|
|
189
|
+
}
|
|
190
|
+
lines.push(
|
|
191
|
+
``,
|
|
192
|
+
` res, _ := http.DefaultClient.Do(req)`,
|
|
193
|
+
` defer res.Body.Close()`,
|
|
194
|
+
` body, _ := io.ReadAll(res.Body)`,
|
|
195
|
+
` fmt.Println(string(body))`,
|
|
196
|
+
`}`,
|
|
197
|
+
);
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderPhp(har: HarRequest): string {
|
|
202
|
+
const lines: string[] = [`<?php`, ``, `$curl = curl_init();`, ``, `curl_setopt_array($curl, [`];
|
|
203
|
+
lines.push(` CURLOPT_URL => ${JSON.stringify(fullUrl(har))},`);
|
|
204
|
+
lines.push(` CURLOPT_RETURNTRANSFER => true,`);
|
|
205
|
+
lines.push(` CURLOPT_CUSTOMREQUEST => ${JSON.stringify(har.method)},`);
|
|
206
|
+
if (har.postData?.text) {
|
|
207
|
+
lines.push(` CURLOPT_POSTFIELDS => ${phpHeredoc(har.postData.text)},`);
|
|
208
|
+
}
|
|
209
|
+
if (har.headers.length) {
|
|
210
|
+
const headerList = har.headers
|
|
211
|
+
.map((h) => ` ${JSON.stringify(`${h.name}: ${h.value}`)}`)
|
|
212
|
+
.join(',\n');
|
|
213
|
+
lines.push(` CURLOPT_HTTPHEADER => [\n${headerList},\n ],`);
|
|
214
|
+
}
|
|
215
|
+
lines.push(`]);`, ``, `$response = curl_exec($curl);`, `curl_close($curl);`, `echo $response;`);
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function renderRuby(har: HarRequest): string {
|
|
220
|
+
const lines: string[] = [
|
|
221
|
+
`require 'net/http'`,
|
|
222
|
+
`require 'uri'`,
|
|
223
|
+
`require 'json'`,
|
|
224
|
+
``,
|
|
225
|
+
`uri = URI(${JSON.stringify(fullUrl(har))})`,
|
|
226
|
+
`http = Net::HTTP.new(uri.host, uri.port)`,
|
|
227
|
+
`http.use_ssl = uri.scheme == 'https'`,
|
|
228
|
+
``,
|
|
229
|
+
];
|
|
230
|
+
const methodClass = har.method.charAt(0) + har.method.slice(1).toLowerCase();
|
|
231
|
+
lines.push(`request = Net::HTTP::${methodClass}.new(uri)`);
|
|
232
|
+
for (const h of har.headers) {
|
|
233
|
+
lines.push(`request[${JSON.stringify(h.name)}] = ${JSON.stringify(h.value)}`);
|
|
234
|
+
}
|
|
235
|
+
if (har.postData?.text) {
|
|
236
|
+
lines.push(`request.body = ${rubyHeredoc(har.postData.text)}`);
|
|
237
|
+
}
|
|
238
|
+
lines.push(``, `response = http.request(request)`, `puts response.body`);
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderJava(har: HarRequest): string {
|
|
243
|
+
const lines: string[] = [
|
|
244
|
+
`OkHttpClient client = new OkHttpClient();`,
|
|
245
|
+
``,
|
|
246
|
+
];
|
|
247
|
+
if (har.postData?.text) {
|
|
248
|
+
lines.push(
|
|
249
|
+
`MediaType mediaType = MediaType.parse("application/json");`,
|
|
250
|
+
`RequestBody body = RequestBody.create(mediaType, ${javaTextBlock(har.postData.text)});`,
|
|
251
|
+
``,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
lines.push(`Request request = new Request.Builder()`);
|
|
255
|
+
lines.push(` .url(${JSON.stringify(fullUrl(har))})`);
|
|
256
|
+
if (har.postData?.text) {
|
|
257
|
+
lines.push(` .method(${JSON.stringify(har.method)}, body)`);
|
|
258
|
+
} else {
|
|
259
|
+
lines.push(` .method(${JSON.stringify(har.method)}, null)`);
|
|
260
|
+
}
|
|
261
|
+
for (const h of har.headers) {
|
|
262
|
+
lines.push(` .addHeader(${JSON.stringify(h.name)}, ${JSON.stringify(h.value)})`);
|
|
263
|
+
}
|
|
264
|
+
lines.push(` .build();`, ``, `Response response = client.newCall(request).execute();`);
|
|
265
|
+
return lines.join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const RENDERERS: Record<CodeSampleTargetId, (har: HarRequest) => string> = {
|
|
269
|
+
curl: renderCurl,
|
|
270
|
+
fetch: renderFetch,
|
|
271
|
+
axios: renderAxios,
|
|
272
|
+
python: renderPython,
|
|
273
|
+
go: renderGo,
|
|
274
|
+
php: renderPhp,
|
|
275
|
+
ruby: renderRuby,
|
|
276
|
+
java: renderJava,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export function renderSnippet(har: HarRequest, targetId: CodeSampleTargetId): string | null {
|
|
280
|
+
const renderer = RENDERERS[targetId];
|
|
281
|
+
if (!renderer) return null;
|
|
282
|
+
try {
|
|
283
|
+
return renderer(har);
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export * from './apiKeyManager';
|
|
8
|
+
export * from './codeSamples';
|
|
9
|
+
export * from './operationToHar';
|
|
8
10
|
export * from './versionManager';
|
|
9
11
|
export * from './formatters';
|
|
12
|
+
export * from './sampler';
|
|
10
13
|
export * from './schemaExport';
|
|
11
14
|
export * from './url';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI endpoint → HAR request.
|
|
3
|
+
*
|
|
4
|
+
* Our in-house code-sample renderers consume a HAR 1.2-shaped request.
|
|
5
|
+
* This module shapes an ``ApiEndpoint`` + user-provided parameter
|
|
6
|
+
* values into that form — path substitution, query-string assembly,
|
|
7
|
+
* header inference, body formatting.
|
|
8
|
+
*
|
|
9
|
+
* We keep the HAR intermediate (rather than a bespoke type) so the
|
|
10
|
+
* renderers match well-known HAR semantics and stay easy to extend.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ApiEndpoint } from '../types';
|
|
14
|
+
|
|
15
|
+
/** HAR 1.2 request subset that our renderers consume. We don't ship
|
|
16
|
+
* the full HAR type surface — just the fields that snippet generators
|
|
17
|
+
* actually read. */
|
|
18
|
+
export interface HarRequest {
|
|
19
|
+
method: string;
|
|
20
|
+
url: string;
|
|
21
|
+
httpVersion: string;
|
|
22
|
+
headers: Array<{ name: string; value: string }>;
|
|
23
|
+
queryString: Array<{ name: string; value: string }>;
|
|
24
|
+
cookies: Array<{ name: string; value: string }>;
|
|
25
|
+
headersSize: number;
|
|
26
|
+
bodySize: number;
|
|
27
|
+
postData?: {
|
|
28
|
+
mimeType: string;
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BuildHarInput {
|
|
34
|
+
endpoint: ApiEndpoint;
|
|
35
|
+
/** Raw body string — whatever the user typed in the playground or
|
|
36
|
+
* the pre-sampled example. Passed through verbatim; the caller
|
|
37
|
+
* decides the content shape. */
|
|
38
|
+
body?: string;
|
|
39
|
+
/** Path-param + query-param values, keyed by name. We look up each
|
|
40
|
+
* parameter on ``endpoint`` to decide whether it slots into the
|
|
41
|
+
* path or the query string. */
|
|
42
|
+
parameters?: Record<string, string>;
|
|
43
|
+
/** Extra headers to merge in (e.g. ``X-API-Key``, ``Authorization``).
|
|
44
|
+
* Later entries with the same name override earlier ones. */
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
/** Override the request URL's base. When absent we use
|
|
47
|
+
* ``endpoint.path`` as-is (extractor already prepended base URL). */
|
|
48
|
+
baseUrl?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Split a template path into path vs query. Substitutes ``{id}``-style
|
|
52
|
+
* placeholders using the provided parameter map; unused entries flow
|
|
53
|
+
* into the query string. */
|
|
54
|
+
function buildUrl(
|
|
55
|
+
endpoint: ApiEndpoint,
|
|
56
|
+
parameters: Record<string, string>,
|
|
57
|
+
baseUrl?: string,
|
|
58
|
+
): { url: string; queryString: HarRequest['queryString'] } {
|
|
59
|
+
const pathParamNames = new Set(
|
|
60
|
+
(endpoint.parameters ?? [])
|
|
61
|
+
.filter((p) => endpoint.path.includes(`{${p.name}}`))
|
|
62
|
+
.map((p) => p.name),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let path = endpoint.path;
|
|
66
|
+
for (const name of pathParamNames) {
|
|
67
|
+
const value = parameters[name] ?? `{${name}}`;
|
|
68
|
+
path = path.replaceAll(`{${name}}`, encodeURIComponent(value));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const queryString: HarRequest['queryString'] = [];
|
|
72
|
+
for (const param of endpoint.parameters ?? []) {
|
|
73
|
+
if (pathParamNames.has(param.name)) continue;
|
|
74
|
+
const value = parameters[param.name];
|
|
75
|
+
if (value === undefined || value === '') continue;
|
|
76
|
+
queryString.push({ name: param.name, value });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}${path.startsWith('/') ? path : `/${path}`}` : path;
|
|
80
|
+
return { url, queryString };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Build a HAR 1.2 request from an API endpoint + current form values. */
|
|
84
|
+
export function buildHarRequest(input: BuildHarInput): HarRequest {
|
|
85
|
+
const { endpoint, body, parameters = {}, headers = {}, baseUrl } = input;
|
|
86
|
+
const { url, queryString } = buildUrl(endpoint, parameters, baseUrl);
|
|
87
|
+
|
|
88
|
+
const hasBody = Boolean(body && body.trim().length > 0);
|
|
89
|
+
const bodyMime = hasBody
|
|
90
|
+
? 'application/json'
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
// Merge headers. Caller wins, but we default Content-Type for bodies
|
|
94
|
+
// and Accept for JSON endpoints — snippet consumers expect these.
|
|
95
|
+
const mergedHeaders: Record<string, string> = {};
|
|
96
|
+
if (hasBody && bodyMime) mergedHeaders['Content-Type'] = bodyMime;
|
|
97
|
+
mergedHeaders['Accept'] = 'application/json';
|
|
98
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
99
|
+
if (v === undefined || v === '') continue;
|
|
100
|
+
mergedHeaders[k] = v;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const har: HarRequest = {
|
|
104
|
+
method: endpoint.method.toUpperCase(),
|
|
105
|
+
url,
|
|
106
|
+
httpVersion: 'HTTP/1.1',
|
|
107
|
+
headers: Object.entries(mergedHeaders).map(([name, value]) => ({ name, value })),
|
|
108
|
+
queryString,
|
|
109
|
+
cookies: [],
|
|
110
|
+
headersSize: -1,
|
|
111
|
+
bodySize: hasBody ? new TextEncoder().encode(body!).length : 0,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (hasBody && bodyMime) {
|
|
115
|
+
har.postData = { mimeType: bodyMime, text: body! };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return har;
|
|
119
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema → example JSON.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over ``openapi-sampler`` (by Redocly — same library Redoc
|
|
5
|
+
* uses internally). Picks deterministic, realistic values from a
|
|
6
|
+
* dereferenced schema: honours ``const`` / ``examples`` / ``enum`` /
|
|
7
|
+
* ``default``, unpacks ``allOf`` / ``oneOf`` / ``anyOf``, respects
|
|
8
|
+
* string ``format`` (email/uuid/date-time/…).
|
|
9
|
+
*
|
|
10
|
+
* Replaces the previous hand-rolled ``exampleFromSchema`` — stricter on
|
|
11
|
+
* spec semantics and cheaper to maintain.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import consola from 'consola';
|
|
15
|
+
import { sample as openapiSample } from 'openapi-sampler';
|
|
16
|
+
|
|
17
|
+
type JsonSchemaLike = Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
export interface SampleOptions {
|
|
20
|
+
/** Skip properties marked ``readOnly`` — useful when the body will
|
|
21
|
+
* be sent in a request (request body editor). Default ``false``. */
|
|
22
|
+
skipReadOnly?: boolean;
|
|
23
|
+
/** Skip properties marked ``writeOnly`` — useful when rendering a
|
|
24
|
+
* response body (passwords etc. must not leak). Default ``false``. */
|
|
25
|
+
skipWriteOnly?: boolean;
|
|
26
|
+
/** Skip non-required properties. Rarely useful for a viewer — we
|
|
27
|
+
* want to show the full shape. Default ``false``. */
|
|
28
|
+
skipNonRequired?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Sample a JSON schema into a plain value. Returns ``null`` on failure
|
|
32
|
+
* (malformed schema, circular refs the sampler can't unwind) so the
|
|
33
|
+
* caller can show "no example" instead of crashing the page.
|
|
34
|
+
*
|
|
35
|
+
* ``spec`` must be the root OpenAPI document whenever ``schema`` may
|
|
36
|
+
* contain ``$ref`` nodes — ``openapi-sampler`` walks the tree and
|
|
37
|
+
* resolves refs against it. Our own ``dereferenceSchema`` only inlines
|
|
38
|
+
* refs up to a depth limit; anything deeper comes through here as a
|
|
39
|
+
* live ``$ref`` and the sampler throws if ``spec`` is missing. */
|
|
40
|
+
export function sampleSchema(
|
|
41
|
+
schema: JsonSchemaLike | undefined,
|
|
42
|
+
options: SampleOptions = {},
|
|
43
|
+
spec?: unknown,
|
|
44
|
+
): unknown {
|
|
45
|
+
if (!schema) return null;
|
|
46
|
+
try {
|
|
47
|
+
return openapiSample(schema as never, options, spec as never);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// Sampler failures used to be silent, which meant "no example"
|
|
50
|
+
// in the UI looked like a missing spec entry. Log so we can see
|
|
51
|
+
// the real cause in dev (circular refs, missing type, etc.).
|
|
52
|
+
consola.warn('[OpenapiViewer] sampleSchema failed:', err, { schema });
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Same as ``sampleSchema`` but returns a pre-stringified JSON payload
|
|
58
|
+
* ready to drop into a textarea / code block. Returns ``undefined``
|
|
59
|
+
* when sampling fails so the UI can conditionally hide the example. */
|
|
60
|
+
export function sampleSchemaJson(
|
|
61
|
+
schema: JsonSchemaLike | undefined,
|
|
62
|
+
options: SampleOptions = {},
|
|
63
|
+
spec?: unknown,
|
|
64
|
+
): string | undefined {
|
|
65
|
+
const value = sampleSchema(schema, options, spec);
|
|
66
|
+
if (value === null || value === undefined) return undefined;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(value, null, 2);
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the nearest ancestor that actually scrolls vertically, or
|
|
3
|
+
* ``window`` when none exists. Handles the common embed scenarios:
|
|
4
|
+
*
|
|
5
|
+
* - Standalone page: nothing in the chain scrolls → caller scrolls
|
|
6
|
+
* ``window`` and listens to ``window.scroll``.
|
|
7
|
+
* - Dev playground / modal shell: an intermediate ``overflow-auto``
|
|
8
|
+
* container scrolls → caller scrolls that element and listens to
|
|
9
|
+
* *its* ``scroll`` events (which do NOT bubble to window).
|
|
10
|
+
*
|
|
11
|
+
* A "scrollable" ancestor is one whose computed ``overflow-y`` is
|
|
12
|
+
* ``auto`` or ``scroll`` AND whose content actually overflows. We bail
|
|
13
|
+
* before ``document.body`` — ``documentElement`` is represented by
|
|
14
|
+
* ``window`` in the caller's hot path, so returning the body itself
|
|
15
|
+
* would double-count the scroll surface.
|
|
16
|
+
*/
|
|
17
|
+
export type ScrollTarget = HTMLElement | Window;
|
|
18
|
+
|
|
19
|
+
export function getScrollParent(el: HTMLElement | null): ScrollTarget {
|
|
20
|
+
if (typeof window === 'undefined') return (null as unknown) as Window;
|
|
21
|
+
if (!el) return window;
|
|
22
|
+
|
|
23
|
+
let cur: HTMLElement | null = el.parentElement;
|
|
24
|
+
while (cur && cur !== document.body && cur !== document.documentElement) {
|
|
25
|
+
const style = getComputedStyle(cur);
|
|
26
|
+
const overflowY = style.overflowY;
|
|
27
|
+
const canScroll =
|
|
28
|
+
(overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&
|
|
29
|
+
cur.scrollHeight > cur.clientHeight;
|
|
30
|
+
if (canScroll) return cur;
|
|
31
|
+
cur = cur.parentElement;
|
|
32
|
+
}
|
|
33
|
+
return window;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Top-relative scroll position of the target. */
|
|
37
|
+
export function getScrollTop(target: ScrollTarget): number {
|
|
38
|
+
return target === window ? window.scrollY : (target as HTMLElement).scrollTop;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Visible viewport height of the target. */
|
|
42
|
+
export function getViewportHeight(target: ScrollTarget): number {
|
|
43
|
+
return target === window ? window.innerHeight : (target as HTMLElement).clientHeight;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Y coordinate of the target's top edge, in viewport space. Used to
|
|
47
|
+
* translate ``getBoundingClientRect().top`` into target-relative
|
|
48
|
+
* coordinates. For ``window`` this is always ``0``. */
|
|
49
|
+
export function getTargetTop(target: ScrollTarget): number {
|
|
50
|
+
return target === window ? 0 : (target as HTMLElement).getBoundingClientRect().top;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Scroll the target so that the given absolute Y lands at its top.
|
|
54
|
+
*
|
|
55
|
+
* For ``window`` we ask the browser to animate smoothly — every engine
|
|
56
|
+
* honours that path. Nested ``overflow-auto`` elements are a different
|
|
57
|
+
* story: some layouts (e.g. dev-playground shells with flex parents)
|
|
58
|
+
* silently drop the animation, leaving the user stuck. Direct
|
|
59
|
+
* ``scrollTop`` writes always work, so we use them there — loss of
|
|
60
|
+
* animation beats broken navigation. Consumers who want animated
|
|
61
|
+
* scrolling inside a custom shell can wrap this function themselves. */
|
|
62
|
+
export function scrollTargetTo(target: ScrollTarget, top: number) {
|
|
63
|
+
if (target === window) {
|
|
64
|
+
window.scrollTo({ top, behavior: 'smooth' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
(target as HTMLElement).scrollTop = top;
|
|
68
|
+
}
|
|
@@ -8,6 +8,13 @@ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
|
|
|
8
8
|
import { FloatingToolbar } from '../../components/FloatingToolbar';
|
|
9
9
|
import { CopyAction } from '../../components/FloatingToolbar/actions';
|
|
10
10
|
|
|
11
|
+
// Load extra Prism grammars (``bash``, ``ruby``, ``java``, ``php``)
|
|
12
|
+
// that aren't in ``prism-react-renderer``'s default bundle. The hook
|
|
13
|
+
// below subscribes to its ready state and re-renders this component
|
|
14
|
+
// once loading completes, so the first tab click gets highlighted
|
|
15
|
+
// correctly even if it happened before the dynamic import resolved.
|
|
16
|
+
import { useEnsurePrismLanguages } from './registerPrismLanguages';
|
|
17
|
+
|
|
11
18
|
interface PrettyCodeProps {
|
|
12
19
|
data: string | object;
|
|
13
20
|
language: Language;
|
|
@@ -23,13 +30,24 @@ interface PrettyCodeProps {
|
|
|
23
30
|
* Set e.g. ``50`` to cap short snippets inline and scroll long ones.
|
|
24
31
|
*/
|
|
25
32
|
maxLines?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Visual variant. ``"card"`` (default) ships full chrome (border,
|
|
35
|
+
* background, hover toolbar, optional internal scroll). ``"plain"``
|
|
36
|
+
* is chrome-less — use when embedding inside another scroll
|
|
37
|
+
* container so the surface manages its own chrome and scroll. */
|
|
38
|
+
variant?: 'card' | 'plain';
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
const PrettyCode = ({ data, language, className, mode, inline = false, customBg, isCompact = false, scrollIsolation, maxLines }: PrettyCodeProps) => {
|
|
41
|
+
const PrettyCode = ({ data, language, className, mode, inline = false, customBg, isCompact = false, scrollIsolation, maxLines, variant = 'card' }: PrettyCodeProps) => {
|
|
29
42
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
30
43
|
const t = useAppT();
|
|
31
44
|
const detectedTheme = useResolvedTheme();
|
|
32
45
|
|
|
46
|
+
// Subscribe to the extra-grammars ready state. When ``bash`` /
|
|
47
|
+
// ``ruby`` / ``java`` / ``php`` finish loading, this hook triggers a
|
|
48
|
+
// re-render so the code block picks up the new grammar.
|
|
49
|
+
useEnsurePrismLanguages();
|
|
50
|
+
|
|
33
51
|
const labels = useMemo(() => ({
|
|
34
52
|
copyCode: t('tools.code.copyCode'),
|
|
35
53
|
noContent: t('tools.code.noContent'),
|
|
@@ -110,6 +128,16 @@ const PrettyCode = ({ data, language, className, mode, inline = false, customBg,
|
|
|
110
128
|
return 'Text';
|
|
111
129
|
case 'mermaid':
|
|
112
130
|
return 'Mermaid';
|
|
131
|
+
case 'ruby':
|
|
132
|
+
case 'rb':
|
|
133
|
+
return 'Ruby';
|
|
134
|
+
case 'java':
|
|
135
|
+
return 'Java';
|
|
136
|
+
case 'php':
|
|
137
|
+
return 'PHP';
|
|
138
|
+
case 'go':
|
|
139
|
+
case 'golang':
|
|
140
|
+
return 'Go';
|
|
113
141
|
default:
|
|
114
142
|
return lang.charAt(0).toUpperCase() + lang.slice(1);
|
|
115
143
|
}
|
|
@@ -140,7 +168,18 @@ const PrettyCode = ({ data, language, className, mode, inline = false, customBg,
|
|
|
140
168
|
return 'markup';
|
|
141
169
|
case 'bash':
|
|
142
170
|
case 'shell':
|
|
171
|
+
case 'sh':
|
|
143
172
|
return 'bash';
|
|
173
|
+
case 'ruby':
|
|
174
|
+
case 'rb':
|
|
175
|
+
return 'ruby';
|
|
176
|
+
case 'java':
|
|
177
|
+
return 'java';
|
|
178
|
+
case 'php':
|
|
179
|
+
return 'php';
|
|
180
|
+
case 'go':
|
|
181
|
+
case 'golang':
|
|
182
|
+
return 'go';
|
|
144
183
|
case 'sql':
|
|
145
184
|
return 'sql';
|
|
146
185
|
case 'yaml':
|
|
@@ -160,6 +199,54 @@ const PrettyCode = ({ data, language, className, mode, inline = false, customBg,
|
|
|
160
199
|
|
|
161
200
|
const displayLanguage = getLanguageDisplayName(language);
|
|
162
201
|
|
|
202
|
+
// Plain variant — chrome-less render for embedding inside other
|
|
203
|
+
// scroll containers. No border, no background, no hover toolbar, no
|
|
204
|
+
// internal scroll. The caller's ScrollArea/panel owns the chrome
|
|
205
|
+
// and scroll responsibilities.
|
|
206
|
+
if (variant === 'plain') {
|
|
207
|
+
return (
|
|
208
|
+
<Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
|
|
209
|
+
{({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
|
|
210
|
+
const { backgroundColor: _bg, ...restStyle } = style;
|
|
211
|
+
return (
|
|
212
|
+
<pre
|
|
213
|
+
className={`${prismClassName} ${className || ''}`}
|
|
214
|
+
style={{
|
|
215
|
+
...restStyle,
|
|
216
|
+
background: 'transparent',
|
|
217
|
+
margin: 0,
|
|
218
|
+
padding: isCompact ? '0.75rem 1rem' : '1rem',
|
|
219
|
+
fontSize,
|
|
220
|
+
lineHeight: lineHeightRatio,
|
|
221
|
+
fontFamily: 'monospace',
|
|
222
|
+
// ``break-all`` (not ``break-word``) so long unbroken
|
|
223
|
+
// strings without whitespace — typical of escaped
|
|
224
|
+
// JSON bodies in generated code samples — wrap at any
|
|
225
|
+
// character rather than overflowing the container.
|
|
226
|
+
whiteSpace: 'pre-wrap',
|
|
227
|
+
wordBreak: 'break-all',
|
|
228
|
+
overflowWrap: 'anywhere',
|
|
229
|
+
// Hard cap on width — parents (grid cells, panels)
|
|
230
|
+
// should constrain us but some callers render inside
|
|
231
|
+
// ``flex: 1 1 auto`` which lets the pre grow past
|
|
232
|
+
// intended bounds. ``max-width: 100%`` pins to parent.
|
|
233
|
+
maxWidth: '100%',
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
{tokens.map((line, i) => (
|
|
237
|
+
<div key={i} {...getLineProps({ line })}>
|
|
238
|
+
{line.map((token, key) => (
|
|
239
|
+
<span key={key} {...getTokenProps({ token })} />
|
|
240
|
+
))}
|
|
241
|
+
</div>
|
|
242
|
+
))}
|
|
243
|
+
</pre>
|
|
244
|
+
);
|
|
245
|
+
}}
|
|
246
|
+
</Highlight>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
163
250
|
if (inline) {
|
|
164
251
|
const inlineBgClass = customBg || (isDarkMode ? 'bg-zinc-800' : 'bg-zinc-100');
|
|
165
252
|
return (
|