docusaurus-theme-openapi-docs 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/lib/markdown/schema.js +38 -15
  2. package/lib/markdown/schema.test.d.ts +1 -0
  3. package/lib/markdown/schema.test.js +86 -0
  4. package/lib/theme/ApiExplorer/ApiCodeBlock/Container/index.js +4 -2
  5. package/lib/theme/ApiExplorer/ApiCodeBlock/Content/String.js +9 -6
  6. package/lib/theme/ApiExplorer/ApiCodeBlock/Line/index.d.ts +1 -1
  7. package/lib/theme/ApiExplorer/ApiCodeBlock/index.d.ts +1 -1
  8. package/lib/theme/ApiExplorer/Authorization/index.js +9 -10
  9. package/lib/theme/ApiExplorer/Body/index.js +4 -5
  10. package/lib/theme/ApiExplorer/CodeSnippets/index.js +96 -61
  11. package/lib/theme/ApiExplorer/CodeSnippets/languages.js +12 -1
  12. package/lib/theme/ApiExplorer/CodeSnippets/languages.test.d.ts +1 -0
  13. package/lib/theme/ApiExplorer/CodeSnippets/languages.test.js +102 -0
  14. package/lib/theme/ApiExplorer/CodeTabs/index.d.ts +1 -1
  15. package/lib/theme/ApiExplorer/CodeTabs/index.js +6 -5
  16. package/lib/theme/ApiExplorer/Export/index.js +9 -2
  17. package/lib/theme/ApiExplorer/FormFileUpload/index.js +1 -2
  18. package/lib/theme/ApiExplorer/FormLabel/index.js +1 -2
  19. package/lib/theme/ApiExplorer/FormTextInput/index.js +1 -2
  20. package/lib/theme/ApiExplorer/LiveEditor/index.js +1 -2
  21. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.js +5 -3
  22. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +75 -4
  23. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.js +1 -2
  24. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.js +67 -4
  25. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.js +65 -1
  26. package/lib/theme/ApiExplorer/ParamOptions/index.js +2 -3
  27. package/lib/theme/ApiExplorer/Request/index.js +17 -18
  28. package/lib/theme/ApiExplorer/Response/index.js +54 -12
  29. package/lib/theme/ApiExplorer/SecuritySchemes/index.js +57 -50
  30. package/lib/theme/ApiExplorer/Server/index.js +2 -3
  31. package/lib/theme/ApiItem/index.js +59 -33
  32. package/lib/theme/ApiTabs/index.d.ts +1 -1
  33. package/lib/theme/ApiTabs/index.js +7 -7
  34. package/lib/theme/DiscriminatorTabs/index.d.ts +1 -1
  35. package/lib/theme/DiscriminatorTabs/index.js +6 -5
  36. package/lib/theme/Example/index.js +3 -4
  37. package/lib/theme/MimeTabs/index.d.ts +1 -1
  38. package/lib/theme/MimeTabs/index.js +6 -5
  39. package/lib/theme/OperationTabs/index.d.ts +1 -1
  40. package/lib/theme/OperationTabs/index.js +6 -5
  41. package/lib/theme/ParamsDetails/index.js +1 -2
  42. package/lib/theme/ParamsItem/index.js +7 -8
  43. package/lib/theme/RequestSchema/index.js +57 -57
  44. package/lib/theme/ResponseExamples/index.js +3 -4
  45. package/lib/theme/ResponseSchema/index.js +26 -24
  46. package/lib/theme/Schema/index.js +148 -27
  47. package/lib/theme/SchemaExpansion/_SchemaExpansion.scss +113 -0
  48. package/lib/theme/SchemaExpansion/context.d.ts +24 -0
  49. package/lib/theme/SchemaExpansion/context.js +187 -0
  50. package/lib/theme/SchemaExpansion/index.d.ts +4 -0
  51. package/lib/theme/SchemaExpansion/index.js +314 -0
  52. package/lib/theme/SchemaItem/index.js +9 -10
  53. package/lib/theme/SchemaTabs/index.d.ts +1 -1
  54. package/lib/theme/SchemaTabs/index.js +6 -5
  55. package/lib/theme/StatusCodes/index.js +2 -4
  56. package/lib/theme/TabItem/index.d.ts +5 -0
  57. package/lib/theme/TabItem/index.js +51 -0
  58. package/lib/theme/TabItem/styles.module.css +3 -0
  59. package/lib/theme/Tabs/index.d.ts +5 -0
  60. package/lib/theme/Tabs/index.js +148 -0
  61. package/lib/theme/Tabs/styles.module.css +7 -0
  62. package/lib/theme/styles.scss +1 -0
  63. package/lib/theme/translationIds.d.ts +1 -93
  64. package/lib/theme/translationIds.js +0 -109
  65. package/lib/theme/utils/codeBlockUtils.d.ts +28 -0
  66. package/lib/theme/utils/codeBlockUtils.js +223 -0
  67. package/lib/theme/utils/reactUtils.d.ts +1 -0
  68. package/lib/theme/utils/reactUtils.js +23 -0
  69. package/lib/theme/utils/scrollUtils.d.ts +7 -0
  70. package/lib/theme/utils/scrollUtils.js +175 -0
  71. package/lib/theme/utils/tabsUtils.d.ts +47 -0
  72. package/lib/theme/utils/tabsUtils.js +299 -0
  73. package/lib/theme/utils/useCodeWordWrap.d.ts +8 -0
  74. package/lib/theme/utils/useCodeWordWrap.js +84 -0
  75. package/lib/theme/utils/useMutationObserver.d.ts +3 -0
  76. package/lib/theme/utils/useMutationObserver.js +34 -0
  77. package/package.json +4 -4
  78. package/src/markdown/schema.test.ts +102 -0
  79. package/src/markdown/schema.ts +42 -15
  80. package/src/theme/ApiExplorer/ApiCodeBlock/Container/index.tsx +2 -1
  81. package/src/theme/ApiExplorer/ApiCodeBlock/Content/String.tsx +8 -7
  82. package/src/theme/ApiExplorer/ApiCodeBlock/Line/index.tsx +1 -1
  83. package/src/theme/ApiExplorer/ApiCodeBlock/index.tsx +1 -1
  84. package/src/theme/ApiExplorer/Authorization/index.tsx +9 -10
  85. package/src/theme/ApiExplorer/Body/index.tsx +7 -5
  86. package/src/theme/ApiExplorer/CodeSnippets/index.tsx +103 -59
  87. package/src/theme/ApiExplorer/CodeSnippets/languages.test.ts +109 -0
  88. package/src/theme/ApiExplorer/CodeSnippets/languages.ts +13 -1
  89. package/src/theme/ApiExplorer/CodeTabs/index.tsx +5 -5
  90. package/src/theme/ApiExplorer/Export/index.tsx +6 -2
  91. package/src/theme/ApiExplorer/FormFileUpload/index.tsx +1 -2
  92. package/src/theme/ApiExplorer/FormLabel/index.tsx +1 -2
  93. package/src/theme/ApiExplorer/FormTextInput/index.tsx +1 -2
  94. package/src/theme/ApiExplorer/LiveEditor/index.tsx +1 -2
  95. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx +5 -3
  96. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx +20 -4
  97. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx +1 -2
  98. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx +15 -4
  99. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.tsx +11 -1
  100. package/src/theme/ApiExplorer/ParamOptions/index.tsx +2 -3
  101. package/src/theme/ApiExplorer/Request/index.tsx +23 -18
  102. package/src/theme/ApiExplorer/Response/index.tsx +63 -9
  103. package/src/theme/ApiExplorer/SecuritySchemes/index.tsx +60 -52
  104. package/src/theme/ApiExplorer/Server/index.tsx +8 -3
  105. package/src/theme/ApiItem/index.tsx +43 -21
  106. package/src/theme/ApiTabs/index.tsx +8 -8
  107. package/src/theme/DiscriminatorTabs/index.tsx +6 -5
  108. package/src/theme/Example/index.tsx +3 -4
  109. package/src/theme/MimeTabs/index.tsx +9 -8
  110. package/src/theme/OperationTabs/index.tsx +5 -4
  111. package/src/theme/ParamsDetails/index.tsx +1 -2
  112. package/src/theme/ParamsItem/index.tsx +13 -8
  113. package/src/theme/RequestSchema/index.tsx +38 -40
  114. package/src/theme/ResponseExamples/index.tsx +3 -4
  115. package/src/theme/ResponseSchema/index.tsx +16 -17
  116. package/src/theme/Schema/index.tsx +156 -27
  117. package/src/theme/SchemaExpansion/_SchemaExpansion.scss +113 -0
  118. package/src/theme/SchemaExpansion/context.tsx +154 -0
  119. package/src/theme/SchemaExpansion/index.tsx +236 -0
  120. package/src/theme/SchemaItem/index.tsx +18 -10
  121. package/src/theme/SchemaTabs/index.tsx +6 -5
  122. package/src/theme/StatusCodes/index.tsx +2 -3
  123. package/src/theme/TabItem/index.tsx +61 -0
  124. package/src/theme/TabItem/styles.module.css +3 -0
  125. package/src/theme/Tabs/index.tsx +164 -0
  126. package/src/theme/Tabs/styles.module.css +7 -0
  127. package/src/theme/styles.scss +1 -0
  128. package/src/theme/translationIds.ts +37 -106
  129. package/src/theme/utils/codeBlockUtils.ts +296 -0
  130. package/src/theme/utils/reactUtils.ts +22 -0
  131. package/src/theme/utils/scrollUtils.tsx +153 -0
  132. package/src/theme/utils/tabsUtils.tsx +329 -0
  133. package/src/theme/utils/useCodeWordWrap.ts +110 -0
  134. package/src/theme/utils/useMutationObserver.ts +43 -0
  135. package/src/theme-classic.d.ts +0 -96
  136. package/src/types.d.ts +27 -0
  137. package/tsconfig.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docusaurus-theme-openapi-docs",
