Skip to content

Commit 65e874b

Browse files
Add support to x-codeSamples in v3
1 parent 1207af7 commit 65e874b

File tree

8 files changed

+214
-1413
lines changed

8 files changed

+214
-1413
lines changed

demo/examples/petstore.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ paths:
147147
Console.WriteLine(response.getRawResponse());
148148
}
149149
- lang: PHP
150+
label: Custom
150151
source: |
151152
$form = new \PetStore\Entities\Pet();
152153
$form->setPetType("Dog");
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* ============================================================================
2+
* Copyright (c) Palo Alto Networks
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
* ========================================================================== */
7+
8+
// https://github.com/github-linguist/linguist/blob/master/lib/linguist/popular.yml
9+
export type CodeSampleLanguage =
10+
| "C"
11+
| "C#"
12+
| "C++"
13+
| "CoffeeScript"
14+
| "CSS"
15+
| "Dart"
16+
| "DM"
17+
| "Elixir"
18+
| "Go"
19+
| "Groovy"
20+
| "HTML"
21+
| "Java"
22+
| "JavaScript"
23+
| "Kotlin"
24+
| "Objective-C"
25+
| "Perl"
26+
| "PHP"
27+
| "PowerShell"
28+
| "Python"
29+
| "Ruby"
30+
| "Rust"
31+
| "Scala"
32+
| "Shell"
33+
| "Swift"
34+
| "TypeScript";
35+
36+
export interface Language {
37+
highlight: string;
38+
language: string;
39+
codeSampleLanguage: CodeSampleLanguage;
40+
logoClass: string;
41+
variant: string;
42+
variants: string[];
43+
options?: { [key: string]: boolean };
44+
sample?: string;
45+
samples?: string[];
46+
samplesSources?: string[];
47+
samplesLabels?: string[];
48+
}
49+
50+
// https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples
51+
export interface CodeSample {
52+
source: string;
53+
lang: CodeSampleLanguage;
54+
label?: string;
55+
}

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,14 @@ import CodeTabs from "@theme/ApiExplorer/CodeTabs";
1616
import { useTypedSelector } from "@theme/ApiItem/hooks";
1717
import merge from "lodash/merge";
1818

19-
export interface Language {
20-
highlight: string;
21-
language: string;
22-
logoClass: string;
23-
variant: string;
24-
variants: string[];
25-
options: { [key: string]: boolean };
26-
source?: string;
27-
}
19+
import { CodeSample, Language } from "./code-snippets-types";
20+
import { mergeCodeSampleLanguage } from "./languages";
2821

