mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-25 20:03:16 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d980d7a9ea | |||
| eab3b7aea4 | |||
| 3af177be19 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Show sections tabs only if there is at least two sections
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/react-openapi": patch
|
||||
---
|
||||
|
||||
Fix Python code example for JSON payload
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve OpenAPIResponses examples and schemas
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook-v2": patch
|
||||
---
|
||||
|
||||
Fallback to the default section if one is not passed in the resolution context.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook-v2": patch
|
||||
---
|
||||
|
||||
Improve error messages around undefined site sections.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix dropdown menu not staying fixed
|
||||
+1
-2
@@ -20,8 +20,7 @@
|
||||
"**/.wrangler/**/*",
|
||||
"packages/openapi-parser/src/fixtures/**/*",
|
||||
"packages/emoji-codepoints/index.ts",
|
||||
"packages/icons/src/data/*.json",
|
||||
"packages/cache-do/worker-configuration.d.ts"
|
||||
"packages/icons/src/data/*.json"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"name": "@gitbook/cache-tags",
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.111.0",
|
||||
"@gitbook/api": "^0.109.0",
|
||||
"assert-never": "^1.2.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -234,8 +234,8 @@
|
||||
"version": "1.1.10",
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "^1.2.19",
|
||||
"@scalar/oas-utils": "^0.2.130",
|
||||
"@scalar/api-client-react": "^1.2.5",
|
||||
"@scalar/oas-utils": "^0.2.120",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@gitbook/api": "0.111.0",
|
||||
"@gitbook/api": "0.109.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
},
|
||||
@@ -628,7 +628,7 @@
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
|
||||
|
||||
"@gitbook/api": ["@gitbook/api@0.111.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-E5Pk28kPD4p6XNWdwFM9pgDijdByseIZQqcFK+/hoW5tEZa5Yw/plRKJyN1hmwfPL6SKq6Maf0fbIzTQiVXyQQ=="],
|
||||
"@gitbook/api": ["@gitbook/api@0.109.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-nzhxmiNR2MMHh+HoU7fFdOJ+u4BFyXjtsVquQf3VhZMjeI71yeLx5JEbjkOcijVd/GMAtvpYlYR2qcPqzq27gw=="],
|
||||
|
||||
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
|
||||
|
||||
@@ -1076,21 +1076,21 @@
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
|
||||
|
||||
"@scalar/api-client": ["@scalar/api-client@2.3.19", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.47", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.13", "@scalar/oas-utils": "0.2.130", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.14", "@scalar/openapi-types": "0.2.0", "@scalar/postman-to-openapi": "0.2.3", "@scalar/snippetz": "0.2.19", "@scalar/themes": "0.9.86", "@scalar/types": "0.1.7", "@scalar/use-codemirror": "0.11.92", "@scalar/use-hooks": "0.1.40", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.1.5", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "type-fest": "^4.20.0", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-1Scff4QL6UExxcmSYv5j1dktvQZTXbmDUJp99RqmUROEhneNWEeWaZe+GZWsda5mvDoe4vP9zZKaymkulBDKYQ=="],
|
||||
"@scalar/api-client": ["@scalar/api-client@2.3.5", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.37", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.2", "@scalar/oas-utils": "0.2.120", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.10", "@scalar/openapi-types": "0.1.9", "@scalar/postman-to-openapi": "0.1.43", "@scalar/snippetz": "0.2.16", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "@scalar/use-codemirror": "0.11.82", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-bVBP8H3laa4VG3dExv4Ak/Kf+q9z8aK/Glpv6CPE0wxgZJWxwkJoSrvwxUE+46JktSObUrhu4xQiqSCmBz8esQ=="],
|
||||
|
||||
"@scalar/api-client-react": ["@scalar/api-client-react@1.2.19", "", { "dependencies": { "@scalar/api-client": "2.3.19", "@scalar/types": "0.1.7", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-zWUQvzAgOQ+oAbzswOlN42OJhwKku5RIIfBlukFMT31ceun3cp8e7+RT5DS6qwwQ2FROhSaqqsdM+o7VQ6pXkA=="],
|
||||
"@scalar/api-client-react": ["@scalar/api-client-react@1.2.5", "", { "dependencies": { "@scalar/api-client": "2.3.5", "@scalar/types": "0.1.1", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-DpVDWb1rtNeU8ZqLX2Duos018q/++b3qIOYs7MzB2cXNg1802ZO7tPywXbYyizxLuy/dziI3UwGkjBuOKKXMGQ=="],
|
||||
|
||||
"@scalar/code-highlight": ["@scalar/code-highlight@0.0.27", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-A61FUxqD278L+iLtdbMl4+Pg72wtMrnAYft8v1FNY44uf6UfmM47eDVmzWrc7bSvDevg3ho5QA8cKiJBHXZHJA=="],
|
||||
"@scalar/code-highlight": ["@scalar/code-highlight@0.0.25", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-rmiXaAoL3Zl+OycIO1CMj8apaeAU/p41EmCpHTxInZiFVW0++iClce2fun1lK6qjTMZneR6UwE4qBKiUUVLCpg=="],
|
||||
|
||||
"@scalar/components": ["@scalar/components@0.13.47", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.27", "@scalar/themes": "0.9.86", "@scalar/use-hooks": "0.1.40", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.1.5", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-e88mKKsCEspd06bpPQPnhtEvCo/jjoFFOX9yUSV9sr0sWFZHi0ihq1zvnpLKpULyS+C5zzyoN/tGVhmaYpXgyg=="],
|
||||
"@scalar/components": ["@scalar/components@0.13.37", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.25", "@scalar/themes": "0.9.79", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-bhJxg0I63nUH0qoZgb8nyHKCSzL8L9widP2WIYymIvXpCFLwCvF64Z0CAbihwgXxq0YblPvNM+g5N3dRtmXqdA=="],
|
||||
|
||||
"@scalar/draggable": ["@scalar/draggable@0.1.11", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-EQW9N1+mDORhsbjdtCI3XDvmUKsuKw1uf6r3kT1Mm2zQKT+rWwA0ChsAkEN6OG62C0YumMuXpH71h1seAWptxw=="],
|
||||
|
||||
"@scalar/icons": ["@scalar/icons@0.1.3", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-Bl46u7WsJ7NYjW1Fva7SMvw9c/92pGBP8B68tvDc+QevQ04DVNxw6+ny1NU/PnLtpuu1rUpPdtSCAkV1OdQGZQ=="],
|
||||
|
||||
"@scalar/import": ["@scalar/import@0.3.13", "", { "dependencies": { "@scalar/oas-utils": "0.2.130", "@scalar/openapi-parser": "0.10.14", "yaml": "^2.4.5" } }, "sha512-ooKyRxwtvMpxBnoLt9mSJF8er5rCR6RzGJaIMRCj7ViN776eY4mbLiYcXre/LO8XfLSHb1p7XbNDhfFyTHaUlw=="],
|
||||
"@scalar/import": ["@scalar/import@0.3.2", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-parser": "0.10.10", "yaml": "^2.4.5" } }, "sha512-de7IDZgEYOhhgaq8lFFbfiJ4Hx/ITIXvuLA8SfSl5UQKg+LxiOnnn/5PSKF+pnjWmUf7Kz/ds0eHOXpZv5iMkw=="],
|
||||
|
||||
"@scalar/oas-utils": ["@scalar/oas-utils@0.2.130", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.2.0", "@scalar/themes": "0.9.86", "@scalar/types": "0.1.7", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-sVpdc3+3c/WiNrKEIwzJ+ml2ZQBjarMOTDJCM/IrvYhrJE0nHrdkzxlJgNPi++vJbVl0saYt8LhEItALv7NziA=="],
|
||||
"@scalar/oas-utils": ["@scalar/oas-utils@0.2.120", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.1.9", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-npu0uLClqqXVZfxMdKBWxkWCmONK0jKaUcfmVhGza9Jij5aJyvdfDw6vH/Hh+DghgECwAvQLQeIBZTxjr9ufzg=="],
|
||||
|
||||
"@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="],
|
||||
|
||||
@@ -1098,17 +1098,17 @@
|
||||
|
||||
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
|
||||
|
||||
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.2.3", "", { "dependencies": { "@scalar/oas-utils": "0.2.130", "@scalar/openapi-types": "0.2.0" } }, "sha512-/I5QbDFy+Sh29EIEgub/ztI+1eNtHRn+mln726hR+uWOyVyaDk0FfNC0R4XOn8SsDNyu5eqPbXBb9vceTVG6jQ=="],
|
||||
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.1.43", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-types": "0.1.9" } }, "sha512-gLOkYYPCTKYFBOwyBOKZDc0seZjntmwPTchJUr3oxGQmLB1Y5VBJ+8fXJCTp5TwKiiztjALJs79y9s7jXBdWMA=="],
|
||||
|
||||
"@scalar/snippetz": ["@scalar/snippetz@0.2.19", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-fxC5mL3AZWiXAM21sMe1QU1/mu5KceN8ZmzFaP3xmdK26o/MkPKSLGVWW7w6OQkZi5hNloLHXXQiaI235qomEg=="],
|
||||
"@scalar/snippetz": ["@scalar/snippetz@0.2.16", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-xtIY4kvV619IF2uXg6fDw7emtXwuJeWzLunGAaUTIOwNRw5mGSKqJnLcSnIiSdH54YmN8D2CtdJRo2VxPP9/Wg=="],
|
||||
|
||||
"@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="],
|
||||
"@scalar/themes": ["@scalar/themes@0.9.79", "", { "dependencies": { "@scalar/types": "0.1.1" } }, "sha512-zWiHCZAIjPGa8X9o/NORBPRMTMblLEz2+2RcfW9yIKNO/8H4Gz0rltiGGlJ6vX0o+qHwx7AdgfY+7njmWQR4ng=="],
|
||||
|
||||
"@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
|
||||
"@scalar/types": ["@scalar/types@0.1.1", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-LlUX6AmOOGoRqOMoO835V2FezM1KiO5UlvQC3poT/s7oqD6ranqwRNFxyrPz/IxClPYR+SV1yBUSNKely4ZQhQ=="],
|
||||
|
||||
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.92", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.47", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-WDd50xGLV+q1T36cKzmhqYP+TyHe4MOW0tIiu09ed9aMXGUk6kQgnf0J3rYPsGeNj9IkVG3znZ7oYT3QplLGVA=="],
|
||||
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.82", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.37", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-zFECln7aWKRf6iJO9oovByD59EsrOMenNLfLhneH6L+K1CrBoHFVr4czSDlom1wlr3HPg3xwpZrukoAteHYILQ=="],
|
||||
|
||||
"@scalar/use-hooks": ["@scalar/use-hooks@0.1.40", "", { "dependencies": { "@scalar/themes": "0.9.86", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-z8qtgIcW9Z3PCrP2cbKG+D2EVhpNgl1N0ucGtDg5SMl/fvCyXNfqB9j+u3ygxkouatfQ9zRZuhxreNMkW9/H5g=="],
|
||||
"@scalar/use-hooks": ["@scalar/use-hooks@0.1.33", "", { "dependencies": { "@scalar/themes": "0.9.79", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-ENm0bWwRdAWWF/S6TbE+fFx0vP2mgEpG5APqQBomm0a41/6L2HJ/TN+9ajAvrJXGi0ULWuxihNS4Jue6tpEssA=="],
|
||||
|
||||
"@scalar/use-toasts": ["@scalar/use-toasts@0.7.9", "", { "dependencies": { "nanoid": "^5.0.9", "vue": "^3.5.12", "vue-sonner": "^1.0.3" } }, "sha512-EcUDJY8VozLS9sfoQKvvipStQJ9RuH/nKOzf0BBr+mZDmumi1WFZ1iIJnHVXIN3iSLcSAr5ej6rOqa6jIv4bCQ=="],
|
||||
|
||||
@@ -2320,7 +2320,7 @@
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
|
||||
"nanoid": ["nanoid@5.1.2", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
@@ -2812,7 +2812,7 @@
|
||||
|
||||
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
||||
|
||||
"type-fest": ["type-fest@4.40.0", "", {}, "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="],
|
||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"type-is": ["type-is@2.0.0", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw=="],
|
||||
|
||||
@@ -3714,23 +3714,21 @@
|
||||
|
||||
"@rollup/pluginutils/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||
|
||||
"@scalar/api-client/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="],
|
||||
|
||||
"@scalar/api-client/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||
|
||||
"@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="],
|
||||
|
||||
"@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="],
|
||||
"@scalar/api-client/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||
"@scalar/code-highlight/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="],
|
||||
|
||||
"@scalar/oas-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
|
||||
|
||||
"@scalar/oas-utils/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
|
||||
|
||||
"@scalar/postman-to-openapi/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||
"@scalar/types/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||
|
||||
"@scalar/use-toasts/nanoid": ["nanoid@5.1.2", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g=="],
|
||||
"@scalar/use-hooks/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
@@ -4032,7 +4030,7 @@
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"gitbook-v2/next": ["next@15.3.1-canary.8", "", { "dependencies": { "@next/env": "15.3.1-canary.8", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.8", "@next/swc-darwin-x64": "15.3.1-canary.8", "@next/swc-linux-arm64-gnu": "15.3.1-canary.8", "@next/swc-linux-arm64-musl": "15.3.1-canary.8", "@next/swc-linux-x64-gnu": "15.3.1-canary.8", "@next/swc-linux-x64-musl": "15.3.1-canary.8", "@next/swc-win32-arm64-msvc": "15.3.1-canary.8", "@next/swc-win32-x64-msvc": "15.3.1-canary.8", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Of5a3BTTIl/iUvL2a9Jh7m7G/H8z4Pj5Vs54CLvcdadokxSNgLOpjzbDgFR8J4PawLx6+MOMy19m9Cvr6EPGug=="],
|
||||
"gitbook-v2/next": ["next@15.3.1-canary.7", "", { "dependencies": { "@next/env": "15.3.1-canary.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.7", "@next/swc-darwin-x64": "15.3.1-canary.7", "@next/swc-linux-arm64-gnu": "15.3.1-canary.7", "@next/swc-linux-arm64-musl": "15.3.1-canary.7", "@next/swc-linux-x64-gnu": "15.3.1-canary.7", "@next/swc-linux-x64-musl": "15.3.1-canary.7", "@next/swc-win32-arm64-msvc": "15.3.1-canary.7", "@next/swc-win32-x64-msvc": "15.3.1-canary.7", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RblvC7A7k8cKpSa6JSCbaatb6JAncv2niUSH7TqXt6xHUoItnxa3Fu7xkducFJQdcW2Hv2h0+NBu6kaKf0S4zw=="],
|
||||
|
||||
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
|
||||
|
||||
@@ -4082,8 +4080,6 @@
|
||||
|
||||
"mdast-util-gfm-task-list-item/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
|
||||
"meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
|
||||
|
||||
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
|
||||
@@ -4908,23 +4904,23 @@
|
||||
|
||||
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.8", "", {}, "sha512-ShZTo0hNhbTRrp7k6oUDSck4Xx4hhfSeLBp35jvGaw1QMZzWYr5v/oc0kEt0bfMdl+833flwKV7kFR3BnrULfg=="],
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.7", "", {}, "sha512-QSzAlzxaGrQYj3Nzgxd35WM6pmxmNk9lOCn0nsNLPidvc3H6wj/fV3Pl8/tVCYQP61luGuA0Ib0VwZc8ja119w=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZaDynM+pbrnLLlBAxH/CDGp9KN79OFrLcT1ejlWyo86V3SS9Gyqr4nmTuvTevByTTpr1VHReQel8Zbq0Pttu7Q=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fs7JYU0MKtpgsDBIvbT/wn1MwKP8C+bqcb6IjWnDtVcciFKzYDs/tPCBW6CtYg1wpHqm9horZDA1ytb/sAghxA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-BsMR8WqeCDAX8C9RYAO8TI4ttpuqKk2oYMb1+bCrOYi857SMfB4vWD4PWmMvj7mwFXGqrxly4W6CBQlD66A+fg=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Efl6es/IEXJf5groF78fc1PrTEwK9bOyi6fRjp1bsa/AWOHp8rTc3l0T6EeY5T0SWeYilisZZa9rax7nTgw4bw=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-xL+K2SW+/46j/KnKNf1gizM1bxwcEaE56eCEG9RPoYS/lfxHLuHcR9O2MlcPI90g/rIN2HXmHeuKRbXbnVy15g=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-IS71I8z2wlecRZRHXqWTtxVozZt1h9IH/2MX1P/WkCjQH98acFmCkw0jo/y70mrZ4lw6tRGTNkSBMun+5P5P+w=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-bzXlCUXkjIRsMTb6rr7OsWEQmdO2rZgKijnMGBJzEpa9ROq95VJTF+rdyrLmHuc4fPsAGDn/C18V3E1YOi4ipQ=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-uJ4env9fZxIFM+aRBCjNeG91HHZ2cRXiF25xJqGmCC1Y5jT5oYAB/tIBCFRSj6NRzdt4pZAamCMRS41WzzVVUQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-snbPQ9th7eoYYMZpNGhEvX4EbqGjjkiXzTEm2F/oDadlZR2wI6egtfQHiZqeczL7XwG3M66hBJ1/U20wdTDtvA=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-/+efTL2TxoSAYCxdf+g6WvDKIJDoaETvF3I+lxpixhctfrIwpr2qnG0e6u2M55k1iYiiyFIOA63fpIsGnIMJZA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-5hmcaGazc3w6rg/gbQOrmuw0kKShf4Egs0JlUPop/ZiRDfZO5KlyrGY0hUD+2pLG7Yx3B/fTbj86cHKfQZDMBw=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Hb+agfULOjEhJAmq/r9OTVAo8KxFlnuCT7BEUbSuVP/Gxn883al6juSG7ZZfk7P4a+ZU6iVYyHKIFAKCarZmUA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-t6uwWC/UbQ8CQGyBVbEmpcJC41yAvz2eZZGec9EoO0ZyQ/fbvjOkqfPnf+WcjWifWNeuiWogjN1UBg7YK9IaVA=="],
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-gNd5Vq7k9xIEQj85JoavHGRsyvu1zKOjVoPUYtsZRxXw6kcBg84x2g8NeT/3SmwH1PiFU/sjQXtRzr2Kuc2j3A=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "x64" }, "sha512-93gNqVYwlr9bz6Pm5dQnqjpuOEQyvDre0+H39UqS7h2KuFh3sf2MejhAEr2uFEux4mb7TN0PlohaihO7YxJ3fw=="],
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5i+KinvNNmspXfgIvEZ1RFOKVhOgdtoSC7/xEUm55hEkcWFVHOtVVPvgV7WUeo1elNxbgxytzmyKbLaifP03g=="],
|
||||
|
||||
"gitbook-v2/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
"@codemirror/state": "6.4.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"@gitbook/api": "0.111.0"
|
||||
"@gitbook/api": "0.109.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.111.0",
|
||||
"@gitbook/api": "^0.109.0",
|
||||
"assert-never": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
getDataOrNull,
|
||||
throwIfDataError,
|
||||
} from '@v2/lib/data';
|
||||
import assertNever from 'assert-never';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { assert } from 'ts-essentials';
|
||||
import { GITBOOK_URL } from './env';
|
||||
@@ -239,91 +238,23 @@ export async function fetchSiteContextByIds(
|
||||
...(customizations.site?.title ? { title: customizations.site.title } : {}),
|
||||
};
|
||||
|
||||
// Parse the current siteSpace, siteSpaces, and sections based on the site structure type.
|
||||
const {
|
||||
siteSpaces,
|
||||
siteSpace,
|
||||
sections,
|
||||
}: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace; sections: SiteSections | null } = (() => {
|
||||
if (siteStructure.type === 'siteSpaces') {
|
||||
const siteSpaces = siteStructure.structure;
|
||||
const siteSpace = siteSpaces.find((siteSpace) => siteSpace.id === ids.siteSpace);
|
||||
const sections = ids.siteSection
|
||||
? parseSiteSectionsAndGroups(siteStructure, ids.siteSection)
|
||||
: null;
|
||||
|
||||
if (!siteSpace) {
|
||||
throw new Error(
|
||||
`Site space "${ids.siteSpace}" not found in structure type="siteSpaces"`
|
||||
);
|
||||
}
|
||||
const siteSpace = (
|
||||
siteStructure.type === 'siteSpaces' && siteStructure.structure
|
||||
? siteStructure.structure
|
||||
: sections?.current.siteSpaces
|
||||
)?.find((siteSpace) => siteSpace.id === ids.siteSpace);
|
||||
if (!siteSpace) {
|
||||
throw new Error('Site space not found');
|
||||
}
|
||||
|
||||
return { siteSpaces, siteSpace, sections: null };
|
||||
}
|
||||
|
||||
if (siteStructure.type === 'sections') {
|
||||
/**
|
||||
* Workaround for #inc-56-sites-are-crashing-with-application-error-a-client-side-exception-ha
|
||||
*
|
||||
* If a site structure is set up as sections, we should always be given a siteSection in the ids. However,
|
||||
* in the incident we noticed it is sometimes undefined - perhaps due to caching.
|
||||
*
|
||||
* This workaround will pick the default section in the site structure if no explicit is provided. It should
|
||||
* be eventually removed and replaced with an assert.
|
||||
*/
|
||||
const currentSectionId = (() => {
|
||||
if (ids.siteSection) {
|
||||
return ids.siteSection;
|
||||
}
|
||||
|
||||
let defaultSectionId: string | undefined;
|
||||
|
||||
for (const sectionOrGroup of siteStructure.structure) {
|
||||
if (sectionOrGroup.object === 'site-section') {
|
||||
defaultSectionId = sectionOrGroup.id;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sectionOrGroup.object === 'site-section-group') {
|
||||
const defaultSection = sectionOrGroup.sections.find(
|
||||
(group) => group.default
|
||||
);
|
||||
|
||||
if (defaultSection) {
|
||||
defaultSectionId = defaultSection.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultSectionId) {
|
||||
throw new Error(
|
||||
`No default section found in structure type="sections" for site "${ids.site}"`
|
||||
);
|
||||
}
|
||||
|
||||
return defaultSectionId;
|
||||
})();
|
||||
|
||||
const sections = parseSiteSectionsAndGroups(siteStructure, currentSectionId);
|
||||
const currentSection = sections.current;
|
||||
const siteSpaces = currentSection.siteSpaces;
|
||||
const siteSpace = currentSection.siteSpaces.find(
|
||||
(siteSpace) => siteSpace.id === ids.siteSpace
|
||||
);
|
||||
|
||||
if (!siteSpace) {
|
||||
throw new Error(
|
||||
`Site space "${ids.siteSpace}" not found in structure type="sections" currentSection="${currentSection.id}"`
|
||||
);
|
||||
}
|
||||
|
||||
return { siteSpaces, siteSpace, sections };
|
||||
}
|
||||
|
||||
assertNever(
|
||||
siteStructure,
|
||||
// @ts-expect-error
|
||||
`cannot handle site structure of type ${siteStructure.type}`
|
||||
);
|
||||
})();
|
||||
const siteSpaces =
|
||||
siteStructure.type === 'siteSpaces'
|
||||
? siteStructure.structure
|
||||
: (sections?.current.siteSpaces ?? []);
|
||||
|
||||
const customization = (() => {
|
||||
if (ids.siteSpace) {
|
||||
@@ -446,7 +377,7 @@ export function checkIsRootSiteContext(context: GitBookSiteContext): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string): SiteSections {
|
||||
function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
|
||||
const sectionsAndGroups = getSiteStructureSections(structure, { ignoreGroups: false });
|
||||
const section = parseCurrentSection(structure, siteSectionId);
|
||||
assert(section, 'A section must be defined when there are multiple sections');
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
# gitbook
|
||||
|
||||
## 0.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ebc39e9]
|
||||
- Updated dependencies [b6b09d4]
|
||||
- @gitbook/react-openapi@1.2.1
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d67699a: Add OpenAPI Webhook block
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4b8a621: Show sections tabs only if there is at least two sections
|
||||
- 8ed1bda: Translate OpenAPI blocks
|
||||
- 7588cfe: Improve OpenAPIResponses examples and schemas
|
||||
- Updated dependencies [eeb977f]
|
||||
- Updated dependencies [3363a18]
|
||||
- Updated dependencies [d67699a]
|
||||
- Updated dependencies [8ed1bda]
|
||||
- Updated dependencies [7588cfe]
|
||||
- Updated dependencies [ad1dc0b]
|
||||
- @gitbook/react-openapi@1.2.0
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -10,18 +10,18 @@ const testCases: TestsCase[] = [
|
||||
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: 'Nexthink',
|
||||
// contentBaseURL: 'https://docs.nexthink.com',
|
||||
// tests: [
|
||||
// {
|
||||
// name: 'Home',
|
||||
// url: '/',
|
||||
// screenshot: { waitForTOCScrolling: false },
|
||||
// run: waitForCookiesDialog,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: 'Nexthink',
|
||||
contentBaseURL: 'https://docs.nexthink.com',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '/',
|
||||
screenshot: { waitForTOCScrolling: false },
|
||||
run: waitForCookiesDialog,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'asiksupport-stg.dto.kemkes.go.id',
|
||||
contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id',
|
||||
@@ -157,11 +157,11 @@ const testCases: TestsCase[] = [
|
||||
contentBaseURL: 'https://wiki.redmodding.org',
|
||||
tests: [{ name: 'Home', url: '/' }],
|
||||
},
|
||||
// {
|
||||
// name: 'docs.cherry-ai.com',
|
||||
// contentBaseURL: 'https://docs.cherry-ai.com',
|
||||
// tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
// },
|
||||
{
|
||||
name: 'docs.cherry-ai.com',
|
||||
contentBaseURL: 'https://docs.cherry-ai.com',
|
||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
},
|
||||
{
|
||||
name: 'docs.snyk.io',
|
||||
contentBaseURL: 'https://docs.snyk.io',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.11.1",
|
||||
"version": "0.10.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent -f ../../.env.local next dev",
|
||||
|
||||
@@ -25,7 +25,7 @@ import { IntegrationBlock } from './Integration';
|
||||
import { List } from './List';
|
||||
import { ListItem } from './ListItem';
|
||||
import { BlockMath } from './Math';
|
||||
import { OpenAPIOperation, OpenAPISchemas, OpenAPIWebhook } from './OpenAPI';
|
||||
import { OpenAPIOperation, OpenAPISchemas } from './OpenAPI';
|
||||
import { Paragraph } from './Paragraph';
|
||||
import { Quote } from './Quote';
|
||||
import { ReusableContent } from './ReusableContent';
|
||||
@@ -85,8 +85,6 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
|
||||
return <OpenAPIOperation {...props} block={block} />;
|
||||
case 'openapi-schemas':
|
||||
return <OpenAPISchemas {...props} block={block} />;
|
||||
case 'openapi-webhook':
|
||||
return <OpenAPIWebhook {...props} block={block} />;
|
||||
case 'embed':
|
||||
return <Embed {...props} block={block} />;
|
||||
case 'blockquote':
|
||||
@@ -161,7 +159,6 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
|
||||
case 'swagger':
|
||||
case 'openapi-operation':
|
||||
case 'openapi-schemas':
|
||||
case 'openapi-webhook':
|
||||
case 'math':
|
||||
case 'divider':
|
||||
case 'content-ref':
|
||||
|
||||
@@ -51,7 +51,7 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
|
||||
return (
|
||||
<BaseOpenAPIOperation
|
||||
data={data}
|
||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
||||
context={getOpenAPIContext({ props, specUrl })}
|
||||
className="openapi-block"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
|
||||
<BaseOpenAPISchemas
|
||||
schemas={data.schemas}
|
||||
grouped={block.data.grouped}
|
||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
||||
context={getOpenAPIContext({ props, specUrl })}
|
||||
className="openapi-block"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
|
||||
|
||||
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
|
||||
import { getOpenAPIContext } from './context';
|
||||
|
||||
/**
|
||||
* Render an openapi block or an openapi-webhook block.
|
||||
*/
|
||||
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
|
||||
const { style } = props;
|
||||
return (
|
||||
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||
<OpenAPIWebhookBody {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
|
||||
const { block, context } = props;
|
||||
|
||||
if (!context.contentContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
|
||||
block,
|
||||
context: context.contentContext,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="hidden">
|
||||
<p>
|
||||
Error with {specUrl}: {error.message}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || !specUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseOpenAPIWebhook
|
||||
data={data}
|
||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
||||
className="openapi-block"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { JSONDocument } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { type OpenAPIContextInput, checkIsValidLocale } from '@gitbook/react-openapi';
|
||||
import type { OpenAPIContext } from '@gitbook/react-openapi';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -11,31 +11,17 @@ import { Heading } from '../Heading';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import { DEFAULT_LOCALE, getCustomizationLocale } from '@/intl/server';
|
||||
import type {
|
||||
AnyOpenAPIOperationsBlock,
|
||||
OpenAPISchemasBlock,
|
||||
OpenAPIWebhookBlock,
|
||||
} from '@/lib/openapi/types';
|
||||
import type { GitBookAnyContext } from '@v2/lib/context';
|
||||
import type { AnyOpenAPIOperationsBlock, OpenAPISchemasBlock } from '@/lib/openapi/types';
|
||||
|
||||
/**
|
||||
* Get the OpenAPI context to render a block.
|
||||
*/
|
||||
export function getOpenAPIContext(args: {
|
||||
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
|
||||
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>;
|
||||
specUrl: string;
|
||||
context: GitBookAnyContext | undefined;
|
||||
}): OpenAPIContextInput {
|
||||
const { props, specUrl, context } = args;
|
||||
}): OpenAPIContext {
|
||||
const { props, specUrl } = args;
|
||||
const { block } = props;
|
||||
|
||||
const customization = context && 'customization' in context ? context.customization : null;
|
||||
const customizationLocale = customization
|
||||
? getCustomizationLocale(customization)
|
||||
: DEFAULT_LOCALE;
|
||||
const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE;
|
||||
|
||||
return {
|
||||
specUrl,
|
||||
icons: {
|
||||
@@ -83,6 +69,5 @@ export function getOpenAPIContext(args: {
|
||||
defaultInteractiveOpened: props.context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
locale,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './OpenAPIOperation';
|
||||
export * from './OpenAPISchemas';
|
||||
export * from './OpenAPIWebhook';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* Layout Components */
|
||||
.openapi-operation,
|
||||
.openapi-schemas,
|
||||
.openapi-webhook {
|
||||
.openapi-schemas {
|
||||
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
|
||||
}
|
||||
|
||||
@@ -229,19 +228,19 @@
|
||||
}
|
||||
|
||||
.openapi-schema-required {
|
||||
@apply text-warning-subtle text-[0.813rem] lowercase;
|
||||
@apply text-warning-subtle text-[0.813rem];
|
||||
}
|
||||
|
||||
.openapi-schema-optional {
|
||||
@apply text-info-subtle text-[0.813rem] lowercase;
|
||||
@apply text-info-subtle text-[0.813rem];
|
||||
}
|
||||
|
||||
.openapi-schema-readonly {
|
||||
@apply text-primary-subtle/9 text-[0.813rem] lowercase;
|
||||
@apply text-primary-subtle/9 text-[0.813rem];
|
||||
}
|
||||
|
||||
.openapi-schema-writeonly {
|
||||
@apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase;
|
||||
@apply text-success dark:text-success-subtle/9 text-[0.813rem];
|
||||
}
|
||||
|
||||
.openapi-schema-type {
|
||||
|
||||
@@ -10,18 +10,22 @@ export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
|
||||
'ref'
|
||||
>;
|
||||
|
||||
/**
|
||||
* Button with a dropdown.
|
||||
*/
|
||||
export function Dropdown<E extends HTMLElement>(props: {
|
||||
export type DropdownProps<E extends HTMLElement = HTMLElement> = {
|
||||
/** Content of the button */
|
||||
button: (buttonProps: DropdownButtonProps<E>) => React.ReactNode;
|
||||
/** Content of the dropdown */
|
||||
children: React.ReactNode;
|
||||
/** Custom styles */
|
||||
className?: ClassValue;
|
||||
}) {
|
||||
const { button, children, className } = props;
|
||||
/** Whether to use fixed positioning (for fixed navbars) */
|
||||
useFixedPosition?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Button with a dropdown.
|
||||
*/
|
||||
export function Dropdown<E extends HTMLElement = HTMLElement>(props: DropdownProps<E>) {
|
||||
const { button, children, className, useFixedPosition = false } = props;
|
||||
const dropdownId = useId();
|
||||
|
||||
return (
|
||||
@@ -53,7 +57,7 @@ export function Dropdown<E extends HTMLElement>(props: {
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="fixed z-50 w-52">
|
||||
<div className={tcls(useFixedPosition ? 'fixed' : 'absolute', 'z-50 w-52')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'mt-2',
|
||||
@@ -92,6 +96,7 @@ export function DropdownChevron() {
|
||||
'ms-1',
|
||||
'transition-all',
|
||||
'group-hover/dropdown:opacity-11',
|
||||
'group-hover/dropdown:rotate-180',
|
||||
'group-focus-within/dropdown:rotate-180'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -212,6 +212,7 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={siteSpaces}
|
||||
className="w-full grow py-1"
|
||||
useFixedPosition={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,15 +3,17 @@ import type { SiteSpace } from '@gitbook/api';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { Dropdown, DropdownChevron, DropdownMenu } from './Dropdown';
|
||||
import { Dropdown, DropdownChevron, DropdownMenu, type DropdownProps } from './Dropdown';
|
||||
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
|
||||
|
||||
export function SpacesDropdown(props: {
|
||||
context: GitBookSiteContext;
|
||||
siteSpace: SiteSpace;
|
||||
siteSpaces: SiteSpace[];
|
||||
className?: string;
|
||||
}) {
|
||||
export function SpacesDropdown(
|
||||
props: {
|
||||
context: GitBookSiteContext;
|
||||
siteSpace: SiteSpace;
|
||||
siteSpaces: SiteSpace[];
|
||||
className?: string;
|
||||
} & Partial<DropdownProps>
|
||||
) {
|
||||
const { context, siteSpace, siteSpaces, className } = props;
|
||||
const { linker } = context;
|
||||
|
||||
@@ -67,6 +69,7 @@ export function SpacesDropdown(props: {
|
||||
<DropdownChevron />
|
||||
</div>
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<DropdownMenu>
|
||||
{siteSpaces.map((otherSiteSpace, index) => (
|
||||
|
||||
@@ -75,13 +75,13 @@ export function SiteSectionListItem(props: {
|
||||
const isMounted = useIsMounted();
|
||||
React.useEffect(() => {}, [isMounted]); // This updates the useScrollToActiveTOCItem hook once we're mounted, so we can actually scroll to the this item
|
||||
|
||||
const anchorRef = React.createRef<HTMLAnchorElement>();
|
||||
useScrollToActiveTOCItem({ anchorRef, isActive });
|
||||
const linkRef = React.createRef<HTMLAnchorElement>();
|
||||
useScrollToActiveTOCItem({ linkRef, isActive });
|
||||
|
||||
return (
|
||||
<Link
|
||||
ref={anchorRef}
|
||||
href={section.url}
|
||||
ref={linkRef}
|
||||
aria-current={isActive && 'page'}
|
||||
className={tcls(
|
||||
'group/section-link flex flex-row items-center gap-3 rounded-md straight-corners:rounded-none px-3 py-2 transition-all hover:bg-tint-hover hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-tint',
|
||||
@@ -121,15 +121,18 @@ export function SiteSectionGroupItem(props: {
|
||||
|
||||
const hasDescendants = group.sections.length > 0;
|
||||
const isActiveGroup = group.sections.some((section) => section.id === currentSection.id);
|
||||
const shouldOpen = hasDescendants && isActiveGroup;
|
||||
const [isOpen, setIsOpen] = React.useState(shouldOpen);
|
||||
const [isVisible, setIsVisible] = React.useState(isActiveGroup);
|
||||
|
||||
// Update the visibility of the children if the group becomes active.
|
||||
// Update the visibility of the children, if we are navigating to a descendant.
|
||||
React.useEffect(() => {
|
||||
if (shouldOpen) {
|
||||
setIsOpen(shouldOpen);
|
||||
if (!hasDescendants) {
|
||||
return;
|
||||
}
|
||||
}, [shouldOpen]);
|
||||
|
||||
setIsVisible((prev) => prev || isActiveGroup);
|
||||
}, [isActiveGroup, hasDescendants]);
|
||||
|
||||
const { show, hide, scope } = useToggleAnimation({ hasDescendants, isVisible });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -138,7 +141,7 @@ export function SiteSectionGroupItem(props: {
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsOpen((prev) => !prev);
|
||||
setIsVisible((prev) => !prev);
|
||||
}}
|
||||
className={`group/section-link flex w-full flex-row items-center gap-3 rounded-md straight-corners:rounded-none px-3 py-2 text-left transition-all hover:bg-tint-hover hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-tint ${
|
||||
isActiveGroup
|
||||
@@ -181,7 +184,7 @@ export function SiteSectionGroupItem(props: {
|
||||
'after:h-7',
|
||||
'hover:bg-tint-active',
|
||||
'hover:text-current',
|
||||
isActiveGroup && 'hover:bg-tint-hover'
|
||||
isActiveGroup ? ['hover:bg-tint-hover'] : []
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
@@ -198,13 +201,17 @@ export function SiteSectionGroupItem(props: {
|
||||
'group-hover:opacity-11',
|
||||
'contrast-more:opacity-11',
|
||||
|
||||
isOpen ? 'rotate-90' : 'rotate-0'
|
||||
isVisible ? ['rotate-90'] : ['rotate-0']
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
{hasDescendants ? (
|
||||
<Descendants isVisible={isOpen}>
|
||||
<motion.div
|
||||
ref={scope}
|
||||
className={tcls(isVisible ? null : '[&_ul>li]:opacity-1')}
|
||||
initial={isVisible ? show : hide}
|
||||
>
|
||||
{group.sections.map((section) => (
|
||||
<SiteSectionListItem
|
||||
section={section}
|
||||
@@ -213,25 +220,8 @@ export function SiteSectionGroupItem(props: {
|
||||
className="pl-5"
|
||||
/>
|
||||
))}
|
||||
</Descendants>
|
||||
</motion.div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Descendants(props: {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { isVisible, children } = props;
|
||||
const { show, hide, scope } = useToggleAnimation(isVisible);
|
||||
return (
|
||||
<motion.div
|
||||
ref={scope}
|
||||
className={isVisible ? undefined : '[&_ul>li]:opacity-1'}
|
||||
initial={isVisible ? show : hide}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function PageDocumentItem(props: {
|
||||
const href = context.linker.toPathForPage({ pages: rootPages, page });
|
||||
|
||||
return (
|
||||
<li className="flex flex-col">
|
||||
<li className={tcls('flex', 'flex-col')}>
|
||||
<ToggleableLinkItem
|
||||
href={href}
|
||||
pathname={getPagePath(rootPages, page)}
|
||||
@@ -52,7 +52,7 @@ export async function PageDocumentItem(props: {
|
||||
}
|
||||
>
|
||||
{page.emoji || page.icon ? (
|
||||
<span className="flex items-center gap-3">
|
||||
<span className={tcls('flex', 'gap-3', 'items-center')}>
|
||||
<TOCPageIcon page={page} />
|
||||
{page.title}
|
||||
</span>
|
||||
|
||||
@@ -15,13 +15,27 @@ export function PageGroupItem(props: {
|
||||
const { rootPages, page, context } = props;
|
||||
|
||||
return (
|
||||
<li className="group/page-group-item flex flex-col">
|
||||
<li className={tcls('flex', 'flex-col', 'group/page-group-item')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'-top-5 group-first/page-group-item:-mt-5 sticky z-[1] flex items-center gap-3 px-3 pt-6',
|
||||
'font-semibold text-xs uppercase tracking-wide',
|
||||
'flex',
|
||||
'items-center',
|
||||
|
||||
'gap-3',
|
||||
'px-3',
|
||||
'z-[1]',
|
||||
'sticky',
|
||||
'-top-5',
|
||||
'pt-6',
|
||||
'group-first/page-group-item:-mt-5',
|
||||
'pb-3', // Add extra padding to make the header fade a bit nicer
|
||||
'-mb-1.5', // Then pull the page items a bit closer, effective bottom padding is 1.5 units / 6px.
|
||||
|
||||
'text-xs',
|
||||
'tracking-wide',
|
||||
'font-semibold',
|
||||
'uppercase',
|
||||
|
||||
'[mask-image:linear-gradient(rgba(0,0,0,1)_70%,rgba(0,0,0,0))]', // Fade out effect of fixed page items. We want the fade to start past the header, this is a good approximation.
|
||||
'bg-tint-base',
|
||||
'sidebar-filled:bg-tint-subtle',
|
||||
|
||||
@@ -55,9 +55,9 @@ export async function PageLinkItem(props: { page: RevisionPageLink; context: Git
|
||||
'shrink-0',
|
||||
'text-current',
|
||||
'transition-colors',
|
||||
'[&>path]:transition-opacity',
|
||||
'[&>path]:opacity-[0.4]',
|
||||
'group-hover:[&>path]:opacity-11'
|
||||
'[&>path]:transition-[opacity]',
|
||||
'[&>path]:[opacity:0.40]',
|
||||
'group-hover:[&>path]:[opacity:1]'
|
||||
)}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { RevisionPage } from '@gitbook/api';
|
||||
import { type RevisionPage, RevisionPageType } from '@gitbook/api';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import assertNever from 'assert-never';
|
||||
import { PageDocumentItem } from './PageDocumentItem';
|
||||
import { PageGroupItem } from './PageGroupItem';
|
||||
import { PageLinkItem } from './PageLinkItem';
|
||||
@@ -17,45 +16,41 @@ export function PagesList(props: {
|
||||
const { rootPages, pages, context, style } = props;
|
||||
|
||||
return (
|
||||
<ul className={tcls('flex flex-col gap-y-0.5', style)}>
|
||||
<ul className={tcls('flex', 'flex-col', 'gap-y-0.5', style)}>
|
||||
{pages.map((page) => {
|
||||
if (page.type === 'computed') {
|
||||
if (page.type === RevisionPageType.Computed) {
|
||||
throw new Error(
|
||||
'Unexpected computed page, it should have been computed in the API'
|
||||
);
|
||||
}
|
||||
|
||||
if (page.type === RevisionPageType.Link) {
|
||||
return <PageLinkItem key={page.id} page={page} context={context} />;
|
||||
}
|
||||
|
||||
if (page.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (page.type) {
|
||||
case 'document':
|
||||
return (
|
||||
<PageDocumentItem
|
||||
key={page.id}
|
||||
rootPages={rootPages}
|
||||
page={page}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'link':
|
||||
return <PageLinkItem key={page.id} page={page} context={context} />;
|
||||
|
||||
case 'group':
|
||||
return (
|
||||
<PageGroupItem
|
||||
key={page.id}
|
||||
rootPages={rootPages}
|
||||
page={page}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
assertNever(page);
|
||||
if (page.type === RevisionPageType.Group) {
|
||||
return (
|
||||
<PageGroupItem
|
||||
key={page.id}
|
||||
rootPages={rootPages}
|
||||
page={page}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageDocumentItem
|
||||
key={page.id}
|
||||
rootPages={rootPages}
|
||||
page={page}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
|
||||
@@ -1,64 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
} from 'react';
|
||||
import { assert } from 'ts-essentials';
|
||||
import React from 'react';
|
||||
|
||||
interface TOCScrollContainerContextType {
|
||||
onContainerMount: (listener: (element: HTMLDivElement) => void) => () => void;
|
||||
}
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
const TOCScrollContainerContext = React.createContext<TOCScrollContainerContextType | null>(null);
|
||||
const TOCScrollContainerRefContext = React.createContext<React.RefObject<HTMLDivElement> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
function useTOCScrollContainerContext() {
|
||||
const ctx = React.useContext(TOCScrollContainerContext);
|
||||
assert(ctx);
|
||||
function useTOCScrollContainerRefContext() {
|
||||
const ctx = React.useContext(TOCScrollContainerRefContext);
|
||||
if (!ctx) {
|
||||
throw new Error('Context `TOCScrollContainerRefContext` must be used within Provider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table of contents scroll container.
|
||||
*/
|
||||
export function TOCScrollContainer(props: ComponentPropsWithoutRef<'div'>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const listeners = useRef<((element: HTMLDivElement) => void)[]>([]);
|
||||
const onContainerMount: TOCScrollContainerContextType['onContainerMount'] = useCallback(
|
||||
(listener) => {
|
||||
if (ref.current) {
|
||||
listener(ref.current);
|
||||
return () => {};
|
||||
}
|
||||
listeners.current.push(listener);
|
||||
return () => {
|
||||
listeners.current = listeners.current.filter((l) => l !== listener);
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
const value: TOCScrollContainerContextType = useMemo(
|
||||
() => ({ onContainerMount }),
|
||||
[onContainerMount]
|
||||
);
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
listeners.current.forEach((listener) => listener(element));
|
||||
return () => {
|
||||
listeners.current = [];
|
||||
};
|
||||
}, []);
|
||||
export function TOCScrollContainer(props: {
|
||||
children: React.ReactNode;
|
||||
className?: ClassValue;
|
||||
style?: React.CSSProperties;
|
||||
}) {
|
||||
const { children, className, style } = props;
|
||||
const scrollContainerRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
<TOCScrollContainerContext.Provider value={value}>
|
||||
<div ref={ref} data-testid="toc-scroll-container" {...props} />
|
||||
</TOCScrollContainerContext.Provider>
|
||||
<TOCScrollContainerRefContext.Provider value={scrollContainerRef}>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
data-testid="toc-scroll-container"
|
||||
className={tcls(className)}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</TOCScrollContainerRefContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,31 +42,39 @@ export function TOCScrollContainer(props: ComponentPropsWithoutRef<'div'>) {
|
||||
const TOC_ITEM_OFFSET = 100;
|
||||
|
||||
/**
|
||||
* Scrolls the table of contents container to the page item when it's initially active.
|
||||
* Scrolls the table of contents container to the page item when it becomes active
|
||||
*/
|
||||
export function useScrollToActiveTOCItem(props: {
|
||||
anchorRef: React.RefObject<HTMLAnchorElement>;
|
||||
isActive: boolean;
|
||||
linkRef: React.RefObject<HTMLAnchorElement>;
|
||||
}) {
|
||||
const { isActive, anchorRef } = props;
|
||||
const isInitialActiveRef = useRef(isActive);
|
||||
const { onContainerMount } = useTOCScrollContainerContext();
|
||||
useEffect(() => {
|
||||
const anchor = anchorRef.current;
|
||||
if (isInitialActiveRef.current && anchor) {
|
||||
return onContainerMount((container) => {
|
||||
if (isOutOfView(anchor, container)) {
|
||||
container.scrollTo({ top: anchor.offsetTop - TOC_ITEM_OFFSET });
|
||||
}
|
||||
});
|
||||
const { isActive, linkRef } = props;
|
||||
const scrollContainerRef = useTOCScrollContainerRefContext();
|
||||
const isScrolled = React.useRef(false);
|
||||
React.useLayoutEffect(() => {
|
||||
if (!isActive) {
|
||||
isScrolled.current = false;
|
||||
return;
|
||||
}
|
||||
}, [onContainerMount, anchorRef]);
|
||||
if (isScrolled.current) {
|
||||
return;
|
||||
}
|
||||
const tocItem = linkRef.current;
|
||||
const tocContainer = scrollContainerRef.current;
|
||||
if (!tocItem || !tocContainer || !isOutOfView(tocItem, tocContainer)) {
|
||||
return;
|
||||
}
|
||||
tocContainer?.scrollTo({
|
||||
top: tocItem.offsetTop - TOC_ITEM_OFFSET,
|
||||
});
|
||||
isScrolled.current = true;
|
||||
}, [isActive, linkRef, scrollContainerRef]);
|
||||
}
|
||||
|
||||
function isOutOfView(element: HTMLElement, container: HTMLElement) {
|
||||
const tocItemTop = element.offsetTop;
|
||||
const containerTop = container.scrollTop;
|
||||
const containerBottom = containerTop + container.clientHeight;
|
||||
function isOutOfView(tocItem: HTMLElement, tocContainer: HTMLElement) {
|
||||
const tocItemTop = tocItem.offsetTop;
|
||||
const containerTop = tocContainer.scrollTop;
|
||||
const containerBottom = containerTop + tocContainer.clientHeight;
|
||||
return (
|
||||
tocItemTop < containerTop + TOC_ITEM_OFFSET ||
|
||||
tocItemTop > containerBottom - TOC_ITEM_OFFSET
|
||||
|
||||
@@ -66,7 +66,12 @@ export function TableOfContents(props: {
|
||||
<div // The actual sidebar, either shown with a filled bg or transparent.
|
||||
className={tcls(
|
||||
'lg:-ms-5',
|
||||
'relative flex flex-grow flex-col overflow-hidden',
|
||||
'overflow-hidden',
|
||||
'relative',
|
||||
|
||||
'flex',
|
||||
'flex-col',
|
||||
'flex-grow',
|
||||
|
||||
'sidebar-filled:bg-tint-subtle',
|
||||
'theme-muted:bg-tint-subtle',
|
||||
@@ -79,12 +84,18 @@ export function TableOfContents(props: {
|
||||
'straight-corners:rounded-none'
|
||||
)}
|
||||
>
|
||||
{innerHeader && <div className="px-5 *:my-4">{innerHeader}</div>}
|
||||
{innerHeader && <div className={tcls('px-5 *:my-4')}>{innerHeader}</div>}
|
||||
<TOCScrollContainer // The scrollview inside the sidebar
|
||||
className={tcls(
|
||||
'flex flex-grow flex-col p-2',
|
||||
'flex',
|
||||
'flex-grow',
|
||||
'flex-col',
|
||||
|
||||
'p-2',
|
||||
customization.trademark.enabled && 'lg:pb-20',
|
||||
'lg:gutter-stable overflow-y-auto',
|
||||
|
||||
'overflow-y-auto',
|
||||
'lg:gutter-stable',
|
||||
'[&::-webkit-scrollbar]:bg-transparent',
|
||||
'[&::-webkit-scrollbar-thumb]:bg-transparent',
|
||||
'group-hover:[&::-webkit-scrollbar]:bg-tint-subtle',
|
||||
@@ -96,7 +107,11 @@ export function TableOfContents(props: {
|
||||
rootPages={pages}
|
||||
pages={pages}
|
||||
context={context}
|
||||
style="page-no-toc:hidden border-tint-subtle sidebar-list-line:border-l"
|
||||
style={tcls(
|
||||
'page-no-toc:hidden',
|
||||
'sidebar-list-line:border-l',
|
||||
'border-tint-subtle'
|
||||
)}
|
||||
/>
|
||||
{customization.trademark.enabled ? (
|
||||
<Trademark
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { motion } from 'framer-motion';
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { useCurrentPagePath } from '../hooks';
|
||||
import { Link, type LinkInsightsProps, type LinkProps } from '../primitives';
|
||||
import { useCurrentPagePath, useToggleAnimation } from '../hooks';
|
||||
import { Link, type LinkInsightsProps } from '../primitives';
|
||||
import { useScrollToActiveTOCItem } from './TOCScroller';
|
||||
|
||||
/**
|
||||
@@ -24,190 +24,128 @@ export function ToggleableLinkItem(
|
||||
const { href, children, descendants, pathname, insights } = props;
|
||||
|
||||
const currentPagePath = useCurrentPagePath();
|
||||
|
||||
const isActive = currentPagePath === pathname;
|
||||
const hasDescendants = !!descendants;
|
||||
const hasActiveDescendant =
|
||||
hasDescendants && (isActive || currentPagePath.startsWith(`${pathname}/`));
|
||||
|
||||
if (!descendants) {
|
||||
return (
|
||||
<LinkItem href={href} insights={insights} isActive={isActive}>
|
||||
{children}
|
||||
</LinkItem>
|
||||
);
|
||||
}
|
||||
const [isVisible, setIsVisible] = React.useState(hasActiveDescendant);
|
||||
|
||||
return (
|
||||
<DescendantsRenderer
|
||||
descendants={descendants}
|
||||
defaultIsOpen={isActive || currentPagePath.startsWith(`${pathname}/`)}
|
||||
>
|
||||
{({ descendants, toggler }) => (
|
||||
<>
|
||||
<LinkItem href={href} insights={insights} isActive={isActive}>
|
||||
{children}
|
||||
{toggler}
|
||||
</LinkItem>
|
||||
{descendants}
|
||||
</>
|
||||
)}
|
||||
</DescendantsRenderer>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkItem(
|
||||
props: Pick<LinkProps, 'href' | 'insights' | 'children'> & {
|
||||
isActive: boolean;
|
||||
}
|
||||
) {
|
||||
const { isActive, href, insights, children } = props;
|
||||
const anchorRef = useRef<HTMLAnchorElement>(null);
|
||||
useScrollToActiveTOCItem({ anchorRef, isActive });
|
||||
return (
|
||||
<Link
|
||||
ref={anchorRef}
|
||||
href={href}
|
||||
insights={insights}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
className={tcls(
|
||||
'group/toclink relative transition-colors',
|
||||
'flex flex-row justify-between',
|
||||
'rounded-md straight-corners:rounded-none p-1.5 pl-3',
|
||||
'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong',
|
||||
'hover:contrast-more:text-tint-strong hover:contrast-more:ring-1 hover:contrast-more:ring-tint-12',
|
||||
'before:contents[] before:-left-px before:absolute before:inset-y-0',
|
||||
'sidebar-list-line:rounded-l-none sidebar-list-line:before:w-px sidebar-list-default:[&+div_a]:rounded-l-none [&+div_a]:pl-5 sidebar-list-default:[&+div_a]:before:w-px',
|
||||
|
||||
isActive && [
|
||||
'font-semibold',
|
||||
'sidebar-list-line:before:w-0.5',
|
||||
|
||||
'before:bg-primary-solid',
|
||||
'text-primary-subtle',
|
||||
'contrast-more:text-primary',
|
||||
|
||||
'sidebar-list-pill:bg-primary',
|
||||
'[html.sidebar-list-pill.theme-muted_&]:bg-primary-hover',
|
||||
'[html.sidebar-list-pill.theme-bold.tint_&]:bg-primary-hover',
|
||||
'[html.sidebar-filled.sidebar-list-pill.theme-muted_&]:bg-primary',
|
||||
'[html.sidebar-filled.sidebar-list-pill.theme-bold.tint_&]:bg-primary',
|
||||
|
||||
'hover:bg-primary-hover',
|
||||
'hover:text-primary',
|
||||
'hover:before:bg-primary-solid-hover',
|
||||
'sidebar-list-pill:hover:bg-primary-hover',
|
||||
|
||||
'contrast-more:text-primary',
|
||||
'contrast-more:hover:text-primary-strong',
|
||||
'contrast-more:bg-primary',
|
||||
'contrast-more:ring-1',
|
||||
'contrast-more:ring-primary',
|
||||
'contrast-more:hover:ring-primary-hover',
|
||||
]
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function DescendantsRenderer(props: {
|
||||
defaultIsOpen: boolean;
|
||||
descendants: React.ReactNode;
|
||||
children: (renderProps: {
|
||||
descendants: React.ReactNode;
|
||||
toggler: React.ReactNode;
|
||||
}) => React.ReactNode;
|
||||
}) {
|
||||
const { defaultIsOpen, children, descendants } = props;
|
||||
const [isOpen, setIsOpen] = React.useState(defaultIsOpen);
|
||||
|
||||
// Update the visibility of the children if one of the descendants becomes active.
|
||||
// Update the visibility of the children, if we are navigating to a descendant.
|
||||
React.useEffect(() => {
|
||||
if (defaultIsOpen) {
|
||||
setIsOpen(defaultIsOpen);
|
||||
if (!hasDescendants) {
|
||||
return;
|
||||
}
|
||||
}, [defaultIsOpen]);
|
||||
|
||||
return children({
|
||||
toggler: (
|
||||
<Toggler
|
||||
isLinkActive={isOpen}
|
||||
isOpen={isOpen}
|
||||
onToggle={() => {
|
||||
setIsOpen((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
descendants: <Descendants isVisible={isOpen}>{descendants}</Descendants>,
|
||||
});
|
||||
}
|
||||
setIsVisible((prev) => prev || hasActiveDescendant);
|
||||
}, [hasActiveDescendant, hasDescendants]);
|
||||
|
||||
const { show, hide, scope } = useToggleAnimation({ hasDescendants, isVisible });
|
||||
|
||||
const linkRef = React.createRef<HTMLAnchorElement>();
|
||||
useScrollToActiveTOCItem({ linkRef, isActive });
|
||||
|
||||
function Toggler(props: {
|
||||
isLinkActive: boolean;
|
||||
isOpen: boolean;
|
||||
onToggle: () => void;
|
||||
}) {
|
||||
const { isLinkActive, isOpen, onToggle } = props;
|
||||
return (
|
||||
<span
|
||||
className={tcls(
|
||||
'group',
|
||||
'relative',
|
||||
'rounded-full',
|
||||
'straight-corners:rounded-sm',
|
||||
'w-5',
|
||||
'h-5',
|
||||
'after:grid-area-1-1',
|
||||
'after:absolute',
|
||||
'after:-top-1',
|
||||
'after:grid',
|
||||
'after:-left-1',
|
||||
'after:w-7',
|
||||
'after:h-7',
|
||||
'hover:bg-tint-active',
|
||||
'hover:text-current',
|
||||
isLinkActive && 'hover:bg-tint-hover'
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onToggle();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
<>
|
||||
<Link
|
||||
ref={linkRef}
|
||||
href={href}
|
||||
insights={insights}
|
||||
{...(isActive ? { 'aria-current': 'page' } : {})}
|
||||
className={tcls(
|
||||
'm-1 grid size-3 flex-shrink-0 text-current opacity-6 transition',
|
||||
'group-hover:opacity-11 contrast-more:opacity-11',
|
||||
isOpen ? 'rotate-90' : 'rotate-0'
|
||||
'group/toclink relative transition-colors',
|
||||
'flex flex-row justify-between',
|
||||
'rounded-md straight-corners:rounded-none p-1.5 pl-3',
|
||||
'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong',
|
||||
'hover:contrast-more:text-tint-strong hover:contrast-more:ring-1 hover:contrast-more:ring-tint-12',
|
||||
'before:contents[] before:-left-px before:absolute before:inset-y-0',
|
||||
'sidebar-list-line:rounded-l-none sidebar-list-line:before:w-px sidebar-list-default:[&+div_a]:rounded-l-none [&+div_a]:pl-5 sidebar-list-default:[&+div_a]:before:w-px',
|
||||
|
||||
isActive && [
|
||||
'font-semibold',
|
||||
'sidebar-list-line:before:w-0.5',
|
||||
|
||||
'before:bg-primary-solid',
|
||||
'text-primary-subtle',
|
||||
'contrast-more:text-primary',
|
||||
|
||||
'sidebar-list-pill:bg-primary',
|
||||
'[html.sidebar-list-pill.theme-muted_&]:bg-primary-hover',
|
||||
'[html.sidebar-list-pill.theme-bold.tint_&]:bg-primary-hover',
|
||||
'[html.sidebar-filled.sidebar-list-pill.theme-muted_&]:bg-primary',
|
||||
'[html.sidebar-filled.sidebar-list-pill.theme-bold.tint_&]:bg-primary',
|
||||
|
||||
'hover:bg-primary-hover',
|
||||
'hover:text-primary',
|
||||
'hover:before:bg-primary-solid-hover',
|
||||
'sidebar-list-pill:hover:bg-primary-hover',
|
||||
|
||||
'contrast-more:text-primary',
|
||||
'contrast-more:hover:text-primary-strong',
|
||||
'contrast-more:bg-primary',
|
||||
'contrast-more:ring-1',
|
||||
'contrast-more:ring-primary',
|
||||
'contrast-more:hover:ring-primary-hover',
|
||||
]
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const show = {
|
||||
opacity: 1,
|
||||
height: 'auto',
|
||||
};
|
||||
const hide = {
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
transitionEnd: {
|
||||
display: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
function Descendants(props: {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { isVisible, children } = props;
|
||||
return (
|
||||
<motion.div
|
||||
className="overflow-hidden"
|
||||
animate={isVisible ? show : hide}
|
||||
initial={isVisible ? show : hide}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
>
|
||||
{children}
|
||||
{hasDescendants ? (
|
||||
<span
|
||||
className={tcls(
|
||||
'group',
|
||||
'relative',
|
||||
'rounded-full',
|
||||
'straight-corners:rounded-sm',
|
||||
'w-5',
|
||||
'h-5',
|
||||
'after:grid-area-1-1',
|
||||
'after:absolute',
|
||||
'after:-top-1',
|
||||
'after:grid',
|
||||
'after:-left-1',
|
||||
'after:w-7',
|
||||
'after:h-7',
|
||||
'hover:bg-tint-active',
|
||||
'hover:text-current',
|
||||
isActive ? ['hover:bg-tint-hover'] : []
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsVisible((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className={tcls(
|
||||
'grid',
|
||||
'flex-shrink-0',
|
||||
'size-3',
|
||||
'm-1',
|
||||
'transition-[opacity]',
|
||||
'text-current',
|
||||
'transition-transform',
|
||||
'opacity-6',
|
||||
'group-hover:opacity-11',
|
||||
'contrast-more:opacity-11',
|
||||
|
||||
isVisible ? ['rotate-90'] : ['rotate-0']
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</Link>
|
||||
{hasDescendants ? (
|
||||
<motion.div
|
||||
ref={scope}
|
||||
className={tcls(isVisible ? null : '[&_ul>li]:opacity-1')}
|
||||
initial={isVisible ? show : hide}
|
||||
>
|
||||
{descendants}
|
||||
</motion.div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useParams, useSelectedLayoutSegment } from 'next/navigation';
|
||||
|
||||
import { removeLeadingSlash } from '@/lib/paths';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
/**
|
||||
* Return the page of the current page being rendered.
|
||||
@@ -15,11 +14,9 @@ export function useCurrentPagePath() {
|
||||
// For V1, we use the selected layout segment.
|
||||
const rawActiveSegment = useSelectedLayoutSegment() ?? '';
|
||||
|
||||
return useMemo(() => {
|
||||
if (typeof params.pagePath === 'string') {
|
||||
return removeLeadingSlash(decodeURIComponent(params.pagePath));
|
||||
}
|
||||
if (typeof params.pagePath === 'string') {
|
||||
return removeLeadingSlash(decodeURIComponent(params.pagePath));
|
||||
}
|
||||
|
||||
return decodeURIComponent(rawActiveSegment);
|
||||
}, [params.pagePath, rawActiveSegment]);
|
||||
return decodeURIComponent(rawActiveSegment);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { stagger, useAnimate } from 'framer-motion';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { useIsMounted } from '.';
|
||||
|
||||
const show = {
|
||||
opacity: 1,
|
||||
@@ -19,35 +19,44 @@ const hide = {
|
||||
|
||||
const staggerMenuItems = stagger(0.02, { ease: (p) => p ** 2 });
|
||||
|
||||
export function useToggleAnimation(isVisible: boolean) {
|
||||
export function useToggleAnimation({
|
||||
hasDescendants,
|
||||
isVisible,
|
||||
}: {
|
||||
hasDescendants: boolean;
|
||||
isVisible: boolean;
|
||||
}) {
|
||||
const isMounted = useIsMounted();
|
||||
const [scope, animate] = useAnimate();
|
||||
const previousIsVisibleRef = React.useRef<boolean | undefined>(undefined);
|
||||
useLayoutEffect(() => {
|
||||
previousIsVisibleRef.current = isVisible;
|
||||
});
|
||||
const previousIsVisible = previousIsVisibleRef.current;
|
||||
|
||||
// Animate the visibility of the children
|
||||
// only after the initial state.
|
||||
React.useEffect(() => {
|
||||
if (previousIsVisible === undefined || previousIsVisible === isVisible) {
|
||||
if (!isMounted || !hasDescendants) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
animate(scope.current, isVisible ? show : hide, { duration: 0.1 });
|
||||
animate(scope.current, isVisible ? show : hide, {
|
||||
duration: 0.1,
|
||||
});
|
||||
|
||||
const selector = '& > ul > li';
|
||||
if (isVisible) {
|
||||
animate(selector, { opacity: 1 }, { delay: staggerMenuItems });
|
||||
} else {
|
||||
if (isVisible)
|
||||
animate(
|
||||
selector,
|
||||
{ opacity: 1 },
|
||||
{
|
||||
delay: staggerMenuItems,
|
||||
}
|
||||
);
|
||||
else {
|
||||
animate(selector, { opacity: 0 });
|
||||
}
|
||||
} catch (error) {
|
||||
// The selector can crash in some browsers, we ignore it as the animation is not critical.
|
||||
console.error(error);
|
||||
}
|
||||
}, [previousIsVisible, isVisible, animate, scope]);
|
||||
}, [isVisible, isMounted, hasDescendants, animate, scope]);
|
||||
|
||||
return { show, hide, scope };
|
||||
}
|
||||
|
||||
@@ -4,26 +4,17 @@ import { type TranslationLanguage, languages } from './translations';
|
||||
|
||||
export * from './translate';
|
||||
|
||||
export const DEFAULT_LOCALE = 'en';
|
||||
|
||||
/**
|
||||
* Get the locale of the customization.
|
||||
*/
|
||||
export function getCustomizationLocale(customization: SiteCustomizationSettings): string {
|
||||
return customization.internationalization.locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the translation context for a space to use in the server components.
|
||||
*/
|
||||
export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage {
|
||||
const fallback = languages[DEFAULT_LOCALE];
|
||||
const fallback = languages.en;
|
||||
|
||||
const locale = getCustomizationLocale(customization);
|
||||
const { locale } = customization.internationalization;
|
||||
|
||||
let language = fallback;
|
||||
// @ts-ignore
|
||||
if (locale !== DEFAULT_LOCALE && languages[locale]) {
|
||||
if (locale !== 'en' && languages[locale]) {
|
||||
// @ts-ignore
|
||||
language = languages[locale];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cac
|
||||
import type {
|
||||
AnyOpenAPIOperationsBlock,
|
||||
OpenAPISchemasBlock,
|
||||
OpenAPIWebhookBlock,
|
||||
ResolveOpenAPIBlockArgs,
|
||||
} from '@/lib/openapi/types';
|
||||
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
|
||||
@@ -17,7 +16,7 @@ import { isV2 } from '../v2';
|
||||
import { enrichFilesystem } from './enrich';
|
||||
import type { FetchOpenAPIFilesystemResult } from './types';
|
||||
|
||||
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock;
|
||||
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock;
|
||||
|
||||
/**
|
||||
* Fetch OpenAPI block.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { fetchOpenAPIFilesystem } from '@/lib/openapi/fetch';
|
||||
import { OpenAPIParseError } from '@gitbook/openapi-parser';
|
||||
import { type OpenAPIWebhookData, resolveOpenAPIWebhook } from '@gitbook/react-openapi';
|
||||
import type {
|
||||
OpenAPIWebhookBlock,
|
||||
ResolveOpenAPIBlockArgs,
|
||||
ResolveOpenAPIBlockResult,
|
||||
} from './types';
|
||||
|
||||
type ResolveOpenAPIWebhookBlockResult = ResolveOpenAPIBlockResult<OpenAPIWebhookData>;
|
||||
|
||||
const weakmap = new WeakMap<OpenAPIWebhookBlock, Promise<ResolveOpenAPIWebhookBlockResult>>();
|
||||
|
||||
/**
|
||||
* Cache the result of resolving an OpenAPI block.
|
||||
* It is important because the resolve is called in sections and in the block itself.
|
||||
*/
|
||||
export function resolveOpenAPIWebhookBlock(
|
||||
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
|
||||
): Promise<ResolveOpenAPIWebhookBlockResult> {
|
||||
if (weakmap.has(args.block)) {
|
||||
return weakmap.get(args.block)!;
|
||||
}
|
||||
|
||||
const result = baseResolveOpenAPIWebhookBlock(args);
|
||||
weakmap.set(args.block, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve OpenAPI webhook block.
|
||||
*/
|
||||
async function baseResolveOpenAPIWebhookBlock(
|
||||
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
|
||||
): Promise<ResolveOpenAPIWebhookBlockResult> {
|
||||
const { context, block } = args;
|
||||
if (!block.data.name || !block.data.method) {
|
||||
return { data: null, specUrl: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
|
||||
|
||||
if (!filesystem) {
|
||||
return { data: null, specUrl: null };
|
||||
}
|
||||
|
||||
const data = await resolveOpenAPIWebhook(filesystem, {
|
||||
name: block.data.name,
|
||||
method: block.data.method,
|
||||
});
|
||||
|
||||
return { data, specUrl };
|
||||
} catch (error) {
|
||||
if (error instanceof OpenAPIParseError) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type {
|
||||
DocumentBlockOpenAPI,
|
||||
DocumentBlockOpenAPIOperation,
|
||||
DocumentBlockOpenAPISchemas,
|
||||
DocumentBlockOpenAPIWebhook,
|
||||
} from '@gitbook/api';
|
||||
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
|
||||
import type { GitBookAnyContext } from '@v2/lib/context';
|
||||
@@ -17,11 +16,6 @@ export type AnyOpenAPIOperationsBlock = DocumentBlockOpenAPI | DocumentBlockOpen
|
||||
*/
|
||||
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
|
||||
|
||||
/**
|
||||
* Type for OpenAPI Webhook block
|
||||
*/
|
||||
export type OpenAPIWebhookBlock = DocumentBlockOpenAPIWebhook;
|
||||
|
||||
/**
|
||||
* Arguments for resolving OpenAPI block.
|
||||
*/
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
# @gitbook/react-openapi
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ebc39e9: Missing select icon
|
||||
- b6b09d4: Fix OpenAPI responses select placement and icon
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d67699a: Add OpenAPI Webhook block
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- eeb977f: Fix Python code example for JSON payload
|
||||
- 3363a18: Merge simple alternatives
|
||||
- 8ed1bda: Translate OpenAPI blocks
|
||||
- 7588cfe: Improve OpenAPIResponses examples and schemas
|
||||
- ad1dc0b: Bump scalar packages
|
||||
|
||||
## 1.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "1.2.1",
|
||||
"version": "1.1.10",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "^1.2.19",
|
||||
"@scalar/oas-utils": "^0.2.130",
|
||||
"@scalar/api-client-react": "^1.2.5",
|
||||
"@scalar/oas-utils": "^0.2.120",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
|
||||
@@ -102,7 +102,6 @@ export function InteractiveSection(props: {
|
||||
) : null}
|
||||
{header}
|
||||
</SectionHeaderContent>
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: we prevent default here */}
|
||||
<div
|
||||
className={clsx(
|
||||
'openapi-section-header-controls',
|
||||
|
||||
@@ -6,10 +6,9 @@ import {
|
||||
import { OpenAPICodeSampleBody } from './OpenAPICodeSampleSelector';
|
||||
import { ScalarApiButton } from './ScalarApiButton';
|
||||
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
|
||||
import { type OpenAPIContext, getOpenAPIClientContext } from './context';
|
||||
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||
import { getDefaultServerURL } from './util/server';
|
||||
import { checkIsReference } from './utils';
|
||||
|
||||
@@ -23,7 +22,7 @@ export function OpenAPICodeSample(props: {
|
||||
data: OpenAPIOperationData;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { data, context } = props;
|
||||
const { data } = props;
|
||||
|
||||
// If code samples are disabled at operation level, we don't display the code samples.
|
||||
if (data.operation['x-codeSamples'] === false) {
|
||||
@@ -44,14 +43,7 @@ export function OpenAPICodeSample(props: {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OpenAPICodeSampleBody
|
||||
context={getOpenAPIClientContext(context)}
|
||||
data={data}
|
||||
items={samples}
|
||||
selectIcon={context.icons.chevronDown}
|
||||
/>
|
||||
);
|
||||
return <OpenAPICodeSampleBody data={data} items={samples} />;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +146,6 @@ function generateCodeSamples(props: {
|
||||
method={data.method}
|
||||
path={data.path}
|
||||
renderers={renderers}
|
||||
blockKey={context.blockKey}
|
||||
/>
|
||||
),
|
||||
footer: (
|
||||
@@ -216,19 +207,11 @@ function OpenAPICodeSampleFooter(props: {
|
||||
path={data.path}
|
||||
renderers={renderers}
|
||||
selectIcon={context.icons.chevronDown}
|
||||
blockKey={context.blockKey}
|
||||
/>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{!hideTryItPanel && (
|
||||
<ScalarApiButton
|
||||
context={getOpenAPIClientContext(context)}
|
||||
method={method}
|
||||
path={path}
|
||||
specUrl={specUrl}
|
||||
/>
|
||||
)}
|
||||
{!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,18 @@
|
||||
import clsx from 'clsx';
|
||||
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||
import { createStateKey } from './utils';
|
||||
|
||||
export function OpenAPIMediaTypeExamplesSelector(props: {
|
||||
method: string;
|
||||
path: string;
|
||||
renderers: MediaTypeRenderer[];
|
||||
selectIcon?: React.ReactNode;
|
||||
blockKey?: string;
|
||||
}) {
|
||||
const { method, path, renderers, selectIcon, blockKey } = props;
|
||||
const { method, path, renderers, selectIcon } = props;
|
||||
if (!renderers[0]) {
|
||||
throw new Error('No renderers provided');
|
||||
}
|
||||
const stateKey = createStateKey('request-body-media-type', blockKey);
|
||||
const stateKey = `media-type-${method}-${path}`;
|
||||
const state = useSelectState(stateKey, renderers[0].mediaType);
|
||||
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
|
||||
|
||||
@@ -104,17 +102,13 @@ export function OpenAPIMediaTypeExamplesBody(props: {
|
||||
method: string;
|
||||
path: string;
|
||||
renderers: MediaTypeRenderer[];
|
||||
blockKey?: string;
|
||||
}) {
|
||||
const { renderers, method, path, blockKey } = props;
|
||||
const { renderers, method, path } = props;
|
||||
if (!renderers[0]) {
|
||||
throw new Error('No renderers provided');
|
||||
}
|
||||
|
||||
const mediaTypeState = useSelectState(
|
||||
createStateKey('request-body-media-type', blockKey),
|
||||
renderers[0].mediaType
|
||||
);
|
||||
const mediaTypeState = useSelectState(`media-type-${method}-${path}`, renderers[0].mediaType);
|
||||
const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
|
||||
if (selected.examples.length === 0) {
|
||||
return selected.element;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useStore } from 'zustand';
|
||||
import { OpenAPIPath } from './OpenAPIPath';
|
||||
import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect';
|
||||
import { StaticSection } from './StaticSection';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { getOrCreateStoreByKey } from './getOrCreateStoreByKey';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
|
||||
@@ -27,13 +26,12 @@ function OpenAPICodeSampleHeader(props: {
|
||||
items: CodeSampleItem[];
|
||||
data: OpenAPIOperationData;
|
||||
selectIcon?: React.ReactNode;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { data, items, selectIcon, context } = props;
|
||||
const { data, items, selectIcon } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<OpenAPIPath context={context} canCopy={false} withServer={false} data={data} />
|
||||
<OpenAPIPath canCopy={false} withServer={false} data={data} />
|
||||
{items.length > 1 ? (
|
||||
<OpenAPISelect
|
||||
icon={selectIcon}
|
||||
@@ -58,9 +56,8 @@ export function OpenAPICodeSampleBody(props: {
|
||||
items: CodeSampleItem[];
|
||||
data: OpenAPIOperationData;
|
||||
selectIcon?: React.ReactNode;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { items, data, selectIcon, context } = props;
|
||||
const { items, data, selectIcon } = props;
|
||||
if (!items[0]) {
|
||||
throw new Error('No items provided');
|
||||
}
|
||||
@@ -75,14 +72,7 @@ export function OpenAPICodeSampleBody(props: {
|
||||
|
||||
return (
|
||||
<StaticSection
|
||||
header={
|
||||
<OpenAPICodeSampleHeader
|
||||
context={context}
|
||||
selectIcon={selectIcon}
|
||||
data={data}
|
||||
items={items}
|
||||
/>
|
||||
}
|
||||
header={<OpenAPICodeSampleHeader selectIcon={selectIcon} data={data} items={items} />}
|
||||
className="openapi-codesample"
|
||||
>
|
||||
<div id={selected.key as string} className="openapi-codesample-panel">
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button, type ButtonProps, Tooltip, TooltipTrigger } from 'react-aria-components';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
|
||||
export function OpenAPICopyButton(
|
||||
props: ButtonProps & {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
context: OpenAPIClientContext;
|
||||
label?: string;
|
||||
/**
|
||||
* Whether to show a tooltip.
|
||||
@@ -18,7 +15,7 @@ export function OpenAPICopyButton(
|
||||
withTooltip?: boolean;
|
||||
}
|
||||
) {
|
||||
const { value, label, children, onPress, className, context, withTooltip = true } = props;
|
||||
const { value, label, children, onPress, className, withTooltip = true } = props;
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -63,9 +60,7 @@ export function OpenAPICopyButton(
|
||||
offset={4}
|
||||
className="openapi-tooltip"
|
||||
>
|
||||
{copied
|
||||
? t(context.translation, 'copied')
|
||||
: label || t(context.translation, 'copy_to_clipboard')}
|
||||
{copied ? 'Copied' : label || 'Copy to clipboard'}
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
);
|
||||
|
||||
@@ -132,7 +132,6 @@ function DisclosureItem(props: {
|
||||
state.expand();
|
||||
}}
|
||||
items={group.tabs}
|
||||
placement="bottom end"
|
||||
>
|
||||
{group.tabs.map((tab) => (
|
||||
<OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import type { OpenAPIContext, OpenAPIUniversalContext } from './context';
|
||||
import { generateSchemaExample } from './generateSchemaExample';
|
||||
import { json2xml } from './json2xml';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { t } from './translate';
|
||||
import type { OpenAPIContext } from './types';
|
||||
import { checkIsReference } from './utils';
|
||||
|
||||
/**
|
||||
* Display an example.
|
||||
@@ -16,7 +17,7 @@ export function OpenAPIExample(props: {
|
||||
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
||||
|
||||
if (code === null) {
|
||||
return <OpenAPIEmptyExample context={context} />;
|
||||
return <OpenAPIEmptyExample />;
|
||||
}
|
||||
|
||||
return context.renderCodeBlock({ code, syntax });
|
||||
@@ -43,13 +44,86 @@ function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean
|
||||
/**
|
||||
* Empty response example.
|
||||
*/
|
||||
export function OpenAPIEmptyExample(props: {
|
||||
context: OpenAPIUniversalContext;
|
||||
}) {
|
||||
const { context } = props;
|
||||
export function OpenAPIEmptyExample() {
|
||||
return (
|
||||
<pre className="openapi-example-empty">
|
||||
<p>{t(context.translation, 'no_content')}</p>
|
||||
<p>No Content</p>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an example from a reference object.
|
||||
*/
|
||||
export function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
||||
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples from a media type object.
|
||||
*/
|
||||
export function getExamplesFromMediaTypeObject(args: {
|
||||
mediaType: string;
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
||||
const { mediaTypeObject, mediaType } = args;
|
||||
if (mediaTypeObject.examples) {
|
||||
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
||||
return {
|
||||
key,
|
||||
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (mediaTypeObject.example) {
|
||||
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
||||
}
|
||||
|
||||
if (mediaTypeObject.schema) {
|
||||
if (mediaType === 'application/xml') {
|
||||
// @TODO normally we should use the name of the schema but we don't have it
|
||||
// fix it when we got the reference name
|
||||
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: {
|
||||
value: {
|
||||
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
||||
xml: mediaType === 'application/xml',
|
||||
mode: 'read',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: {
|
||||
value: generateSchemaExample(mediaTypeObject.schema, {
|
||||
mode: 'read',
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get example from a schema object.
|
||||
*/
|
||||
export function getExampleFromSchema(args: {
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
}): OpenAPIV3.ExampleObject {
|
||||
const { schema } = args;
|
||||
|
||||
if (schema.example) {
|
||||
return { value: schema.example };
|
||||
}
|
||||
|
||||
return { value: generateSchemaExample(schema, { mode: 'read' }) };
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type {
|
||||
OpenAPICustomOperationProperties,
|
||||
OpenAPIStability,
|
||||
OpenAPIV3,
|
||||
} from '@gitbook/openapi-parser';
|
||||
import { Markdown } from './Markdown';
|
||||
import { OpenAPICodeSample } from './OpenAPICodeSample';
|
||||
import { OpenAPIPath } from './OpenAPIPath';
|
||||
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
||||
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
|
||||
import { OpenAPISummary } from './common/OpenAPISummary';
|
||||
import { type OpenAPIContextInput, resolveOpenAPIContext } from './context';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
import { OpenAPISpec } from './OpenAPISpec';
|
||||
import { getOpenAPIClientContext } from './context';
|
||||
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||
import { resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
* Display an interactive OpenAPI operation.
|
||||
@@ -12,24 +20,105 @@ import type { OpenAPIOperationData } from './types';
|
||||
export function OpenAPIOperation(props: {
|
||||
className?: string;
|
||||
data: OpenAPIOperationData;
|
||||
context: OpenAPIContextInput;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { className, data, context: contextInput } = props;
|
||||
const { className, data, context } = props;
|
||||
const { operation } = data;
|
||||
|
||||
const context = resolveOpenAPIContext(contextInput);
|
||||
const clientContext = getOpenAPIClientContext(context);
|
||||
|
||||
return (
|
||||
<div className={clsx('openapi-operation', className)}>
|
||||
<OpenAPISummary data={data} context={context} />
|
||||
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
|
||||
{(operation.deprecated || operation['x-stability']) && (
|
||||
<div className="openapi-summary-tags">
|
||||
{operation.deprecated && (
|
||||
<div className="openapi-deprecated">Deprecated</div>
|
||||
)}
|
||||
{operation['x-stability'] && (
|
||||
<OpenAPIOperationStability stability={operation['x-stability']} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{operation.summary
|
||||
? context.renderHeading({
|
||||
deprecated: operation.deprecated ?? false,
|
||||
stability: operation['x-stability'],
|
||||
title: operation.summary,
|
||||
})
|
||||
: null}
|
||||
<OpenAPIPath data={data} />
|
||||
</div>
|
||||
<div className="openapi-columns">
|
||||
<OpenAPIColumnSpec data={data} context={context} />
|
||||
<div className="openapi-column-spec">
|
||||
{operation['x-deprecated-sunset'] ? (
|
||||
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
|
||||
This operation is deprecated and will be sunset on{' '}
|
||||
<span className="openapi-deprecated-sunset-date">
|
||||
{operation['x-deprecated-sunset']}
|
||||
</span>
|
||||
{'.'}
|
||||
</div>
|
||||
) : null}
|
||||
<OpenAPIOperationDescription operation={operation} context={context} />
|
||||
<OpenAPISpec data={data} context={clientContext} />
|
||||
</div>
|
||||
<div className="openapi-column-preview">
|
||||
<div className="openapi-column-preview-body">
|
||||
<OpenAPICodeSample data={data} context={context} />
|
||||
<OpenAPIResponseExample data={data} context={context} />
|
||||
<OpenAPICodeSample {...props} />
|
||||
<OpenAPIResponseExample {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIOperationDescription(props: {
|
||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { operation } = props;
|
||||
if (operation['x-gitbook-description-document']) {
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
{props.context.renderDocument({
|
||||
document: operation['x-gitbook-description-document'],
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const description = resolveDescription(operation);
|
||||
if (!description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
<Markdown className="openapi-description" source={description} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const stabilityEnum = {
|
||||
experimental: 'Experimental',
|
||||
alpha: 'Alpha',
|
||||
beta: 'Beta',
|
||||
} as const;
|
||||
|
||||
function OpenAPIOperationStability(props: { stability: OpenAPIStability }) {
|
||||
const { stability } = props;
|
||||
|
||||
const foundStability = stabilityEnum[stability];
|
||||
|
||||
if (!foundStability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
|
||||
{foundStability}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { Markdown } from './Markdown';
|
||||
import type { OpenAPIContext } from './context';
|
||||
import { resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
* Display the description of an OpenAPI operation.
|
||||
*/
|
||||
export function OpenAPIOperationDescription(props: {
|
||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { operation } = props;
|
||||
if (operation['x-gitbook-description-document']) {
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
{props.context.renderDocument({
|
||||
document: operation['x-gitbook-description-document'],
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const description = resolveDescription(operation);
|
||||
if (!description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
<Markdown className="openapi-description" source={description} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { OpenAPIStability } from '@gitbook/openapi-parser';
|
||||
import type { OpenAPIContext } from './context';
|
||||
import { t } from './translate';
|
||||
|
||||
/**
|
||||
* Display the stability of an OpenAPI operation.
|
||||
*/
|
||||
export function OpenAPIOperationStability(props: {
|
||||
stability: OpenAPIStability;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { stability, context } = props;
|
||||
|
||||
const stabilityLabel = getStabilityLabel(stability, context);
|
||||
|
||||
if (!stabilityLabel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`openapi-stability openapi-stability-${stability}`}>{stabilityLabel}</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stability label for the given stability level.
|
||||
*/
|
||||
function getStabilityLabel(stability: OpenAPIStability, context: OpenAPIContext) {
|
||||
switch (stability) {
|
||||
case 'experimental':
|
||||
return t(context.translation, 'stability_experimental');
|
||||
case 'alpha':
|
||||
return t(context.translation, 'stability_alpha');
|
||||
case 'beta':
|
||||
return t(context.translation, 'stability_beta');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||
import { type OpenAPIUniversalContext, getOpenAPIClientContext } from './context';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
import { getDefaultServerURL } from './util/server';
|
||||
|
||||
@@ -8,7 +7,6 @@ import { getDefaultServerURL } from './util/server';
|
||||
*/
|
||||
export function OpenAPIPath(props: {
|
||||
data: OpenAPIOperationData;
|
||||
context: OpenAPIUniversalContext;
|
||||
/** Whether to show the server URL.
|
||||
* @default true
|
||||
*/
|
||||
@@ -19,7 +17,7 @@ export function OpenAPIPath(props: {
|
||||
*/
|
||||
canCopy?: boolean;
|
||||
}) {
|
||||
const { data, context, withServer = true, canCopy = true } = props;
|
||||
const { data, withServer = true, canCopy = true } = props;
|
||||
const { method, path, operation } = data;
|
||||
|
||||
const server = getDefaultServerURL(data.servers);
|
||||
@@ -43,7 +41,6 @@ export function OpenAPIPath(props: {
|
||||
className="openapi-path-title"
|
||||
data-deprecated={operation.deprecated}
|
||||
isDisabled={!canCopy}
|
||||
context={getOpenAPIClientContext(context)}
|
||||
>
|
||||
{element}
|
||||
</OpenAPICopyButton>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { InteractiveSection } from './InteractiveSection';
|
||||
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||
import { checkIsReference, createStateKey } from './utils';
|
||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
||||
import { checkIsReference } from './utils';
|
||||
|
||||
/**
|
||||
* Display an interactive request body.
|
||||
@@ -12,9 +10,10 @@ import { checkIsReference, createStateKey } from './utils';
|
||||
export function OpenAPIRequestBody(props: {
|
||||
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
|
||||
context: OpenAPIClientContext;
|
||||
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||
data: OpenAPIOperationData;
|
||||
}) {
|
||||
const { requestBody, context, data } = props;
|
||||
const { method, path } = data;
|
||||
|
||||
if (checkIsReference(requestBody)) {
|
||||
return null;
|
||||
@@ -22,9 +21,9 @@ export function OpenAPIRequestBody(props: {
|
||||
|
||||
return (
|
||||
<InteractiveSection
|
||||
header={t(context.translation, 'name' in data ? 'payload' : 'body')}
|
||||
header="Body"
|
||||
className="openapi-requestbody"
|
||||
stateKey={createStateKey('request-body-media-type', context.blockKey)}
|
||||
stateKey={`media-type-${method}-${path}`}
|
||||
selectIcon={context.icons.chevronDown}
|
||||
tabs={Object.entries(requestBody.content ?? {}).map(
|
||||
([contentType, mediaTypeObject]) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
import { parameterToProperty, resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { Markdown } from './Markdown';
|
||||
import { OpenAPIEmptyExample, OpenAPIExample } from './OpenAPIExample';
|
||||
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
|
||||
import {
|
||||
OpenAPIEmptyExample,
|
||||
OpenAPIExample,
|
||||
getExampleFromReference,
|
||||
getExamplesFromMediaTypeObject,
|
||||
} from './OpenAPIExample';
|
||||
import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
|
||||
import { type OpenAPIContext, getOpenAPIClientContext } from './context';
|
||||
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||
import { getExampleFromReference, getExamples } from './util/example';
|
||||
import { createStateKey, getStatusCodeDefaultLabel } from './utils';
|
||||
import { OpenAPIResponseMediaTypeContent } from './OpenAPIResponseMediaType';
|
||||
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||
import { getStatusCodeDefaultLabel } from './utils';
|
||||
import { checkIsReference, resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
* Display an example of the response content.
|
||||
*/
|
||||
export function OpenAPIResponseExample(props: {
|
||||
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||
data: OpenAPIOperationData;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { data, context } = props;
|
||||
@@ -46,7 +49,7 @@ export function OpenAPIResponseExample(props: {
|
||||
const label = description ? (
|
||||
<Markdown source={description} />
|
||||
) : (
|
||||
getStatusCodeDefaultLabel(key, context)
|
||||
getStatusCodeDefaultLabel(key)
|
||||
);
|
||||
|
||||
if (checkIsReference(responseObject)) {
|
||||
@@ -56,7 +59,7 @@ export function OpenAPIResponseExample(props: {
|
||||
statusCode: key,
|
||||
body: (
|
||||
<OpenAPIExample
|
||||
example={getExampleFromReference(responseObject, context)}
|
||||
example={getExampleFromReference(responseObject)}
|
||||
context={context}
|
||||
syntax="json"
|
||||
/>
|
||||
@@ -69,7 +72,7 @@ export function OpenAPIResponseExample(props: {
|
||||
key: key,
|
||||
label,
|
||||
statusCode: key,
|
||||
body: <OpenAPIEmptyExample context={context} />,
|
||||
body: <OpenAPIEmptyExample />,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,11 +127,45 @@ function OpenAPIResponse(props: {
|
||||
});
|
||||
|
||||
return (
|
||||
<OpenAPIMediaTypeContent
|
||||
<OpenAPIResponseMediaTypeContent
|
||||
selectIcon={context.icons.chevronDown}
|
||||
stateKey={createStateKey('response-media-types', context.blockKey)}
|
||||
blockKey={context.blockKey}
|
||||
items={tabs}
|
||||
context={getOpenAPIClientContext(context)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getExamples(props: {
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
mediaType: string;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { mediaTypeObject, mediaType } = props;
|
||||
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
||||
const syntax = getSyntaxFromMediaType(mediaType);
|
||||
|
||||
return examples.map((example) => {
|
||||
return {
|
||||
key: example.key,
|
||||
label: example.example.summary || example.key,
|
||||
body: (
|
||||
<OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the syntax from a media type.
|
||||
*/
|
||||
function getSyntaxFromMediaType(mediaType: string): string {
|
||||
if (mediaType.includes('json')) {
|
||||
return 'json';
|
||||
}
|
||||
|
||||
if (mediaType === 'application/xml') {
|
||||
return 'xml';
|
||||
}
|
||||
|
||||
return 'text';
|
||||
}
|
||||
|
||||
+48
-39
@@ -1,35 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import type { Key } from 'react-aria';
|
||||
import { OpenAPIEmptyExample } from './OpenAPIExample';
|
||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||
import { StaticSection } from './StaticSection';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { createStateKey } from './utils';
|
||||
|
||||
type OpenAPIMediaTypeItem = OpenAPISelectItem & {
|
||||
type OpenAPIResponseMediaTypeItem = OpenAPISelectItem & {
|
||||
body: React.ReactNode;
|
||||
examples?: OpenAPIMediaTypeItem[];
|
||||
examples?: OpenAPIResponseMediaTypeItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the state of the response examples select.
|
||||
*/
|
||||
export function useMediaTypesState(stateKey: string | undefined, initialKey: Key = 'default') {
|
||||
return useSelectState(stateKey, initialKey);
|
||||
export function useResponseMediaTypesState(
|
||||
blockKey: string | undefined,
|
||||
initialKey: Key = 'default'
|
||||
) {
|
||||
return useSelectState(getResponseMediaTypeStateKey(blockKey), initialKey);
|
||||
}
|
||||
|
||||
function useMediaTypeExamplesState(stateKey: string | undefined, initialKey: Key = 'default') {
|
||||
return useSelectState(stateKey, initialKey);
|
||||
function useResponseMediaTypeExamplesState(
|
||||
blockKey: string | undefined,
|
||||
initialKey: Key = 'default'
|
||||
) {
|
||||
return useSelectState(getResponseMediaTypeExamplesStateKey(blockKey), initialKey);
|
||||
}
|
||||
|
||||
export function OpenAPIMediaTypeContent(props: {
|
||||
items: OpenAPIMediaTypeItem[];
|
||||
export function OpenAPIResponseMediaTypeContent(props: {
|
||||
items: OpenAPIResponseMediaTypeItem[];
|
||||
blockKey?: string;
|
||||
selectIcon?: React.ReactNode;
|
||||
stateKey: string;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { stateKey, items, selectIcon, context } = props;
|
||||
const state = useMediaTypesState(stateKey, items[0]?.key);
|
||||
const { blockKey, items, selectIcon } = props;
|
||||
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
|
||||
|
||||
const examples = items.find((item) => item.key === state.key)?.examples ?? [];
|
||||
|
||||
@@ -41,33 +45,28 @@ export function OpenAPIMediaTypeContent(props: {
|
||||
<StaticSection
|
||||
footer={
|
||||
items.length > 1 || examples.length > 1 ? (
|
||||
<OpenAPIMediaTypeFooter
|
||||
<OpenAPIResponseMediaTypeFooter
|
||||
blockKey={blockKey}
|
||||
items={items}
|
||||
examples={examples}
|
||||
selectIcon={selectIcon}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
className="openapi-response-media-types-examples"
|
||||
>
|
||||
<OpenAPIMediaTypeBody
|
||||
context={context}
|
||||
stateKey={stateKey}
|
||||
items={items}
|
||||
examples={examples}
|
||||
/>
|
||||
<OpenAPIResponseMediaTypeBody blockKey={blockKey} items={items} examples={examples} />
|
||||
</StaticSection>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIMediaTypeFooter(props: {
|
||||
items: OpenAPIMediaTypeItem[];
|
||||
examples?: OpenAPIMediaTypeItem[];
|
||||
function OpenAPIResponseMediaTypeFooter(props: {
|
||||
items: OpenAPIResponseMediaTypeItem[];
|
||||
examples?: OpenAPIResponseMediaTypeItem[];
|
||||
blockKey?: string;
|
||||
selectIcon?: React.ReactNode;
|
||||
stateKey: string;
|
||||
}) {
|
||||
const { items, examples, stateKey, selectIcon } = props;
|
||||
const { items, examples, blockKey, selectIcon } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -75,7 +74,7 @@ function OpenAPIMediaTypeFooter(props: {
|
||||
<OpenAPISelect
|
||||
icon={selectIcon}
|
||||
items={items}
|
||||
stateKey={stateKey}
|
||||
stateKey={getResponseMediaTypeStateKey(blockKey)}
|
||||
placement="bottom start"
|
||||
>
|
||||
{items.map((item) => (
|
||||
@@ -90,7 +89,7 @@ function OpenAPIMediaTypeFooter(props: {
|
||||
<OpenAPISelect
|
||||
icon={selectIcon}
|
||||
items={examples}
|
||||
stateKey={`${stateKey}-examples`}
|
||||
stateKey={getResponseMediaTypeExamplesStateKey(blockKey)}
|
||||
placement="bottom start"
|
||||
>
|
||||
{examples.map((example) => (
|
||||
@@ -104,19 +103,18 @@ function OpenAPIMediaTypeFooter(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIMediaTypeBody(props: {
|
||||
items: OpenAPIMediaTypeItem[];
|
||||
examples?: OpenAPIMediaTypeItem[];
|
||||
stateKey: string;
|
||||
context: OpenAPIClientContext;
|
||||
function OpenAPIResponseMediaTypeBody(props: {
|
||||
items: OpenAPIResponseMediaTypeItem[];
|
||||
examples?: OpenAPIResponseMediaTypeItem[];
|
||||
blockKey?: string;
|
||||
}) {
|
||||
const { stateKey, items, examples, context } = props;
|
||||
const state = useMediaTypesState(stateKey, items[0]?.key);
|
||||
const { blockKey, items, examples } = props;
|
||||
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
|
||||
|
||||
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
|
||||
|
||||
const exampleState = useMediaTypeExamplesState(
|
||||
`${stateKey}-examples`,
|
||||
const exampleState = useResponseMediaTypeExamplesState(
|
||||
blockKey,
|
||||
selectedItem?.examples?.[0]?.key
|
||||
);
|
||||
|
||||
@@ -129,7 +127,7 @@ function OpenAPIMediaTypeBody(props: {
|
||||
examples.find((example) => example.key === exampleState.key) ?? examples[0];
|
||||
|
||||
if (!selectedExample) {
|
||||
return <OpenAPIEmptyExample context={context} />;
|
||||
return <OpenAPIEmptyExample />;
|
||||
}
|
||||
|
||||
return selectedExample.body;
|
||||
@@ -137,3 +135,14 @@ function OpenAPIMediaTypeBody(props: {
|
||||
|
||||
return selectedItem.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state key for the response media types.
|
||||
*/
|
||||
function getResponseMediaTypeStateKey(blockKey: string | undefined) {
|
||||
return createStateKey('response-media-types', blockKey);
|
||||
}
|
||||
|
||||
function getResponseMediaTypeExamplesStateKey(blockKey: string | undefined) {
|
||||
return createStateKey('response-media-types-examples', blockKey);
|
||||
}
|
||||
@@ -7,8 +7,7 @@ import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup';
|
||||
import { OpenAPIResponse } from './OpenAPIResponse';
|
||||
import { useResponseExamplesState } from './OpenAPIResponseExampleContent';
|
||||
import { StaticSection } from './StaticSection';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
import { createStateKey, getStatusCodeClassName, getStatusCodeDefaultLabel } from './utils';
|
||||
|
||||
/**
|
||||
@@ -77,7 +76,7 @@ export function OpenAPIResponses(props: {
|
||||
className="openapi-response-description"
|
||||
/>
|
||||
) : (
|
||||
getStatusCodeDefaultLabel(statusCode, context)
|
||||
getStatusCodeDefaultLabel(statusCode)
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
@@ -89,7 +88,7 @@ export function OpenAPIResponses(props: {
|
||||
const state = useResponseExamplesState(context.blockKey, groups[0]?.key);
|
||||
|
||||
return (
|
||||
<StaticSection header={t(context.translation, 'responses')} className="openapi-responses">
|
||||
<StaticSection header="Responses" className="openapi-responses">
|
||||
<OpenAPIDisclosureGroup
|
||||
icon={context.icons.chevronRight}
|
||||
expandedKeys={state.key ? new Set([state.key]) : new Set()}
|
||||
@@ -98,7 +97,6 @@ export function OpenAPIResponses(props: {
|
||||
state.setKey(key);
|
||||
}}
|
||||
groups={groups}
|
||||
selectIcon={context.icons.chevronDown}
|
||||
selectStateKey={createStateKey('response-media-types', context.blockKey)}
|
||||
/>
|
||||
</StaticSection>
|
||||
|
||||
@@ -35,86 +35,6 @@ describe('getSchemaAlternatives', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('merges string enum', () => {
|
||||
expect(
|
||||
getSchemaAlternatives({
|
||||
oneOf: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['a', 'b'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['c', 'd'],
|
||||
nullable: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['a', 'b', 'c', 'd'],
|
||||
nullable: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('merges objects with allOf', () => {
|
||||
expect(
|
||||
getSchemaAlternatives({
|
||||
allOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
map: {
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
externalId: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['map', 'externalId'],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
map: {
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['name', 'map', 'externalId'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not flatten oneOf and allOf', () => {
|
||||
expect(
|
||||
getSchemaAlternatives({
|
||||
|
||||
@@ -10,10 +10,9 @@ import { Markdown } from './Markdown';
|
||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { retrocycle } from './decycle';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { tString } from './translate';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
|
||||
|
||||
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
||||
@@ -41,7 +40,7 @@ function OpenAPISchemaProperty(props: {
|
||||
|
||||
return (
|
||||
<div id={id} className={clsx('openapi-schema', className)}>
|
||||
<OpenAPISchemaPresentation context={context} property={property} />
|
||||
<OpenAPISchemaPresentation property={property} />
|
||||
{(() => {
|
||||
const circularRefId = parentCircularRefs.get(schema);
|
||||
// Avoid recursing infinitely, and instead render a link to the parent schema
|
||||
@@ -57,9 +56,7 @@ function OpenAPISchemaProperty(props: {
|
||||
return (
|
||||
<OpenAPIDisclosure
|
||||
icon={context.icons.plus}
|
||||
label={(isExpanded) =>
|
||||
getDisclosureLabel({ schema, isExpanded, context })
|
||||
}
|
||||
label={(isExpanded) => getDisclosureLabel(schema, isExpanded)}
|
||||
>
|
||||
<OpenAPISchemaProperties
|
||||
properties={properties}
|
||||
@@ -216,7 +213,7 @@ function OpenAPISchemaAlternative(props: {
|
||||
) : null}
|
||||
<OpenAPIDisclosure
|
||||
icon={context.icons.plus}
|
||||
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
|
||||
label={(isExpanded) => getDisclosureLabel(schema, isExpanded)}
|
||||
>
|
||||
{properties?.length ? (
|
||||
<OpenAPISchemaProperties
|
||||
@@ -255,9 +252,8 @@ function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaO
|
||||
*/
|
||||
function OpenAPISchemaEnum(props: {
|
||||
schema: OpenAPIV3.SchemaObject & OpenAPICustomOperationProperties;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { schema, context } = props;
|
||||
const { schema } = props;
|
||||
|
||||
const enumValues = (() => {
|
||||
// Render x-gitbook-enum first, as it has a different format
|
||||
@@ -300,7 +296,6 @@ function OpenAPISchemaEnum(props: {
|
||||
value={item.value}
|
||||
label={item.description}
|
||||
withTooltip={!!item.description}
|
||||
context={context}
|
||||
>
|
||||
<code>{`${item.value}`}</code>
|
||||
</OpenAPICopyButton>
|
||||
@@ -313,13 +308,9 @@ function OpenAPISchemaEnum(props: {
|
||||
/**
|
||||
* Render the top row of a schema. e.g: name, type, and required status.
|
||||
*/
|
||||
function OpenAPISchemaPresentation(props: {
|
||||
property: OpenAPISchemaPropertyEntry;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry }) {
|
||||
const {
|
||||
property: { schema, propertyName, required },
|
||||
context,
|
||||
} = props;
|
||||
|
||||
const description = resolveDescription(schema);
|
||||
@@ -332,7 +323,6 @@ function OpenAPISchemaPresentation(props: {
|
||||
type={getSchemaTitle(schema)}
|
||||
propertyName={propertyName}
|
||||
required={required}
|
||||
context={context}
|
||||
/>
|
||||
{typeof schema['x-deprecated-sunset'] === 'string' ? (
|
||||
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
|
||||
@@ -365,7 +355,7 @@ function OpenAPISchemaPresentation(props: {
|
||||
Pattern: <code>{schema.pattern}</code>
|
||||
</span>
|
||||
) : null}
|
||||
<OpenAPISchemaEnum schema={schema} context={context} />
|
||||
<OpenAPISchemaEnum schema={schema} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -460,91 +450,7 @@ export function getSchemaAlternatives(
|
||||
}
|
||||
|
||||
const [type, schemas] = alternatives;
|
||||
return mergeAlternatives(
|
||||
type,
|
||||
flattenAlternatives(type, schemas, new Set(ancestors).add(schema))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge alternatives of the same type into a single schema.
|
||||
* - Merge string enums
|
||||
*/
|
||||
function mergeAlternatives(
|
||||
alternativeType: AlternativeType,
|
||||
schemasOrRefs: OpenAPIV3.SchemaObject[]
|
||||
): OpenAPIV3.SchemaObject[] | null {
|
||||
switch (alternativeType) {
|
||||
case 'oneOf': {
|
||||
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
|
||||
const latest = acc.at(-1);
|
||||
|
||||
if (
|
||||
latest &&
|
||||
latest.type === 'string' &&
|
||||
latest.enum &&
|
||||
schemaOrRef.type === 'string' &&
|
||||
schemaOrRef.enum
|
||||
) {
|
||||
latest.enum = Array.from(new Set([...latest.enum, ...schemaOrRef.enum]));
|
||||
latest.nullable = latest.nullable || schemaOrRef.nullable;
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push(schemaOrRef);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
case 'allOf': {
|
||||
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
|
||||
const latest = acc.at(-1);
|
||||
|
||||
if (
|
||||
latest &&
|
||||
latest.type === 'string' &&
|
||||
latest.enum &&
|
||||
schemaOrRef.type === 'string' &&
|
||||
schemaOrRef.enum
|
||||
) {
|
||||
const keys = Object.keys(schemaOrRef);
|
||||
if (keys.every((key) => ['type', 'enum', 'nullable'].includes(key))) {
|
||||
latest.enum = Array.from(new Set([...latest.enum, ...schemaOrRef.enum]));
|
||||
latest.nullable = latest.nullable || schemaOrRef.nullable;
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest && latest.type === 'object' && schemaOrRef.type === 'object') {
|
||||
const keys = Object.keys(schemaOrRef);
|
||||
if (
|
||||
keys.every((key) =>
|
||||
['type', 'properties', 'required', 'nullable'].includes(key)
|
||||
)
|
||||
) {
|
||||
latest.properties = {
|
||||
...latest.properties,
|
||||
...schemaOrRef.properties,
|
||||
};
|
||||
latest.required = Array.from(
|
||||
new Set([
|
||||
...(Array.isArray(latest.required) ? latest.required : []),
|
||||
...(Array.isArray(schemaOrRef.required)
|
||||
? schemaOrRef.required
|
||||
: []),
|
||||
])
|
||||
);
|
||||
latest.nullable = latest.nullable || schemaOrRef.nullable;
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
acc.push(schemaOrRef);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
default:
|
||||
return schemasOrRefs;
|
||||
}
|
||||
return flattenAlternatives(type, schemas, new Set(ancestors).add(schema));
|
||||
}
|
||||
|
||||
function flattenAlternatives(
|
||||
@@ -602,26 +508,21 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
||||
return type;
|
||||
}
|
||||
|
||||
function getDisclosureLabel(props: {
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
isExpanded: boolean;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { schema, isExpanded, context } = props;
|
||||
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject, isExpanded: boolean) {
|
||||
let label: string;
|
||||
if (schema.type === 'array' && !!schema.items) {
|
||||
if (schema.items.oneOf) {
|
||||
label = tString(context.translation, 'available_items').toLowerCase();
|
||||
label = 'available items';
|
||||
}
|
||||
// Fallback to "child attributes" for enums and objects
|
||||
else if (schema.items.enum || schema.items.type === 'object') {
|
||||
label = tString(context.translation, 'child_attributes').toLowerCase();
|
||||
label = 'child attributes';
|
||||
} else {
|
||||
label = schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
|
||||
}
|
||||
} else {
|
||||
label = schema.title || tString(context.translation, 'child_attributes').toLowerCase();
|
||||
label = schema.title || 'child attributes';
|
||||
}
|
||||
|
||||
return `${isExpanded ? tString(context.translation, 'hide') : tString(context.translation, 'show')} ${label}`;
|
||||
return `${isExpanded ? 'Hide' : 'Show'} ${label}`;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import type React from 'react';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t, tString } from './translate';
|
||||
|
||||
interface OpenAPISchemaNameProps {
|
||||
schema?: OpenAPIV3.SchemaObject;
|
||||
propertyName?: string | React.JSX.Element;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
context: OpenAPIClientContext;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,9 +13,9 @@ interface OpenAPISchemaNameProps {
|
||||
* It includes the property name, type, required and deprecated status.
|
||||
*/
|
||||
export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
||||
const { schema, type, propertyName, required, context } = props;
|
||||
const { schema, type, propertyName, required } = props;
|
||||
|
||||
const additionalItems = schema && getAdditionalItems(schema, context);
|
||||
const additionalItems = schema && getAdditionalItems(schema);
|
||||
|
||||
return (
|
||||
<span className="openapi-schema-name">
|
||||
@@ -33,45 +30,33 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
||||
<span className="openapi-schema-type">{additionalItems}</span>
|
||||
) : null}
|
||||
</span>
|
||||
{schema?.readOnly ? (
|
||||
<span className="openapi-schema-readonly">
|
||||
{t(context.translation, 'read_only')}
|
||||
</span>
|
||||
) : null}
|
||||
{schema?.readOnly ? <span className="openapi-schema-readonly">read-only</span> : null}
|
||||
{schema?.writeOnly ? (
|
||||
<span className="openapi-schema-writeonly">
|
||||
{t(context.translation, 'write_only')}
|
||||
</span>
|
||||
<span className="openapi-schema-writeonly">write-only</span>
|
||||
) : null}
|
||||
{required ? (
|
||||
<span className="openapi-schema-required">
|
||||
{t(context.translation, 'required')}
|
||||
</span>
|
||||
<span className="openapi-schema-required">required</span>
|
||||
) : (
|
||||
<span className="openapi-schema-optional">
|
||||
{t(context.translation, 'optional')}
|
||||
</span>
|
||||
<span className="openapi-schema-optional">optional</span>
|
||||
)}
|
||||
{schema?.deprecated ? (
|
||||
<span className="openapi-deprecated">{t(context.translation, 'deprecated')}</span>
|
||||
) : null}
|
||||
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function getAdditionalItems(schema: OpenAPIV3.SchemaObject, context: OpenAPIClientContext): string {
|
||||
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
|
||||
let additionalItems = '';
|
||||
|
||||
if (schema.minimum || schema.minLength || schema.minItems) {
|
||||
additionalItems += ` · ${tString(context.translation, 'min').toLowerCase()}: ${schema.minimum || schema.minLength || schema.minItems}`;
|
||||
additionalItems += ` · min: ${schema.minimum || schema.minLength || schema.minItems}`;
|
||||
}
|
||||
|
||||
if (schema.maximum || schema.maxLength || schema.maxItems) {
|
||||
additionalItems += ` · ${tString(context.translation, 'max').toLowerCase()}: ${schema.maximum || schema.maxLength || schema.maxItems}`;
|
||||
additionalItems += ` · max: ${schema.maximum || schema.maxLength || schema.maxItems}`;
|
||||
}
|
||||
|
||||
if (schema.nullable) {
|
||||
additionalItems = ` | ${tString(context.translation, 'nullable').toLowerCase()}`;
|
||||
additionalItems = ' | nullable';
|
||||
}
|
||||
|
||||
return additionalItems;
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
OpenAPISchemaPropertiesFromServer,
|
||||
type OpenAPISchemaPropertyEntry,
|
||||
} from './OpenAPISchema';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { decycle } from './decycle';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
|
||||
export function OpenAPISchemaProperties(props: {
|
||||
id?: string;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { InteractiveSection } from './InteractiveSection';
|
||||
import { Markdown } from './Markdown';
|
||||
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
|
||||
import type {
|
||||
OpenAPIClientContext,
|
||||
OpenAPIOperationData,
|
||||
OpenAPISecurityWithRequired,
|
||||
} from './types';
|
||||
import { createStateKey, resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
@@ -21,7 +23,7 @@ export function OpenAPISecurities(props: {
|
||||
|
||||
return (
|
||||
<InteractiveSection
|
||||
header={t(context.translation, 'authorizations')}
|
||||
header="Authorizations"
|
||||
stateKey={createStateKey('securities', context.blockKey)}
|
||||
toggeable
|
||||
defaultOpened={false}
|
||||
@@ -36,7 +38,7 @@ export function OpenAPISecurities(props: {
|
||||
body: (
|
||||
<div className="openapi-schema">
|
||||
<div className="openapi-schema-presentation">
|
||||
{getLabelForType(security, context)}
|
||||
{getLabelForType(security)}
|
||||
|
||||
{description ? (
|
||||
<Markdown
|
||||
@@ -53,12 +55,11 @@ export function OpenAPISecurities(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPIClientContext) {
|
||||
function getLabelForType(security: OpenAPISecurityWithRequired) {
|
||||
switch (security.type) {
|
||||
case 'apiKey':
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName={security.name ?? 'apiKey'}
|
||||
type="string"
|
||||
required={security.required}
|
||||
@@ -68,7 +69,6 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
||||
if (security.scheme === 'basic') {
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="Authorization"
|
||||
type="string"
|
||||
required={security.required}
|
||||
@@ -81,7 +81,6 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
||||
return (
|
||||
<>
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="Authorization"
|
||||
type="string"
|
||||
required={security.required}
|
||||
@@ -97,29 +96,11 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="HTTP"
|
||||
required={security.required}
|
||||
/>
|
||||
);
|
||||
return <OpenAPISchemaName propertyName="HTTP" required={security.required} />;
|
||||
case 'oauth2':
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="OAuth2"
|
||||
required={security.required}
|
||||
/>
|
||||
);
|
||||
return <OpenAPISchemaName propertyName="OAuth2" required={security.required} />;
|
||||
case 'openIdConnect':
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="OpenID Connect"
|
||||
required={security.required}
|
||||
/>
|
||||
);
|
||||
return <OpenAPISchemaName propertyName="OpenID Connect" required={security.required} />;
|
||||
default:
|
||||
// @ts-ignore
|
||||
return security.type;
|
||||
|
||||
@@ -5,23 +5,16 @@ import { OpenAPIResponses } from './OpenAPIResponses';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
|
||||
import { OpenAPISecurities } from './OpenAPISecurities';
|
||||
import { StaticSection } from './StaticSection';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { tString } from './translate';
|
||||
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
||||
import { parameterToProperty } from './utils';
|
||||
|
||||
export function OpenAPISpec(props: {
|
||||
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAPIClientContext }) {
|
||||
const { data, context } = props;
|
||||
|
||||
const { operation } = data;
|
||||
const { operation, securities } = data;
|
||||
|
||||
const parameters = operation.parameters ?? [];
|
||||
const parameterGroups = groupParameters(parameters, context);
|
||||
|
||||
const securities = 'securities' in data ? data.securities : [];
|
||||
const parameterGroups = groupParameters(parameters);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -63,10 +56,7 @@ export function OpenAPISpec(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function groupParameters(
|
||||
parameters: OpenAPI.Parameters,
|
||||
context: OpenAPIClientContext
|
||||
): Array<{
|
||||
function groupParameters(parameters: OpenAPI.Parameters): Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
parameters: OpenAPI.Parameters;
|
||||
@@ -83,7 +73,7 @@ function groupParameters(
|
||||
.filter((parameter) => parameter.in)
|
||||
.forEach((parameter) => {
|
||||
const key = parameter.in;
|
||||
const label = getParameterGroupName(parameter.in, context);
|
||||
const label = getParameterGroupName(parameter.in);
|
||||
const group = groups.find((group) => group.key === key);
|
||||
if (group) {
|
||||
group.parameters.push(parameter);
|
||||
@@ -101,14 +91,14 @@ function groupParameters(
|
||||
return groups;
|
||||
}
|
||||
|
||||
function getParameterGroupName(paramIn: string, context: OpenAPIClientContext): string {
|
||||
function getParameterGroupName(paramIn: string): string {
|
||||
switch (paramIn) {
|
||||
case 'path':
|
||||
return tString(context.translation, 'path_parameters');
|
||||
return 'Path parameters';
|
||||
case 'query':
|
||||
return tString(context.translation, 'query_parameters');
|
||||
return 'Query parameters';
|
||||
case 'header':
|
||||
return tString(context.translation, 'header_parameters');
|
||||
return 'Header parameters';
|
||||
default:
|
||||
return paramIn;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import { OpenAPIWebhookExample } from './OpenAPIWebhookExample';
|
||||
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
|
||||
import { OpenAPISummary } from './common/OpenAPISummary';
|
||||
import { type OpenAPIContextInput, resolveOpenAPIContext } from './context';
|
||||
import type { OpenAPIWebhookData } from './types';
|
||||
|
||||
/**
|
||||
* Display an interactive OpenAPI webhook.
|
||||
*/
|
||||
export function OpenAPIWebhook(props: {
|
||||
className?: string;
|
||||
data: OpenAPIWebhookData;
|
||||
context: OpenAPIContextInput;
|
||||
}) {
|
||||
const { className, data, context: contextInput } = props;
|
||||
|
||||
const context = resolveOpenAPIContext(contextInput);
|
||||
|
||||
return (
|
||||
<div className={clsx('openapi-webhook', className)}>
|
||||
<OpenAPISummary data={data} context={context} />
|
||||
<div className="openapi-columns">
|
||||
<OpenAPIColumnSpec data={data} context={context} />
|
||||
<div className="openapi-column-preview">
|
||||
<div className="openapi-column-preview-body">
|
||||
<OpenAPIWebhookExample data={data} context={context} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { OpenAPIEmptyExample } from './OpenAPIExample';
|
||||
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
|
||||
import { type OpenAPIContext, getOpenAPIClientContext } from './context';
|
||||
import type { OpenAPIWebhookData } from './types';
|
||||
import { getExamples } from './util/example';
|
||||
import { createStateKey } from './utils';
|
||||
|
||||
export function OpenAPIWebhookExample(props: {
|
||||
data: OpenAPIWebhookData;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { data, context } = props;
|
||||
const { operation } = data;
|
||||
|
||||
const items = (() => {
|
||||
if (!operation.requestBody) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(
|
||||
operation.requestBody.content as Record<string, OpenAPIV3.MediaTypeObject>
|
||||
).map(([key, value]) => {
|
||||
const schema = value.schema;
|
||||
|
||||
if (!schema) {
|
||||
return {
|
||||
key,
|
||||
label: key,
|
||||
body: <OpenAPIEmptyExample context={context} />,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
label: key,
|
||||
body: <></>,
|
||||
examples: getExamples({
|
||||
mediaTypeObject: value,
|
||||
mediaType: key,
|
||||
context,
|
||||
}),
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="openapi-panel">
|
||||
<h4 className="openapi-panel-heading">Payload</h4>
|
||||
<div className="openapi-panel-body">
|
||||
<OpenAPIMediaTypeContent
|
||||
selectIcon={context.icons.chevronDown}
|
||||
stateKey={createStateKey('request-body-media-type', context.blockKey)}
|
||||
items={items}
|
||||
context={getOpenAPIClientContext(context)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import { createPortal } from 'react-dom';
|
||||
|
||||
import type { OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||
import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
|
||||
/**
|
||||
* Button which launches the Scalar API Client
|
||||
@@ -16,9 +14,8 @@ export function ScalarApiButton(props: {
|
||||
method: OpenAPIV3_1.HttpMethods;
|
||||
path: string;
|
||||
specUrl: string;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { method, path, specUrl, context } = props;
|
||||
const { method, path, specUrl } = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const controllerRef = useRef<ScalarModalControllerRef>(null);
|
||||
return (
|
||||
@@ -30,7 +27,7 @@ export function ScalarApiButton(props: {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
{t(context.translation, 'test_it')}
|
||||
Test it
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" fill="currentColor">
|
||||
<path
|
||||
stroke="currentColor"
|
||||
@@ -62,7 +59,10 @@ function ScalarModal(props: {
|
||||
}) {
|
||||
const { method, path, specUrl, controllerRef } = props;
|
||||
return (
|
||||
<ApiClientModalProvider configuration={{ url: specUrl }} initialRequest={{ method, path }}>
|
||||
<ApiClientModalProvider
|
||||
configuration={{ spec: { url: specUrl } }}
|
||||
initialRequest={{ method, path }}
|
||||
>
|
||||
<ScalarModalController method={method} path={path} controllerRef={controllerRef} />
|
||||
</ApiClientModalProvider>
|
||||
);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { OpenAPISpec } from '../OpenAPISpec';
|
||||
import { type OpenAPIContext, getOpenAPIClientContext } from '../context';
|
||||
import { t } from '../translate';
|
||||
import type { OpenAPIOperationData, OpenAPIWebhookData } from '../types';
|
||||
import { OpenAPIOperationDescription } from './OpenAPIOperationDescription';
|
||||
|
||||
export function OpenAPIColumnSpec(props: {
|
||||
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { data, context } = props;
|
||||
const { operation } = data;
|
||||
|
||||
const clientContext = getOpenAPIClientContext(context);
|
||||
|
||||
return (
|
||||
<div className="openapi-column-spec">
|
||||
{operation['x-deprecated-sunset'] ? (
|
||||
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
|
||||
{t(context.translation, 'deprecated_and_sunset_on', [
|
||||
<span key="date" className="openapi-deprecated-sunset-date">
|
||||
{operation['x-deprecated-sunset']}
|
||||
</span>,
|
||||
])}
|
||||
</div>
|
||||
) : null}
|
||||
<OpenAPIOperationDescription operation={operation} context={context} />
|
||||
<OpenAPISpec data={data} context={clientContext} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { Markdown } from '../Markdown';
|
||||
import type { OpenAPIContext } from '../context';
|
||||
import { resolveDescription } from '../utils';
|
||||
|
||||
export function OpenAPIOperationDescription(props: {
|
||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { operation } = props;
|
||||
if (operation['x-gitbook-description-document']) {
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
{props.context.renderDocument({
|
||||
document: operation['x-gitbook-description-document'],
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const description = resolveDescription(operation);
|
||||
if (!description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
<Markdown className="openapi-description" source={description} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { OpenAPIStability as OpenAPIStabilityType } from '@gitbook/openapi-parser';
|
||||
|
||||
const stabilityEnum: Record<OpenAPIStabilityType, string> = {
|
||||
experimental: 'Experimental',
|
||||
alpha: 'Alpha',
|
||||
beta: 'Beta',
|
||||
} as const;
|
||||
|
||||
export function OpenAPIStability(props: { stability: OpenAPIStabilityType }) {
|
||||
const { stability } = props;
|
||||
|
||||
const foundStability = stabilityEnum[stability];
|
||||
|
||||
if (!foundStability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
|
||||
{foundStability}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { OpenAPIPath } from '../OpenAPIPath';
|
||||
import type { OpenAPIContext } from '../context';
|
||||
import type { OpenAPIOperationData, OpenAPIWebhookData } from '../types';
|
||||
import { OpenAPIStability } from './OpenAPIStability';
|
||||
|
||||
export function OpenAPISummary(props: {
|
||||
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { data, context } = props;
|
||||
const { operation } = data;
|
||||
|
||||
const title = (() => {
|
||||
if (operation.summary) {
|
||||
return operation.summary;
|
||||
}
|
||||
|
||||
if ('name' in data) {
|
||||
return data.name;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
|
||||
{(operation.deprecated || operation['x-stability']) && (
|
||||
<div className="openapi-summary-tags">
|
||||
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
||||
{operation['x-stability'] && (
|
||||
<OpenAPIStability stability={operation['x-stability']} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{title
|
||||
? context.renderHeading({
|
||||
deprecated: operation.deprecated ?? false,
|
||||
stability: operation['x-stability'],
|
||||
title,
|
||||
})
|
||||
: null}
|
||||
{'path' in data ? <OpenAPIPath data={data} context={context} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
import { type Translation, type TranslationLocale, translations } from './translations';
|
||||
|
||||
export interface OpenAPIClientContext {
|
||||
/**
|
||||
* The translation language to use.
|
||||
*/
|
||||
translation: Translation;
|
||||
|
||||
/**
|
||||
* Icons used in the block.
|
||||
*/
|
||||
@@ -30,14 +23,9 @@ export interface OpenAPIClientContext {
|
||||
* Optional id attached to the heading and used as an anchor.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Mark the context as a client context.
|
||||
*/
|
||||
$$isClientContext$$: true;
|
||||
}
|
||||
|
||||
export interface OpenAPIContext extends Omit<OpenAPIClientContext, '$$isClientContext$$'> {
|
||||
export interface OpenAPIContext extends OpenAPIClientContext {
|
||||
/**
|
||||
* Render a code block.
|
||||
*/
|
||||
@@ -63,37 +51,14 @@ export interface OpenAPIContext extends Omit<OpenAPIClientContext, '$$isClientCo
|
||||
specUrl: string;
|
||||
}
|
||||
|
||||
export type OpenAPIUniversalContext = OpenAPIClientContext | OpenAPIContext;
|
||||
|
||||
export interface OpenAPIContextInput extends Omit<OpenAPIContext, 'translation'> {
|
||||
/**
|
||||
* The translation language to use.
|
||||
* @default 'en'
|
||||
*/
|
||||
locale?: TranslationLocale | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve OpenAPI context from the input.
|
||||
*/
|
||||
export function resolveOpenAPIContext(context: OpenAPIContextInput): OpenAPIContext {
|
||||
const { locale, ...rest } = context;
|
||||
return {
|
||||
...rest,
|
||||
translation: translations[locale ?? 'en'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client context from the OpenAPI context.
|
||||
*/
|
||||
export function getOpenAPIClientContext(context: OpenAPIUniversalContext): OpenAPIClientContext {
|
||||
export function getOpenAPIClientContext(context: OpenAPIContext): OpenAPIClientContext {
|
||||
return {
|
||||
translation: context.translation,
|
||||
icons: context.icons,
|
||||
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
||||
blockKey: context.blockKey,
|
||||
id: context.id,
|
||||
$$isClientContext$$: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export * from './schemas';
|
||||
export * from './OpenAPIOperation';
|
||||
export * from './OpenAPIWebhook';
|
||||
export * from './OpenAPIOperationContext';
|
||||
export * from './resolveOpenAPIOperation';
|
||||
export * from './resolveOpenAPIWebhook';
|
||||
export type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||
export type { OpenAPIContextInput } from './context';
|
||||
export { checkIsValidLocale } from './translations';
|
||||
export type { OpenAPIOperationData, OpenAPIContext } from './types';
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import { fromJSON, toJSON } from 'flatted';
|
||||
|
||||
import type {
|
||||
Filesystem,
|
||||
OpenAPIV3,
|
||||
OpenAPIV3_1,
|
||||
OpenAPIV3xDocument,
|
||||
} from '@gitbook/openapi-parser';
|
||||
import { dereferenceFilesystem } from './dereference';
|
||||
import type { OpenAPIWebhookData } from './types';
|
||||
|
||||
export { fromJSON, toJSON };
|
||||
|
||||
/**
|
||||
* Resolve an OpenAPI webhook in a file and compile it to a more usable format.
|
||||
*/
|
||||
export async function resolveOpenAPIWebhook(
|
||||
filesystem: Filesystem<OpenAPIV3xDocument>,
|
||||
webhookDescriptor: {
|
||||
name: string;
|
||||
method: string;
|
||||
}
|
||||
): Promise<OpenAPIWebhookData | null> {
|
||||
const { name, method } = webhookDescriptor;
|
||||
const schema = await dereferenceFilesystem(filesystem);
|
||||
let operation = getWebhookByNameAndMethod(schema, name, method);
|
||||
|
||||
if (!operation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve common parameters
|
||||
const commonParameters = getPathObjectParameter(schema, name);
|
||||
if (commonParameters) {
|
||||
operation = {
|
||||
...operation,
|
||||
parameters: [...commonParameters, ...(operation.parameters ?? [])],
|
||||
};
|
||||
}
|
||||
|
||||
const servers = 'servers' in schema ? (schema.servers ?? []) : [];
|
||||
|
||||
return {
|
||||
servers,
|
||||
operation,
|
||||
method,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a path object from its path.
|
||||
*/
|
||||
function getPathObject(
|
||||
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
||||
name: string
|
||||
): OpenAPIV3.PathItemObject | OpenAPIV3_1.PathItemObject | null {
|
||||
if (schema.webhooks?.[name]) {
|
||||
return schema.webhooks[name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve parameters from a path in an OpenAPI schema.
|
||||
*/
|
||||
function getPathObjectParameter(
|
||||
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
||||
path: string
|
||||
):
|
||||
| (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[]
|
||||
| (OpenAPIV3.ParameterObject | OpenAPIV3_1.ReferenceObject)[]
|
||||
| null {
|
||||
const pathObject = getPathObject(schema, path);
|
||||
if (pathObject?.parameters) {
|
||||
return pathObject.parameters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an operation by its path and method.
|
||||
*/
|
||||
function getWebhookByNameAndMethod(
|
||||
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
||||
name: string,
|
||||
method: string
|
||||
): OpenAPIV3.OperationObject | null {
|
||||
// Types are buffy for OpenAPIV3_1.OperationObject, so we use v3
|
||||
const pathObject = getPathObject(schema, name);
|
||||
if (!pathObject) {
|
||||
return null;
|
||||
}
|
||||
const normalizedMethod = method.toLowerCase();
|
||||
if (!pathObject[normalizedMethod]) {
|
||||
return null;
|
||||
}
|
||||
return pathObject[normalizedMethod];
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import type { OpenAPISchema } from '@gitbook/openapi-parser';
|
||||
import clsx from 'clsx';
|
||||
import { OpenAPIDisclosure } from '../OpenAPIDisclosure';
|
||||
import { OpenAPIExample } from '../OpenAPIExample';
|
||||
import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample';
|
||||
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
|
||||
import { Section, SectionBody, StaticSection } from '../StaticSection';
|
||||
import {
|
||||
type OpenAPIContextInput,
|
||||
getOpenAPIClientContext,
|
||||
resolveOpenAPIContext,
|
||||
} from '../context';
|
||||
import { t } from '../translate';
|
||||
import { getExampleFromSchema } from '../util/example';
|
||||
import { getOpenAPIClientContext } from '../context';
|
||||
import type { OpenAPIContext } from '../types';
|
||||
|
||||
/**
|
||||
* OpenAPI Schemas component.
|
||||
@@ -18,13 +13,13 @@ import { getExampleFromSchema } from '../util/example';
|
||||
export function OpenAPISchemas(props: {
|
||||
className?: string;
|
||||
schemas: OpenAPISchema[];
|
||||
context: OpenAPIContextInput;
|
||||
context: OpenAPIContext;
|
||||
/**
|
||||
* Whether to show the schema directly if there is only one.
|
||||
*/
|
||||
grouped?: boolean;
|
||||
}) {
|
||||
const { schemas, context: contextInput, grouped, className } = props;
|
||||
const { schemas, context, grouped, className } = props;
|
||||
|
||||
const firstSchema = schemas[0];
|
||||
|
||||
@@ -32,7 +27,6 @@ export function OpenAPISchemas(props: {
|
||||
return null;
|
||||
}
|
||||
|
||||
const context = resolveOpenAPIContext(contextInput);
|
||||
const clientContext = getOpenAPIClientContext(context);
|
||||
|
||||
// If there is only one model and we are not grouping, we show it directly.
|
||||
@@ -43,16 +37,11 @@ export function OpenAPISchemas(props: {
|
||||
<div className="openapi-summary" id={context.id}>
|
||||
{context.renderHeading({
|
||||
title,
|
||||
deprecated: Boolean(firstSchema.schema.deprecated),
|
||||
stability: firstSchema.schema['x-stability'],
|
||||
})}
|
||||
</div>
|
||||
<div className="openapi-columns">
|
||||
<div className="openapi-column-spec">
|
||||
<StaticSection
|
||||
className="openapi-parameters"
|
||||
header={t(context.translation, 'attributes')}
|
||||
>
|
||||
<StaticSection className="openapi-parameters" header="Attributes">
|
||||
<OpenAPIRootSchema
|
||||
schema={firstSchema.schema}
|
||||
context={clientContext}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { Translation, TranslationKey } from './translations';
|
||||
|
||||
/**
|
||||
* Translate a string.
|
||||
*/
|
||||
export function t(
|
||||
translation: Translation,
|
||||
id: TranslationKey,
|
||||
...args: React.ReactNode[]
|
||||
): React.ReactNode {
|
||||
const string = translation[id];
|
||||
if (!string) {
|
||||
throw new Error(`Translation not found for "${id}"`);
|
||||
}
|
||||
|
||||
// Now we are going to replace the arguments
|
||||
// but we want to return a string as long as it's possible
|
||||
// (eg. if there isn't any argument that is a ReactNode)
|
||||
const parts: React.ReactNode[] = [];
|
||||
let currentStringToReplace: string = string;
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (typeof arg === 'string') {
|
||||
currentStringToReplace = currentStringToReplace.replace(`\${${i + 1}}`, arg);
|
||||
} else {
|
||||
const [partToPush, partToReplace] = currentStringToReplace.split(`\${${i + 1}}`);
|
||||
if (partToPush === undefined || partToReplace === undefined) {
|
||||
throw new Error(`Invalid translation "${id}"`);
|
||||
}
|
||||
parts.push(<React.Fragment key={`string-${i}`}>{partToPush}</React.Fragment>);
|
||||
parts.push(<React.Fragment key={`arg-${i}`}>{arg}</React.Fragment>);
|
||||
currentStringToReplace = partToReplace;
|
||||
}
|
||||
});
|
||||
|
||||
if (!parts.length) {
|
||||
return currentStringToReplace;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{parts}
|
||||
{currentStringToReplace}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of `t` that returns a string.
|
||||
*/
|
||||
export function tString(
|
||||
translation: Translation,
|
||||
id: TranslationKey,
|
||||
...args: React.ReactNode[]
|
||||
): string {
|
||||
const result = t(translation, id, ...args);
|
||||
return reactToString(result);
|
||||
}
|
||||
|
||||
function reactToString(el: React.ReactNode): string {
|
||||
if (typeof el === 'string' || typeof el === 'number' || typeof el === 'boolean') {
|
||||
return `${el}`;
|
||||
}
|
||||
|
||||
if (el === null || el === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(el)) {
|
||||
return el.map(reactToString).join('');
|
||||
}
|
||||
|
||||
if (typeof el === 'object' && 'props' in el) {
|
||||
return el.props.children.map(reactToString).join('');
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported type ${typeof el}`);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export const de = {
|
||||
required: 'Erforderlich',
|
||||
deprecated: 'Veraltet',
|
||||
deprecated_and_sunset_on: 'Diese Operation ist veraltet und wird am ${1} eingestellt.',
|
||||
stability_experimental: 'Experimentell',
|
||||
stability_alpha: 'Alpha',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: 'In die Zwischenablage kopieren',
|
||||
copied: 'Kopiert',
|
||||
no_content: 'Kein Inhalt',
|
||||
unresolved_reference: 'Nicht aufgelöste Referenz',
|
||||
circular_reference: 'Zirkuläre Referenz',
|
||||
read_only: 'Nur lesen',
|
||||
write_only: 'Nur schreiben',
|
||||
optional: 'Optional',
|
||||
min: 'Min',
|
||||
max: 'Max',
|
||||
nullable: 'Nullfähig',
|
||||
body: 'Rumpf',
|
||||
payload: 'Nutzlast',
|
||||
headers: 'Kopfzeilen',
|
||||
authorizations: 'Autorisierungen',
|
||||
responses: 'Antworten',
|
||||
path_parameters: 'Pfadparameter',
|
||||
query_parameters: 'Abfrageparameter',
|
||||
header_parameters: 'Header-Parameter',
|
||||
attributes: 'Attribute',
|
||||
test_it: 'Teste es',
|
||||
information: 'Information',
|
||||
success: 'Erfolg',
|
||||
redirect: 'Umleitung',
|
||||
error: 'Fehler',
|
||||
show: 'Anzeigen',
|
||||
hide: 'Verstecken',
|
||||
available_items: 'Verfügbare Elemente',
|
||||
child_attributes: 'Unterattribute',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const en = {
|
||||
required: 'Required',
|
||||
deprecated: 'Deprecated',
|
||||
deprecated_and_sunset_on: 'This operation is deprecated and will be sunset on ${1}.',
|
||||
stability_experimental: 'Experimental',
|
||||
stability_alpha: 'Alpha',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: 'Copy to clipboard',
|
||||
copied: 'Copied',
|
||||
no_content: 'No content',
|
||||
unresolved_reference: 'Unresolved reference',
|
||||
circular_reference: 'Circular reference',
|
||||
read_only: 'Read-only',
|
||||
write_only: 'Write-only',
|
||||
optional: 'Optional',
|
||||
min: 'Min',
|
||||
max: 'Max',
|
||||
nullable: 'Nullable',
|
||||
body: 'Body',
|
||||
payload: 'Payload',
|
||||
headers: 'Headers',
|
||||
authorizations: 'Authorizations',
|
||||
responses: 'Responses',
|
||||
path_parameters: 'Path parameters',
|
||||
query_parameters: 'Query parameters',
|
||||
header_parameters: 'Header parameters',
|
||||
attributes: 'Attributes',
|
||||
test_it: 'Test it',
|
||||
information: 'Information',
|
||||
success: 'Success',
|
||||
redirect: 'Redirect',
|
||||
error: 'Error',
|
||||
show: 'Show',
|
||||
hide: 'Hide',
|
||||
available_items: 'Available items',
|
||||
child_attributes: 'Child attributes',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const es = {
|
||||
required: 'Requerido',
|
||||
deprecated: 'Obsoleto',
|
||||
deprecated_and_sunset_on: 'Esta operación está obsoleta y se retirará el ${1}.',
|
||||
stability_experimental: 'Experimental',
|
||||
stability_alpha: 'Alfa',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: 'Copiar al portapapeles',
|
||||
copied: 'Copiado',
|
||||
no_content: 'Sin contenido',
|
||||
unresolved_reference: 'Referencia no resuelta',
|
||||
circular_reference: 'Referencia circular',
|
||||
read_only: 'Solo lectura',
|
||||
write_only: 'Solo escritura',
|
||||
optional: 'Opcional',
|
||||
min: 'Mín',
|
||||
max: 'Máx',
|
||||
nullable: 'Nulo',
|
||||
body: 'Cuerpo',
|
||||
payload: 'Caga útil',
|
||||
headers: 'Encabezados',
|
||||
authorizations: 'Autorizaciones',
|
||||
responses: 'Respuestas',
|
||||
path_parameters: 'Parámetros de ruta',
|
||||
query_parameters: 'Parámetros de consulta',
|
||||
header_parameters: 'Parámetros de encabezado',
|
||||
attributes: 'Atributos',
|
||||
test_it: 'Pruébalo',
|
||||
information: 'Información',
|
||||
success: 'Éxito',
|
||||
redirect: 'Redirección',
|
||||
error: 'Error',
|
||||
show: 'Mostrar',
|
||||
hide: 'Ocultar',
|
||||
available_items: 'Elementos disponibles',
|
||||
child_attributes: 'Atributos secundarios',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const fr = {
|
||||
required: 'Requis',
|
||||
deprecated: 'Obsolète',
|
||||
deprecated_and_sunset_on: 'Cette opération est obsolète et sera supprimée le ${1}.',
|
||||
stability_experimental: 'Expérimental',
|
||||
stability_alpha: 'Alpha',
|
||||
stability_beta: 'Bêta',
|
||||
copy_to_clipboard: 'Copier dans le presse-papiers',
|
||||
copied: 'Copié',
|
||||
no_content: 'Aucun contenu',
|
||||
unresolved_reference: 'Référence non résolue',
|
||||
circular_reference: 'Référence circulaire',
|
||||
read_only: 'Lecture seule',
|
||||
write_only: 'Écriture seule',
|
||||
optional: 'Optionnel',
|
||||
min: 'Min',
|
||||
max: 'Max',
|
||||
nullable: 'Nullable',
|
||||
body: 'Corps',
|
||||
payload: 'Charge utile',
|
||||
headers: 'En-têtes',
|
||||
authorizations: 'Autorisations',
|
||||
responses: 'Réponses',
|
||||
path_parameters: 'Paramètres de chemin',
|
||||
query_parameters: 'Paramètres de requête',
|
||||
header_parameters: 'Paramètres d’en-tête',
|
||||
attributes: 'Attributs',
|
||||
test_it: 'Tester',
|
||||
information: 'Information',
|
||||
success: 'Succès',
|
||||
redirect: 'Redirection',
|
||||
error: 'Erreur',
|
||||
show: 'Afficher',
|
||||
hide: 'Masquer',
|
||||
available_items: 'Éléments disponibles',
|
||||
child_attributes: 'Attributs enfants',
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { de } from './de';
|
||||
import { en } from './en';
|
||||
import { es } from './es';
|
||||
import { fr } from './fr';
|
||||
import { ja } from './ja';
|
||||
import { nl } from './nl';
|
||||
import { no } from './no';
|
||||
import { pt_br } from './pt-br';
|
||||
import type { Translation } from './types';
|
||||
import { zh } from './zh';
|
||||
|
||||
export * from './types';
|
||||
|
||||
export const translations = {
|
||||
en,
|
||||
de,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
nl,
|
||||
no,
|
||||
'pt-br': pt_br,
|
||||
zh,
|
||||
} satisfies Record<string, Translation>;
|
||||
|
||||
export type TranslationLocale = keyof typeof translations;
|
||||
|
||||
/**
|
||||
* Check if the locale is valid.
|
||||
*/
|
||||
export function checkIsValidLocale(locale: string): locale is TranslationLocale {
|
||||
return Object.prototype.hasOwnProperty.call(translations, locale);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export const ja = {
|
||||
required: '必須',
|
||||
deprecated: '非推奨',
|
||||
deprecated_and_sunset_on: 'この操作は非推奨であり、${1}に廃止されます。',
|
||||
stability_experimental: '実験的',
|
||||
stability_alpha: 'アルファ',
|
||||
stability_beta: 'ベータ',
|
||||
copy_to_clipboard: 'クリップボードにコピー',
|
||||
copied: 'コピー済み',
|
||||
no_content: 'コンテンツなし',
|
||||
unresolved_reference: '未解決の参照',
|
||||
circular_reference: '循環参照',
|
||||
read_only: '読み取り専用',
|
||||
write_only: '書き込み専用',
|
||||
optional: 'オプション',
|
||||
min: '最小',
|
||||
max: '最大',
|
||||
nullable: 'ヌル許容',
|
||||
body: '本文',
|
||||
payload: 'ペイロード',
|
||||
headers: 'ヘッダー',
|
||||
authorizations: '認可',
|
||||
responses: 'レスポンス',
|
||||
path_parameters: 'パスパラメータ',
|
||||
query_parameters: 'クエリパラメータ',
|
||||
header_parameters: 'ヘッダーパラメータ',
|
||||
attributes: '属性',
|
||||
test_it: 'テストする',
|
||||
information: '情報',
|
||||
success: '成功',
|
||||
redirect: 'リダイレクト',
|
||||
error: 'エラー',
|
||||
show: '表示',
|
||||
hide: '非表示',
|
||||
available_items: '利用可能なアイテム',
|
||||
child_attributes: '子属性',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const nl = {
|
||||
required: 'Vereist',
|
||||
deprecated: 'Verouderd',
|
||||
deprecated_and_sunset_on: 'Deze bewerking is verouderd en wordt op ${1} beëindigd.',
|
||||
stability_experimental: 'Experimenteel',
|
||||
stability_alpha: 'Alfa',
|
||||
stability_beta: 'Bèta',
|
||||
copy_to_clipboard: 'Kopiëren naar klembord',
|
||||
copied: 'Gekopieerd',
|
||||
no_content: 'Geen inhoud',
|
||||
unresolved_reference: 'Onopgeloste verwijzing',
|
||||
circular_reference: 'Circulaire verwijzing',
|
||||
read_only: 'Alleen lezen',
|
||||
write_only: 'Alleen schrijven',
|
||||
optional: 'Optioneel',
|
||||
min: 'Min',
|
||||
max: 'Max',
|
||||
nullable: 'Null toegestaan',
|
||||
body: 'Body',
|
||||
payload: 'Payload',
|
||||
headers: 'Headers',
|
||||
authorizations: 'Autorisaties',
|
||||
responses: 'Reacties',
|
||||
path_parameters: 'Padparameters',
|
||||
query_parameters: 'Queryparameters',
|
||||
header_parameters: 'Headerparameters',
|
||||
attributes: 'Attributen',
|
||||
test_it: 'Test het',
|
||||
information: 'Informatie',
|
||||
success: 'Succes',
|
||||
redirect: 'Omleiding',
|
||||
error: 'Fout',
|
||||
show: 'Toon',
|
||||
hide: 'Verbergen',
|
||||
available_items: 'Beschikbare items',
|
||||
child_attributes: 'Kindattributen',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const no = {
|
||||
required: 'Påkrevd',
|
||||
deprecated: 'Foreldet',
|
||||
deprecated_and_sunset_on: 'Denne operasjonen er foreldet og vil bli avviklet den ${1}.',
|
||||
stability_experimental: 'Eksperimentell',
|
||||
stability_alpha: 'Alfa',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: 'Kopier til utklippstavle',
|
||||
copied: 'Kopiert',
|
||||
no_content: 'Ingen innhold',
|
||||
unresolved_reference: 'Uavklart referanse',
|
||||
circular_reference: 'Sirkulær referanse',
|
||||
read_only: 'Skrivebeskyttet',
|
||||
write_only: 'Kun skriving',
|
||||
optional: 'Valgfri',
|
||||
min: 'Min',
|
||||
max: 'Maks',
|
||||
nullable: 'Kan være null',
|
||||
body: 'Brødtekst',
|
||||
payload: 'Nyttelast',
|
||||
headers: 'Overskrifter',
|
||||
authorizations: 'Autorisasjoner',
|
||||
responses: 'Responser',
|
||||
path_parameters: 'Sti-parametere',
|
||||
query_parameters: 'Forespørselsparametere',
|
||||
header_parameters: 'Header-parametere',
|
||||
attributes: 'Attributter',
|
||||
test_it: 'Test det',
|
||||
information: 'Informasjon',
|
||||
success: 'Suksess',
|
||||
redirect: 'Viderekobling',
|
||||
error: 'Feil',
|
||||
show: 'Vis',
|
||||
hide: 'Skjul',
|
||||
available_items: 'Tilgjengelige elementer',
|
||||
child_attributes: 'Barneattributter',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const pt_br = {
|
||||
required: 'Obrigatório',
|
||||
deprecated: 'Obsoleto',
|
||||
deprecated_and_sunset_on: 'Esta operação está obsoleta e será descontinuada em ${1}.',
|
||||
stability_experimental: 'Experimental',
|
||||
stability_alpha: 'Alfa',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: 'Copiar para a área de transferência',
|
||||
copied: 'Copiado',
|
||||
no_content: 'Sem conteúdo',
|
||||
unresolved_reference: 'Referência não resolvida',
|
||||
circular_reference: 'Referência circular',
|
||||
read_only: 'Somente leitura',
|
||||
write_only: 'Somente escrita',
|
||||
optional: 'Opcional',
|
||||
min: 'Mín',
|
||||
max: 'Máx',
|
||||
nullable: 'Nulo',
|
||||
body: 'Corpo',
|
||||
payload: 'Carga útil',
|
||||
headers: 'Cabeçalhos',
|
||||
authorizations: 'Autorizações',
|
||||
responses: 'Respostas',
|
||||
path_parameters: 'Parâmetros de rota',
|
||||
query_parameters: 'Parâmetros de consulta',
|
||||
header_parameters: 'Parâmetros de cabeçalho',
|
||||
attributes: 'Atributos',
|
||||
test_it: 'Testar',
|
||||
information: 'Informação',
|
||||
success: 'Sucesso',
|
||||
redirect: 'Redirecionamento',
|
||||
error: 'Erro',
|
||||
show: 'Mostrar',
|
||||
hide: 'Ocultar',
|
||||
available_items: 'Itens disponíveis',
|
||||
child_attributes: 'Atributos filhos',
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { en } from './en';
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
||||
export type Translation = {
|
||||
[key in TranslationKey]: string;
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export const zh = {
|
||||
required: '必填',
|
||||
deprecated: '已弃用',
|
||||
deprecated_and_sunset_on: '此操作已弃用,将于 ${1} 停止使用。',
|
||||
stability_experimental: '实验性',
|
||||
stability_alpha: 'Alpha',
|
||||
stability_beta: 'Beta',
|
||||
copy_to_clipboard: '复制到剪贴板',
|
||||
copied: '已复制',
|
||||
no_content: '无内容',
|
||||
unresolved_reference: '未解析的引用',
|
||||
circular_reference: '循环引用',
|
||||
read_only: '只读',
|
||||
write_only: '只写',
|
||||
optional: '可选',
|
||||
min: '最小值',
|
||||
max: '最大值',
|
||||
nullable: '可为 null',
|
||||
body: '请求体',
|
||||
payload: '有效载荷',
|
||||
headers: '头部信息',
|
||||
authorizations: '授权',
|
||||
responses: '响应',
|
||||
path_parameters: '路径参数',
|
||||
query_parameters: '查询参数',
|
||||
header_parameters: '头参数',
|
||||
attributes: '属性',
|
||||
test_it: '测试一下',
|
||||
information: '信息',
|
||||
success: '成功',
|
||||
redirect: '重定向',
|
||||
error: '错误',
|
||||
show: '显示',
|
||||
hide: '隐藏',
|
||||
available_items: '可用项',
|
||||
child_attributes: '子属性',
|
||||
};
|
||||
@@ -4,6 +4,59 @@ import type {
|
||||
OpenAPIV3,
|
||||
} from '@gitbook/openapi-parser';
|
||||
|
||||
export interface OpenAPIClientContext {
|
||||
/**
|
||||
* Icons used in the block.
|
||||
*/
|
||||
icons: {
|
||||
chevronDown: React.ReactNode;
|
||||
chevronRight: React.ReactNode;
|
||||
plus: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Force all sections to be opened by default.
|
||||
* @default false
|
||||
*/
|
||||
defaultInteractiveOpened?: boolean;
|
||||
|
||||
/**
|
||||
* The key of the block
|
||||
*/
|
||||
blockKey?: string;
|
||||
|
||||
/**
|
||||
* Optional id attached to the heading and used as an anchor.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface OpenAPIContext extends OpenAPIClientContext {
|
||||
/**
|
||||
* Render a code block.
|
||||
*/
|
||||
renderCodeBlock: (props: { code: string; syntax: string }) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Render the heading of the operation.
|
||||
*/
|
||||
renderHeading: (props: {
|
||||
deprecated?: boolean;
|
||||
title: string;
|
||||
stability?: string;
|
||||
}) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Render the document of the operation.
|
||||
*/
|
||||
renderDocument: (props: { document: object }) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Specification URL.
|
||||
*/
|
||||
specUrl: string;
|
||||
}
|
||||
|
||||
export type OpenAPISecurityWithRequired = OpenAPIV3.SecuritySchemeObject & { required?: boolean };
|
||||
|
||||
export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
||||
@@ -19,14 +72,3 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
||||
/** Securities that should be used for this operation */
|
||||
securities: [string, OpenAPISecurityWithRequired][];
|
||||
}
|
||||
|
||||
export interface OpenAPIWebhookData extends OpenAPICustomSpecProperties {
|
||||
name: string;
|
||||
method: string;
|
||||
|
||||
/** Servers to be used for this operation */
|
||||
servers: OpenAPIV3.ServerObject[];
|
||||
|
||||
/** Spec of the webhook */
|
||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { OpenAPIExample } from '../OpenAPIExample';
|
||||
import type { OpenAPIContext } from '../context';
|
||||
import { generateSchemaExample } from '../generateSchemaExample';
|
||||
import { tString } from '../translate';
|
||||
import { checkIsReference } from '../utils';
|
||||
|
||||
/**
|
||||
* Generate an example from a reference object.
|
||||
*/
|
||||
export function getExampleFromReference(
|
||||
ref: OpenAPIV3.ReferenceObject,
|
||||
context: OpenAPIContext
|
||||
): OpenAPIV3.ExampleObject {
|
||||
return {
|
||||
summary: tString(context.translation, 'unresolved_reference'),
|
||||
value: { $ref: ref.$ref },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples from a media type object.
|
||||
*/
|
||||
export function getExamplesFromMediaTypeObject(args: {
|
||||
mediaType: string;
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
context: OpenAPIContext;
|
||||
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
||||
const { mediaTypeObject, mediaType, context } = args;
|
||||
if (mediaTypeObject.examples) {
|
||||
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
||||
return {
|
||||
key,
|
||||
example: checkIsReference(example)
|
||||
? getExampleFromReference(example, context)
|
||||
: example,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (mediaTypeObject.example) {
|
||||
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
||||
}
|
||||
|
||||
if (mediaTypeObject.schema) {
|
||||
if (mediaType === 'application/xml') {
|
||||
// @TODO normally we should use the name of the schema but we don't have it
|
||||
// fix it when we got the reference name
|
||||
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: {
|
||||
value: {
|
||||
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
||||
xml: mediaType === 'application/xml',
|
||||
mode: 'read',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: {
|
||||
value: generateSchemaExample(mediaTypeObject.schema, {
|
||||
mode: 'read',
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get example from a schema object.
|
||||
*/
|
||||
export function getExampleFromSchema(args: {
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
}): OpenAPIV3.ExampleObject {
|
||||
const { schema } = args;
|
||||
|
||||
if (schema.example) {
|
||||
return { value: schema.example };
|
||||
}
|
||||
|
||||
return { value: generateSchemaExample(schema, { mode: 'read' }) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the examples from a media type object.
|
||||
*/
|
||||
export function getExamples(props: {
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
mediaType: string;
|
||||
context: OpenAPIContext;
|
||||
}) {
|
||||
const { mediaTypeObject, mediaType, context } = props;
|
||||
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType, context });
|
||||
const syntax = getSyntaxFromMediaType(mediaType);
|
||||
|
||||
return examples.map((example) => {
|
||||
return {
|
||||
key: example.key,
|
||||
label: example.example.summary || example.key,
|
||||
body: (
|
||||
<OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the syntax from a media type.
|
||||
*/
|
||||
function getSyntaxFromMediaType(mediaType: string): string {
|
||||
if (mediaType.includes('json')) {
|
||||
return 'json';
|
||||
}
|
||||
|
||||
if (mediaType === 'application/xml') {
|
||||
return 'xml';
|
||||
}
|
||||
|
||||
return 'text';
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||
import type { OpenAPIUniversalContext } from './context';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { tString } from './translate';
|
||||
|
||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||
return typeof input === 'object' && !!input && '$ref' in input;
|
||||
@@ -184,21 +182,18 @@ export function getStatusCodeClassName(statusCode: number | string): string {
|
||||
* 3xx: Redirect
|
||||
* 4xx, 5xx: Error
|
||||
*/
|
||||
export function getStatusCodeDefaultLabel(
|
||||
statusCode: number | string,
|
||||
context: OpenAPIUniversalContext
|
||||
): string {
|
||||
export function getStatusCodeDefaultLabel(statusCode: number | string): string {
|
||||
const category = getStatusCodeCategory(statusCode);
|
||||
switch (category) {
|
||||
case 1:
|
||||
return tString(context.translation, 'information');
|
||||
return 'Information';
|
||||
case 2:
|
||||
return tString(context.translation, 'success');
|
||||
return 'Success';
|
||||
case 3:
|
||||
return tString(context.translation, 'redirect');
|
||||
return 'Redirect';
|
||||
case 4:
|
||||
case 5:
|
||||
return tString(context.translation, 'error');
|
||||
return 'Error';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user