3
3
  "description": "OpenAPI theme for Docusaurus.",
4
- "version": "5.0.1",
4
+ "version": "5.1.0",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -37,8 +37,8 @@
37
37
  "@types/pako": "^2.0.3",
38
38
  "@types/postman-collection": "^3.5.11",
39
39
  "@types/react-modal": "^3.16.3",
40
- "concurrently": "^9.2.0",
41
- "docusaurus-plugin-openapi-docs": "^5.0.1",
40
+ "concurrently": "^10.0.3",
41
+ "docusaurus-plugin-openapi-docs": "^5.1.0",
42
42
  "docusaurus-plugin-sass": "^0.2.6",
43
43
  "eslint-plugin-prettier": "^5.5.1"
44
44
  },
@@ -82,5 +82,5 @@
82
82
  "engines": {
83
83
  "node": ">=14"
84
84
  },
85
- "gitHead": "467fb6d5e1e78e0fd2ce5aaa2904aef701fe87e3"
85
+ "gitHead": "977ec415b37207142f71f6e3d3780df834301d31"
86
86
  }
@@ -0,0 +1,102 @@
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
+ jest.mock(
9
+ "@docusaurus/Translate",
10
+ () => ({
11
+ translate: ({ message }: { message: string }) => message,
12
+ }),
13
+ { virtual: true }
14
+ );
15
+
16
+ import { getSchemaName } from "./schema";
17
+ import { SchemaObject } from "../types";
18
+
19
+ describe("getSchemaName", () => {
20
+ it("returns the type for a primitive schema", () => {
21
+ expect(getSchemaName({ type: "string" } as SchemaObject)).toBe("string");
22
+ });
23
+
24
+ it("appends [] for an array of primitives", () => {
25
+ const schema: SchemaObject = {
26
+ type: "array",
27
+ items: { type: "string" },
28
+ } as SchemaObject;
29
+ expect(getSchemaName(schema)).toBe("string[]");
30
+ });
31
+
32
+ it("appends [][] for an array of arrays of primitives", () => {
33
+ const schema: SchemaObject = {
34
+ type: "array",
35
+ items: { type: "array", items: { type: "string" } },
36
+ } as SchemaObject;
37
+ expect(getSchemaName(schema)).toBe("string[][]");
38
+ });
39
+
40
+ it("appends [][] for an array of arrays of objects (issue #1114)", () => {
41
+ const schema: SchemaObject = {
42
+ type: "array",
43
+ items: {
44
+ type: "array",
45
+ items: { type: "object", properties: { foo: { type: "string" } } },
46
+ },
47
+ } as SchemaObject;
48
+ expect(getSchemaName(schema)).toBe("object[][]");
49
+ });
50
+
51
+ it("handles three levels of array nesting", () => {
52
+ const schema: SchemaObject = {
53
+ type: "array",
54
+ items: {
55
+ type: "array",
56
+ items: { type: "array", items: { type: "integer" } },
57
+ },
58
+ } as SchemaObject;
59
+ expect(getSchemaName(schema)).toBe("integer[][][]");
60
+ });
61
+
62
+ it("joins OpenAPI 3.1 type arrays with ` | ` (issue #950)", () => {
63
+ const schema = { type: ["string", "null"] } as unknown as SchemaObject;
64
+ expect(getSchemaName(schema)).toBe("string | null");
65
+ });
66
+
67
+ it("unwraps a single-element type array", () => {
68
+ const schema = { type: ["integer"] } as unknown as SchemaObject;
69
+ expect(getSchemaName(schema)).toBe("integer");
70
+ });
71
+
72
+ it("renders single type with format as `type<format>`", () => {
73
+ const schema = {
74
+ type: "string",
75
+ format: "uuid",
76
+ } as SchemaObject;
77
+ expect(getSchemaName(schema)).toBe("string<uuid>");
78
+ });
79
+
80
+ it("renders type union with format", () => {
81
+ const schema = {
82
+ type: ["string", "null"],
83
+ format: "uuid",
84
+ } as unknown as SchemaObject;
85
+ expect(getSchemaName(schema)).toBe("(string | null)<uuid>");
86
+ });
87
+
88
+ it("resolves type from an allOf wrapper that contains an enum", () => {
89
+ const schema = {
90
+ allOf: [{ type: "string", enum: ["a", "b"] }],
91
+ } as unknown as SchemaObject;
92
+ expect(getSchemaName(schema)).toBe("string");
93
+ });
94
+
95
+ it("renders array of items whose type is a union", () => {
96
+ const schema: SchemaObject = {
97
+ type: "array",
98
+ items: { type: ["string", "null"] } as any,
99
+ } as SchemaObject;
100
+ expect(getSchemaName(schema)).toBe("(string | null)[]");
101
+ });
102
+ });
@@ -7,7 +7,6 @@
7
7
 