2922
export const languageSet: Language[] = [
3023
{
3124
highlight: "bash",
3225
language: "curl",
26+
codeSampleLanguage: "Shell",
3327
logoClass: "bash",
3428
options: {
3529
longFormat: false,
@@ -42,6 +36,7 @@ export const languageSet: Language[] = [
4236
{
4337
highlight: "python",
4438
language: "python",
39+
codeSampleLanguage: "Python",
4540
logoClass: "python",
4641
options: {
4742
followRedirect: true,
@@ -53,6 +48,7 @@ export const languageSet: Language[] = [
5348
{
5449
highlight: "go",
5550
language: "go",
51+
codeSampleLanguage: "Go",
5652
logoClass: "go",
5753
options: {
5854
followRedirect: true,
@@ -64,18 +60,20 @@ export const languageSet: Language[] = [
6460
{
6561
highlight: "javascript",
6662
language: "nodejs",
63+
codeSampleLanguage: "JavaScript",
6764
logoClass: "nodejs",
6865
options: {
6966
ES6_enabled: true,
7067
followRedirect: true,
7168
trimRequestBody: true,
7269
},
7370
variant: "axios",
74-
variants: ["axios", "native", "request", "unirest"],
71+
variants: ["axios", "native"],
7572
},
7673
{
7774
highlight: "ruby",
7875
language: "ruby",
76+
codeSampleLanguage: "Ruby",
7977
logoClass: "ruby",
8078
options: {
8179
followRedirect: true,
@@ -87,6 +85,7 @@ export const languageSet: Language[] = [
8785
{
8886
highlight: "csharp",
8987
language: "csharp",
88+
codeSampleLanguage: "C#",
9089
logoClass: "csharp",
9190
options: {
9291
followRedirect: true,
@@ -98,6 +97,7 @@ export const languageSet: Language[] = [
9897
{
9998
highlight: "php",
10099
language: "php",
100+
codeSampleLanguage: "PHP",
101101
logoClass: "php",
102102
options: {
103103
followRedirect: true,
@@ -109,6 +109,7 @@ export const languageSet: Language[] = [
109109
{
110110
highlight: "java",
111111
language: "java",
112+
codeSampleLanguage: "Java",
112113
logoClass: "java",
113114
options: {
114115
followRedirect: true,
@@ -120,6 +121,7 @@ export const languageSet: Language[] = [
120121
{
121122
highlight: "powershell",
122123
language: "powershell",
124+
codeSampleLanguage: "PowerShell",
123125
logoClass: "powershell",
124126
options: {
125127
followRedirect: true,
@@ -132,10 +134,10 @@ export const languageSet: Language[] = [
132134

133135
export interface Props {
134136
postman: sdk.Request;
135-
codeSamples: any; // TODO: Type this...
137+
codeSamples: CodeSample[];
136138
}
137139

138-
function CodeTab({ children, hidden, className, onClick }: any): JSX.Element {
140+
function CodeTab({ children, hidden, className }: any): JSX.Element {
139141
return (
140142
<div role="tabpanel" className={className} {...{ hidden }}>
141143
{children}
@@ -165,7 +167,6 @@ function CodeSnippets({ postman, codeSamples }: Props) {
165167
const langs = [
166168
...((siteConfig?.themeConfig?.languageTabs as Language[] | undefined) ??
167169
languageSet),
168-
...codeSamples,
169170
];
170171

171172
// Filter languageSet by user-defined langs
@@ -176,14 +177,18 @@ function CodeSnippets({ postman, codeSamples }: Props) {
176177
});
177178

178179
// Merge user-defined langs into languageSet
179-
const mergedLangs = merge(filteredLanguageSet, langs);
180+
const mergedLangs = mergeCodeSampleLanguage(
181+
merge(filteredLanguageSet, langs),
182+
codeSamples
183+
);
180184

181185
// Read defaultLang from localStorage
182186
const defaultLang: Language[] = mergedLangs.filter(
183187
(lang) =>
184188
lang.language === localStorage.getItem("docusaurus.tab.code-samples")
185189
);
186-
const [selectedVariant, setSelectedVariant] = useState();
190+
const [selectedVariant, setSelectedVariant] = useState<string | undefined>();
191+
const [selectedSample, setSelectedSample] = useState<string | undefined>();
187192
const [language, setLanguage] = useState(() => {
188193
// Return first index if only 1 user-defined language exists
189194
if (mergedLangs.length === 1) {
@@ -192,9 +197,23 @@ function CodeSnippets({ postman, codeSamples }: Props) {
192197
// Fall back to language in localStorage or first user-defined language
193198
return defaultLang[0] ?? mergedLangs[0];
194199
});
195-
const [codeText, setCodeText] = useState("");
200+
const [codeText, setCodeText] = useState<string>("");
201+
const [codeSampleCodeText, setCodeSampleCodeText] = useState<string>("");
196202

197203
useEffect(() => {
204+
// initial active language is custom code sample
205+
if (
206+
language &&
207+
language.sample &&
208+
language.samples &&
209+
language.samplesSources
210+
) {
211+
const sampleIndex = language.samples.findIndex(
212+
(smp) => smp === language.sample
213+
);
214+
setCodeSampleCodeText(language.samplesSources[sampleIndex]);
215+
}
216+
198217
if (language && !!language.options) {
199218
const postmanRequest = buildPostmanRequest(postman, {
200219
queryParams,
@@ -219,8 +238,6 @@ function CodeSnippets({ postman, codeSamples }: Props) {
219238
setCodeText(snippet);
220239
}
221240
);
222-
} else if (language && !!language.source) {
223-
setCodeText(language.source);
224241
} else if (language && !language.options) {
225242
const langSource = mergedLangs.filter(
226243
(lang) => lang.language === language.language
@@ -271,8 +288,8 @@ function CodeSnippets({ postman, codeSamples }: Props) {
271288
auth,
272289
mergedLangs,
273290
]);
274-
275-
useEffect(() => {
291+
// no dependencies was intentionlly set for this particular hook. it's safe as long as if conditions are set
292+
useEffect(function onSelectedVariantUpdate() {
276293
if (selectedVariant && selectedVariant !== language.variant) {
277294
const postmanRequest = buildPostmanRequest(postman, {
278295
queryParams,
@@ -300,6 +317,22 @@ function CodeSnippets({ postman, codeSamples }: Props) {
300317
}
301318
});
302319

320+
// no dependencies was intentionlly set for this particular hook. it's safe as long as if conditions are set
321+
// eslint-disable-next-line react-hooks/exhaustive-deps
322+
useEffect(function onSelectedSampleUpdate() {
323+
if (
324+
language.samples &&
325+
language.samplesSources &&
326+
selectedSample &&
327+
selectedSample !== language.sample
328+
) {
329+
const sampleIndex = language.samples.findIndex(
330+
(smp) => smp === selectedSample
331+
);
332+
setCodeSampleCodeText(language.samplesSources[sampleIndex]);
333+
}
334+
});
335+
303336
if (language === undefined) {
304337
return null;
305338
}
@@ -324,6 +357,46 @@ function CodeSnippets({ postman, codeSamples }: Props) {
324357
className: `openapi-tabs__code-item--${lang.logoClass}`,
325358
}}
326359
>
360+
{lang.samples && (
361+
<CodeTabs
362+
className="openapi-tabs__code-container-inner"
363+
action={{
364+
setLanguage: setLanguage,
365+
setSelectedSample: setSelectedSample,
366+
}}
367+
includeSample={true}
368+
currentLanguage={lang.language}
369+
defaultValue={selectedSample}
370+
lazy
371+
>
372+
{lang.samples.map((sample, index) => {
373+
return (
374+
<CodeTab
375+
value={sample}
376+
label={
377+
lang.samplesLabels
378+
? lang.samplesLabels[index]
379+
: sample
380+
}
381+
key={`${lang.language}-${lang.sample}`}
382+
attributes={{
383+
className: `openapi-tabs__code-item--sample`,
384+
}}
385+
>
386+
{/* @ts-ignore */}
387+
<ApiCodeBlock
388+
language={lang.highlight}
389+
className="openapi-explorer__code-block"
390+
showLineNumbers={true}
391+
>
392+
{codeSampleCodeText}
393+
</ApiCodeBlock>
394+
</CodeTab>
395+
);
396+
})}
397+
</CodeTabs>
398+
)}
399+
327400
<CodeTabs
328401
className="openapi-tabs__code-container-inner"
329402
action={{
@@ -335,7 +408,7 @@ function CodeSnippets({ postman, codeSamples }: Props) {
335408
defaultValue={selectedVariant}
336409
lazy
337410
>
338-
{lang.variants.map((variant) => {
411+
{lang.variants.map((variant, index) => {
339412
return (
340413
<CodeTab
341414
value={variant.toLowerCase()}

0 commit comments

Comments
 (0)