8
8
  import { translate } from "@docusaurus/Translate";
9
9
 
10
- import { OPENAPI_SCHEMA_ITEM } from "../theme/translationIds";
11
10
  import { SchemaObject } from "../types";
12
11
 
13
12
  /**
@@ -48,6 +47,24 @@ function getTypeFromSchema(schema: SchemaObject): string | undefined {
48
47
  return undefined;
49
48
  }
50
49
 
50
+ // OpenAPI 3.1 / JSON Schema 2020-12 allows `type` to be an array of type names
51
+ // (e.g. `["string", "null"]`). Normalize to a single name and a pretty-printed
52
+ // union form joined with ` | `.
53
+ function normalizeType(type: unknown): {
54
+ single?: string;
55
+ pretty?: string;
56
+ isUnion: boolean;
57
+ } {
58
+ if (Array.isArray(type)) {
59
+ const filtered = type.filter((t): t is string => typeof t === "string");
60
+ if (filtered.length === 0) return { isUnion: false };
61
+ if (filtered.length === 1) return { single: filtered[0], isUnion: false };
62
+ return { pretty: filtered.join(" | "), isUnion: true };
63
+ }
64
+ if (typeof type === "string") return { single: type, isUnion: false };
65
+ return { isUnion: false };
66
+ }
67
+
51
68
  function prettyName(schema: SchemaObject, circular?: boolean) {
52
69
  // Handle enum-only schemas (valid in JSON Schema)
53
70
  // When enum is present without explicit type, treat as string
@@ -55,7 +72,15 @@ function prettyName(schema: SchemaObject, circular?: boolean) {
55
72
  return "string";
56
73
  }
57
74
 
75
+ const t = normalizeType(schema.type);
76
+
58
77
  if (schema.format) {
78
+ if (t.single) {
79
+ return `${t.single}<${schema.format}>`;
80
+ }
81
+ if (t.isUnion) {
82
+ return `(${t.pretty})<${schema.format}>`;
83
+ }
59
84
  return schema.format;
60
85
  }
61
86
 
@@ -83,21 +108,19 @@ function prettyName(schema: SchemaObject, circular?: boolean) {
83
108
  return "object";
84
109
  }
85
110
 
86
- if (schema.type === "object") {
87
- return schema.xml?.name ?? schema.type;
88
- // return schema.type;
111
+ if (t.single === "object") {
112
+ return schema.xml?.name ?? t.single;
89
113
  }
90
114
 
91
- if (schema.type === "array") {
92
- return schema.xml?.name ?? schema.type;
93
- // return schema.type;
115
+ if (t.single === "array") {
116
+ return schema.xml?.name ?? t.single;
94
117
  }
95
118
 
96
- if (Array.isArray(schema.type)) {
97
- return schema.type.join(" | ");
119
+ if (t.isUnion) {
120
+ return t.pretty;
98
121
  }
99
122
 
100
- return schema.title ?? schema.type;
123
+ return schema.title ?? t.single;
101
124
  }
102
125
 
103
126
  export function getSchemaName(
@@ -105,7 +128,11 @@ export function getSchemaName(
105
128
  circular?: boolean
106
129
  ): string {
107
130
  if (schema.items) {
108
- return prettyName(schema.items, circular) + "[]";
131
+ const items = schema.items as SchemaObject;
132
+ const inner = getSchemaName(items, circular);
133
+ const needsParens =
134
+ Array.isArray((items as any).type) && (items as any).type.length > 1;
135
+ return needsParens ? `(${inner})[]` : `${inner}[]`;
109
136
  }
110
137
 
111
138
  return prettyName(schema, circular) ?? "";
@@ -130,7 +157,7 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
130
157
  }
131
158
 
132
159
  let message = `**${translate({
133
- id: OPENAPI_SCHEMA_ITEM.POSSIBLE_VALUES,
160
+ id: "theme.openapi.schemaItem.possibleValues",
134
161
  message: "Possible values:",
135
162
  })}** `;
136
163
 
@@ -149,11 +176,11 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
149
176
  let minLength;
150
177
  let maxLength;
151
178
  const charactersMessage = translate({
152
- id: OPENAPI_SCHEMA_ITEM.CHARACTERS,
179
+ id: "theme.openapi.schemaItem.characters",
153
180
  message: "characters",
154
181
  });
155
182
  const nonEmptyMessage = translate({
156
- id: OPENAPI_SCHEMA_ITEM.NON_EMPTY,
183
+ id: "theme.openapi.schemaItem.nonEmpty",
157
184
  message: "non-empty",
158
185
  });
159
186
  if (schema.minLength && schema.minLength > 1) {
@@ -218,7 +245,7 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
218
245
 
219
246
  if (schema.pattern) {
220
247
  const expressionMessage = translate({
221
- id: OPENAPI_SCHEMA_ITEM.EXPRESSION,
248
+ id: "theme.openapi.schemaItem.expression",
222
249
  message: "Value must match regular expression",
223
250
  });
224
251
  qualifierGroups.push(`${expressionMessage} \`${schema.pattern}\``);
@@ -8,9 +8,10 @@
8
8
  import React, { ComponentProps } from "react";
9
9
 
10
10
  import { ThemeClassNames, usePrismTheme } from "@docusaurus/theme-common";
11
- import { getPrismCssVariables } from "@docusaurus/theme-common/internal";
12
11
  import clsx from "clsx";
13
12
 
13
+ import { getPrismCssVariables } from "@theme/utils/codeBlockUtils";
14
+
14
15
  export default function CodeBlockContainer<T extends "div" | "pre">({
15
16
  as: As,
16
17
  ...props
@@ -8,13 +8,6 @@
8
8
  import React from "react";
9
9
 
10
10
  import { useThemeConfig, usePrismTheme } from "@docusaurus/theme-common";
11
- import {
12
- parseCodeBlockTitle,
13
- parseLanguage,
14
- parseLines,
15
- containsLineNumbers,
16
- useCodeWordWrap,
17
- } from "@docusaurus/theme-common/internal";
18
11
  import Container from "@theme/ApiExplorer/ApiCodeBlock/Container";
19
12
  import CopyButton from "@theme/ApiExplorer/ApiCodeBlock/CopyButton";
20
13
  import ExpandButton from "@theme/ApiExplorer/ApiCodeBlock/ExpandButton";
@@ -24,6 +17,14 @@ import type { Props } from "@theme/CodeBlock/Content/String";
24
17
  import clsx from "clsx";
25
18
  import { Highlight, Language } from "prism-react-renderer";
26
19
 
20
+ import {
21
+ containsLineNumbers,
22
+ parseCodeBlockTitle,
23
+ parseLanguage,
24
+ parseLines,
25
+ } from "@theme/utils/codeBlockUtils";
26
+ import { useCodeWordWrap } from "@theme/utils/useCodeWordWrap";
27
+
27
28
  export default function CodeBlockString({
28
29
  children,
29
30
  className: blockClassName = "",
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React from "react";
9
9
 
10
- import { LineProps } from "@docusaurus/theme-common/internal";
10
+ import type { Props as LineProps } from "@theme/CodeBlock/Line";
11
11
  import clsx from "clsx";
12
12
 
13
13
  export default function CodeBlockLine({
@@ -7,8 +7,8 @@
7
7
 
8
8
  import React, { isValidElement, ReactNode } from "react";
9
9
 
10
- import { CodeBlockProps } from "@docusaurus/theme-common/internal";
11
10
  import useIsBrowser from "@docusaurus/useIsBrowser";
11
+ import type { Props as CodeBlockProps } from "@theme/CodeBlock";
12
12
  import ElementContent from "@theme/ApiExplorer/ApiCodeBlock/Content/Element";
13
13
  import StringContent from "@theme/ApiExplorer/ApiCodeBlock/Content/String";
14
14
 
@@ -12,7 +12,6 @@ import FormItem from "@theme/ApiExplorer/FormItem";
12
12
  import FormSelect from "@theme/ApiExplorer/FormSelect";
13
13
  import FormTextInput from "@theme/ApiExplorer/FormTextInput";
14
14
  import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
15
- import { OPENAPI_AUTH } from "@theme/translationIds";
16
15
 
17
16
  import { setAuthData, setSelectedAuth } from "./slice";
18
17
 
@@ -37,7 +36,7 @@ function Authorization() {
37
36
  <FormItem>
38
37
  <FormSelect
39
38
  label={translate({
40
- id: OPENAPI_AUTH.SECURITY_SCHEME,
39
+ id: "theme.openapi.auth.securityScheme",
41
40
  message: "Security Scheme",
42
41
  })}
43
42
  options={optionKeys}
@@ -54,11 +53,11 @@ function Authorization() {
54
53
  <FormItem key={a.key + "-bearer"}>
55
54
  <FormTextInput
56
55
  label={translate({
57
- id: OPENAPI_AUTH.BEARER_TOKEN,
56
+ id: "theme.openapi.auth.bearerToken",
58
57
  message: "Bearer Token",
59
58
  })}
60
59
  placeholder={translate({
61
- id: OPENAPI_AUTH.BEARER_TOKEN,
60
+ id: "theme.openapi.auth.bearerToken",
62
61
  message: "Bearer Token",
63
62
  })}
64
63
  password
@@ -83,11 +82,11 @@ function Authorization() {
83
82
  <FormItem key={a.key + "-oauth2"}>
84
83
  <FormTextInput
85
84
  label={translate({
86
- id: OPENAPI_AUTH.BEARER_TOKEN,
85
+ id: "theme.openapi.auth.bearerToken",
87
86
  message: "Bearer Token",
88
87
  })}
89
88
  placeholder={translate({
90
- id: OPENAPI_AUTH.BEARER_TOKEN,
89
+ id: "theme.openapi.auth.bearerToken",
91
90
  message: "Bearer Token",
92
91
  })}
93
92
  password
@@ -113,11 +112,11 @@ function Authorization() {
113
112
  <FormItem>
114
113
  <FormTextInput
115
114
  label={translate({
116
- id: OPENAPI_AUTH.USERNAME,
115
+ id: "theme.openapi.auth.username",
117
116
  message: "Username",
118
117
  })}
119
118
  placeholder={translate({
120
- id: OPENAPI_AUTH.USERNAME,
119
+ id: "theme.openapi.auth.username",
121
120
  message: "Username",
122
121
  })}
123
122
  value={data[a.key].username ?? ""}
@@ -136,11 +135,11 @@ function Authorization() {
136
135
  <FormItem>
137
136
  <FormTextInput
138
137
  label={translate({
139
- id: OPENAPI_AUTH.PASSWORD,
138
+ id: "theme.openapi.auth.password",
140
139
  message: "Password",
141
140
  })}
142
141
  placeholder={translate({
143
- id: OPENAPI_AUTH.PASSWORD,
142
+ id: "theme.openapi.auth.password",
144
143
  message: "Password",
145
144
  })}
146
145
  password
@@ -16,7 +16,6 @@ import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
16
16
  import Markdown from "@theme/Markdown";
17
17
  import SchemaTabs from "@theme/SchemaTabs";
18
18
  import TabItem from "@theme/TabItem";
19
- import { OPENAPI_BODY, OPENAPI_REQUEST } from "@theme/translationIds";
20
19
  import { sampleFromSchema } from "docusaurus-plugin-openapi-docs/lib/openapi/createSchemaExample";
21
20
  import type { RequestBodyObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
22
21
  import format from "xml-formatter";
@@ -292,7 +291,10 @@ function Body({
292
291
  <FormFileUpload
293
292
  placeholder={
294
293
  schema.description ||
295
- translate({ id: OPENAPI_REQUEST.BODY_TITLE, message: "Body" })
294
+ translate({
295
+ id: "theme.openapi.request.body.title",
296
+ message: "Body",
297
+ })
296
298
  }
297
299
  onChange={(file: any) => {
298
300
  if (file === undefined) {
@@ -322,7 +324,7 @@ function Body({
322
324
  {/* @ts-ignore */}
323
325
  <TabItem
324
326
  label={translate({
325
- id: OPENAPI_BODY.EXAMPLE_FROM_SCHEMA,
327
+ id: "theme.openapi.body.exampleFromSchema",
326
328
  message: "Example (from schema)",
327
329
  })}
328
330
  value="Example (from schema)"
@@ -412,7 +414,7 @@ function Body({
412
414
  {/* @ts-ignore */}
413
415
  <TabItem
414
416
  label={translate({
415
- id: OPENAPI_BODY.EXAMPLE_FROM_SCHEMA,
417
+ id: "theme.openapi.body.exampleFromSchema",
416
418
  message: "Example (from schema)",
417
419
  })}
418
420
  value="Example (from schema)"
@@ -453,7 +455,7 @@ function Body({
453
455
  {/* @ts-ignore */}
454
456
  <TabItem
455
457
  label={translate({
456
- id: OPENAPI_BODY.EXAMPLE_FROM_SCHEMA,
458
+ id: "theme.openapi.body.exampleFromSchema",
457
459
  message: "Example (from schema)",
458
460
  })}
459
461
  value="Example (from schema)"
@@ -5,14 +5,16 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  * ========================================================================== */
7
7
 
8
- import React, { useState, useEffect } from "react";
8
+ import React, { useEffect, useMemo, useState } from "react";
9
9
 
10
+ import { useStorageSlot } from "@docusaurus/theme-common";
10
11
  import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
11
12
  import ApiCodeBlock from "@theme/ApiExplorer/ApiCodeBlock";
12
13
  import buildPostmanRequest from "@theme/ApiExplorer/buildPostmanRequest";
13
14
  import CodeTabs from "@theme/ApiExplorer/CodeTabs";
14
15
  import { useResolvedEncoding } from "@theme/ApiExplorer/EncodingSelection/useResolvedEncoding";
15
16
  import { useTypedSelector } from "@theme/ApiItem/hooks";
17
+ import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
16
18
  import cloneDeep from "lodash/cloneDeep";
17
19
  import codegen from "postman-code-generators";
18
20
  import * as sdk from "postman-collection";
@@ -42,6 +44,21 @@ function CodeTab({ children, hidden, className }: any): React.JSX.Element {
42
44
  );
43
45
  }
44
46
 
47
+ /** Align with Docusaurus `<Tabs groupId="code-samples">` persisted tab value. */
48
+ function resolveOuterLanguageFromPersistedTab(
49
+ persistedTab: string | null,
50
+ langs: Language[]
51
+ ): Language {
52
+ if (langs.length === 1) {
53
+ return langs[0];
54
+ }
55
+ const matched = langs.find((lang) => lang.language === persistedTab);
56
+ if (matched) {
57
+ return matched;
58
+ }
59
+ return langs[0];
60
+ }
61
+
45
62
  function CodeSnippets({
46
63
  postman,
47
64
  codeSamples,
@@ -114,45 +131,66 @@ function CodeSnippets({
114
131
  encoding,
115
132
  });
116
133
 
134
+ const themeConfig = siteConfig.themeConfig as ThemeConfig;
135
+ const hideGeneratedSnippets =
136
+ themeConfig?.api?.hideGeneratedSnippets ?? false;
137
+
117
138
  // User-defined languages array
118
139
  // Can override languageSet, change order of langs, override options and variants
119
140
  const userDefinedLanguageSet =
120
141
  (siteConfig?.themeConfig?.languageTabs as Language[] | undefined) ??
121
142
  languageSet;
122
143
 
123
- // Filter languageSet by user-defined langs
124
- const filteredLanguageSet = languageSet.filter((ls) => {
125
- return userDefinedLanguageSet?.some((lang) => {
126
- return lang.language === ls.language;
144
+ const mergedLangs = useMemo(() => {
145
+ const filteredLanguageSet = languageSet.filter((ls) => {
146
+ return userDefinedLanguageSet?.some((lang) => {
147
+ return lang.language === ls.language;
148
+ });
127
149
  });
128
- });
150
+ return mergeCodeSampleLanguage(
151
+ mergeArraysbyLanguage(userDefinedLanguageSet, filteredLanguageSet),
152
+ codeSamples
153
+ );
154
+ }, [userDefinedLanguageSet, codeSamples]);
129
155
 
130
- // Merge user-defined langs into languageSet
131
- const mergedLangs = mergeCodeSampleLanguage(
132
- mergeArraysbyLanguage(userDefinedLanguageSet, filteredLanguageSet),
133
- codeSamples
134
- );
156
+ const [persistedOuterTab] = useStorageSlot("docusaurus.tab.code-samples");
135
157
 
136
- // Read defaultLang from localStorage
137
- const defaultLang: Language[] = mergedLangs.filter(
138
- (lang) =>
139
- lang.language === localStorage.getItem("docusaurus.tab.code-samples")
158
+ const initialOuterLanguage = useMemo(
159
+ () => resolveOuterLanguageFromPersistedTab(persistedOuterTab, mergedLangs),
160
+ [persistedOuterTab, mergedLangs]
140
161
  );
162
+
141
163
  const [selectedVariant, setSelectedVariant] = useState<string | undefined>();
142
164
  const [selectedSample, setSelectedSample] = useState<string | undefined>();
143
- const [language, setLanguage] = useState(() => {
144
- // Return first index if only 1 user-defined language exists
145
- if (mergedLangs.length === 1) {
146
- return mergedLangs[0];
147
- }
148
- // Fall back to language in localStorage or first user-defined language
149
- return defaultLang[0] ?? mergedLangs[0];
150
- });
165
+ const [language, setLanguage] = useState(() =>
166
+ resolveOuterLanguageFromPersistedTab(persistedOuterTab, mergedLangs)
167
+ );
168
+
169
+ useEffect(() => {
170
+ const next = resolveOuterLanguageFromPersistedTab(
171
+ persistedOuterTab,
172
+ mergedLangs
173
+ );
174
+ setLanguage((prev) => {
175
+ if (prev?.language !== next.language) {
176
+ return next;
177
+ }
178
+ return mergedLangs.find((l) => l.language === next.language) ?? next;
179
+ });
180
+ }, [persistedOuterTab, mergedLangs]);
151
181
  const [codeText, setCodeText] = useState<string>("");
152
182
  const [codeSampleCodeText, setCodeSampleCodeText] = useState<string>(() =>
153
183
  getCodeSampleSourceFromLanguage(language)
154
184
  );
155
185
 
186
+ // Reset selectedSample whenever the active language changes so the inner
187
+ // tab strip falls back to its first sample instead of trying to match an
188
+ // id that belongs to a different language's samples.
189
+ useEffect(() => {
190
+ setSelectedSample(language?.samples?.[0]);
191
+ // eslint-disable-next-line react-hooks/exhaustive-deps
192
+ }, [language?.language]);
193
+
156
194
  useEffect(() => {
157
195
  if (language && !!language.sample) {
158
196
  setCodeSampleCodeText(getCodeSampleSourceFromLanguage(language));
@@ -259,7 +297,7 @@ function CodeSnippets({
259
297
  setSelectedSample: setSelectedSample,
260
298
  }}
261
299
  languageSet={mergedLangs}
262
- defaultValue={defaultLang[0]?.language ?? mergedLangs[0].language}
300
+ defaultValue={initialOuterLanguage.language}
263
301
  lazy
264
302
  >
265
303
  {mergedLangs.map((lang) => {
@@ -282,7 +320,11 @@ function CodeSnippets({
282
320
  }}
283
321
  includeSample={true}
284
322
  currentLanguage={lang}
285
- defaultValue={selectedSample}
323
+ defaultValue={
324
+ selectedSample && lang.samples.includes(selectedSample)
325
+ ? selectedSample
326
+ : lang.samples[0]
327
+ }
286
328
  languageSet={mergedLangs}
287
329
  lazy
288
330
  >
@@ -295,7 +337,7 @@ function CodeSnippets({
295
337
  ? lang.samplesLabels[index]
296
338
  : sample
297
339
  }
298
- key={`${lang.language}-${lang.sample}`}
340
+ key={`${lang.language}-${sample}`}
299
341
  attributes={{
300
342
  className: `openapi-tabs__code-item--sample`,
301
343
  }}
@@ -315,40 +357,42 @@ function CodeSnippets({
315
357
  )}
316
358
 
317
359
  {/* Inner generated code snippets */}
318
- <CodeTabs
319
- className="openapi-tabs__code-container-inner"
320
- action={{
321
- setLanguage: setLanguage,
322
- setSelectedVariant: setSelectedVariant,
323
- }}
324
- includeVariant={true}
325
- currentLanguage={lang}
326
- defaultValue={selectedVariant}
327
- languageSet={mergedLangs}
328
- lazy
329
- >
330
- {lang.variants.map((variant, index) => {
331
- return (
332
- <CodeTab
333
- value={variant.toLowerCase()}
334
- label={variant.toUpperCase()}
335
- key={`${lang.language}-${lang.variant}`}
336
- attributes={{
337
- className: `openapi-tabs__code-item--variant`,
338
- }}
339
- >
340
- {/* @ts-ignore */}
341
- <ApiCodeBlock
342
- language={lang.highlight}
343
- className="openapi-explorer__code-block"
344
- showLineNumbers={true}
360
+ {!(hideGeneratedSnippets && lang.samples?.length) && (
361
+ <CodeTabs
362
+ className="openapi-tabs__code-container-inner"
363
+ action={{
364
+ setLanguage: setLanguage,
365
+ setSelectedVariant: setSelectedVariant,
366
+ }}
367
+ includeVariant={true}
368
+ currentLanguage={lang}
369
+ defaultValue={selectedVariant}
370
+ languageSet={mergedLangs}
371
+ lazy
372
+ >
373
+ {lang.variants.map((variant, index) => {
374
+ return (
375
+ <CodeTab
376
+ value={variant.toLowerCase()}
377
+ label={variant.toUpperCase()}
378
+ key={`${lang.language}-${variant}`}
379
+ attributes={{
380
+ className: `openapi-tabs__code-item--variant`,
381
+ }}
345
382
  >
346
- {codeText}
347
- </ApiCodeBlock>
348
- </CodeTab>
349
- );
350
- })}
351
- </CodeTabs>
383
+ {/* @ts-ignore */}
384
+ <ApiCodeBlock
385
+ language={lang.highlight}
386
+ className="openapi-explorer__code-block"
387
+ showLineNumbers={true}
388
+ >
389
+ {codeText}
390
+ </ApiCodeBlock>
391
+ </CodeTab>
392
+ );
393
+ })}
394
+ </CodeTabs>
395
+ )}
352
396
  </CodeTab>
353
397
  );
354
398
  })}