mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 304042017c | |||
| 82cd9f2979 | |||
| a3f1fea27b | |||
| bb5c6a42e7 | |||
| 445baaaa61 | |||
| 7419ee7dba | |||
| aa6be381a4 | |||
| 61575838f3 | |||
| 359bb979f8 |
@@ -22,6 +22,13 @@
|
||||
"wrangler": "3.82.0",
|
||||
},
|
||||
},
|
||||
"packages/colors": {
|
||||
"name": "@gitbook/colors",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
},
|
||||
"packages/emoji-codepoints": {
|
||||
"name": "@gitbook/emoji-codepoints",
|
||||
"version": "0.2.0",
|
||||
@@ -31,10 +38,11 @@
|
||||
},
|
||||
"packages/gitbook": {
|
||||
"name": "gitbook",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.93.0",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -61,6 +69,8 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.15",
|
||||
"micromark": "^4.0.1",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"next": "14.2.23",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
@@ -71,17 +81,11 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"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",
|
||||
"rison": "^0.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "^1.27.2",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.4",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
},
|
||||
@@ -207,13 +211,14 @@
|
||||
},
|
||||
"packages/react-openapi": {
|
||||
"name": "@gitbook/react-openapi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "1.0.87",
|
||||
"@scalar/oas-utils": "^0.2.101",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
"react-aria": "^3.37.0",
|
||||
"react-aria-components": "^1.6.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
@@ -598,6 +603,8 @@
|
||||
|
||||
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
|
||||
|
||||
"@gitbook/colors": ["@gitbook/colors@workspace:packages/colors"],
|
||||
|
||||
"@gitbook/emoji-codepoints": ["@gitbook/emoji-codepoints@workspace:packages/emoji-codepoints"],
|
||||
|
||||
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.8", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.6.0" } }, "sha512-i4PgiuGyUb52Muhc52kK3aMJIMfMkA2RbPW30tre8a6M8T6mWTfYo6gafSgjNvF1vH29zcuB8oBYnF0gO4XcHA=="],
|
||||
@@ -2210,7 +2217,7 @@
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.2.0", "unist-util-position": "^5.0.0" } }, "sha512-IGrgWLuip4O2nq5CugXy4GI2V8kx4sFVy5Hd4vF7AR2gxS0N9s7nEAVUyeMtZKZvzrxVsHt73XdTsno1tClIkQ=="],
|
||||
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
|
||||
|
||||
@@ -2332,7 +2339,7 @@
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
"is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||
|
||||
"is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
|
||||
|
||||
@@ -2404,6 +2411,8 @@
|
||||
|
||||
"json-stringify-deterministic": ["json-stringify-deterministic@1.0.12", "", {}, "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g=="],
|
||||
|
||||
"json-xml-parse": ["json-xml-parse@1.3.0", "", {}, "sha512-MVosauc/3W2wL4dd4yaJzH5oXw+HOUfptn0+d4+bFghMiJFop7MaqIwFXJNLiRnNYJNQ6L4o7B+53n5wcvoLFw=="],
|
||||
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||
@@ -2542,7 +2551,7 @@
|
||||
|
||||
"microdiff": ["microdiff@1.4.0", "", {}, "sha512-OBKBOa1VBznvLPb/3ljeJaENVe0fO0lnWl77lR4vhPlQD71UpjEoRV5P0KdQkcjbFlBu1Oy2mEUBMU3wxcBAGg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
|
||||
"micromark": ["micromark@4.0.1", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA=="],
|
||||
|
||||
@@ -4292,8 +4301,6 @@
|
||||
|
||||
"@sentry/webpack-plugin/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="],
|
||||
|
||||
"@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@smithy/chunked-blob-reader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -4682,6 +4689,8 @@
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"mdast-util-from-markdown/micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
|
||||
|
||||
"mdast-util-gfm/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=="],
|
||||
|
||||
"mdast-util-gfm-footnote/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=="],
|
||||
@@ -4700,12 +4709,8 @@
|
||||
|
||||
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
|
||||
|
||||
"micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"minimist-options/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="],
|
||||
|
||||
"minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||
|
||||
"minizlib/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
|
||||
|
||||
"next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
|
||||
@@ -4760,6 +4765,8 @@
|
||||
|
||||
"read-yaml-file/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
|
||||
|
||||
"rehype-stringify/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
|
||||
|
||||
"remark-stringify/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=="],
|
||||
|
||||
"require-in-the-middle/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
@@ -4814,6 +4821,8 @@
|
||||
|
||||
"type-is/mime-types": ["mime-types@3.0.0", "", { "dependencies": { "mime-db": "^1.53.0" } }, "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w=="],
|
||||
|
||||
"unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"update-notifier/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
@@ -5614,6 +5623,8 @@
|
||||
|
||||
"gtoken/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
|
||||
|
||||
"mdast-util-from-markdown/micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"raw-body/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
|
||||
|
||||
"raw-body/http-errors/inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist/
|
||||
@@ -0,0 +1,7 @@
|
||||
# @gitbook/colors
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 445baaa: Initial release
|
||||
@@ -0,0 +1,3 @@
|
||||
# `@gitbook/colors`
|
||||
|
||||
A set of default colors and transformation functions used throughout the GitBook Open and app.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@gitbook/colors",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "0.2.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "tsc -w"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Default primary color throughout the GitBook ecosystem.
|
||||
*/
|
||||
export const DEFAULT_PRIMARY_COLOR = '#346DDB';
|
||||
|
||||
/**
|
||||
* The darkest color that exists in GitBook, used as the relative minimum of every generated color scale.
|
||||
*/
|
||||
export const DARK_BASE = '#1D1D1D';
|
||||
|
||||
/**
|
||||
* The lightest color that exists in GitBook, used as the relative maximum of every generated color scale.
|
||||
*/
|
||||
export const LIGHT_BASE = '#FFFFFF';
|
||||
|
||||
/**
|
||||
* Used as the basis of all UI elements that are not colored by the primary color. Neutral gray by default, overridden by site customization.
|
||||
*/
|
||||
export const DEFAULT_TINT_COLOR = '#787878';
|
||||
|
||||
/**
|
||||
* Used for informational messages and neutral alerts.
|
||||
*/
|
||||
export const DEFAULT_HINT_INFO_COLOR = '#787878';
|
||||
|
||||
/**
|
||||
* Used for showing important information or non-critical warnings.
|
||||
*/
|
||||
export const DEFAULT_HINT_WARNING_COLOR = '#FE9A00';
|
||||
|
||||
/**
|
||||
* Used for destructive actions or raising attention to critical information.
|
||||
*/
|
||||
export const DEFAULT_HINT_DANGER_COLOR = '#FB2C36';
|
||||
|
||||
/**
|
||||
* Used for showing positive actions or achievements.
|
||||
*/
|
||||
export const DEFAULT_HINT_SUCCESS_COLOR = '#00C950';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './colors';
|
||||
export * from './transformations';
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DARK_BASE, LIGHT_BASE, DEFAULT_TINT_COLOR } from './colors';
|
||||
|
||||
type ColorShades = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6,9 +8,6 @@ type RGBColor = [number, number, number];
|
||||
type OKLABColor = { L: number; A: number; B: number };
|
||||
type OKLCHColor = { L: number; C: number; H: number };
|
||||
|
||||
export const DARK_BASE = '#1d1d1d';
|
||||
export const LIGHT_BASE = '#ffffff';
|
||||
export const DEFAULT_TINT_COLOR = '#787878';
|
||||
const D65 = [95.047, 100.0, 108.883]; // Reference white (D65)
|
||||
|
||||
export enum ColorCategory {
|
||||
@@ -226,7 +225,7 @@ export function colorScale(
|
||||
/**
|
||||
* Convert a hex color to an RGB color set.
|
||||
*/
|
||||
function hexToRgbArray(hex: string): RGBColor {
|
||||
export function hexToRgbArray(hex: string): RGBColor {
|
||||
const originalHex = hex;
|
||||
|
||||
let value = hex.replace('#', '');
|
||||
@@ -252,7 +251,7 @@ function hexToRgbArray(hex: string): RGBColor {
|
||||
/**
|
||||
* Convert a RGB color set to a hex color.
|
||||
*/
|
||||
function rgbArrayToHex(rgb: RGBColor): string {
|
||||
export function rgbArrayToHex(rgb: RGBColor): string {
|
||||
return `#${rgb
|
||||
.map((channel) => {
|
||||
const component = channel.toString(16);
|
||||
@@ -262,7 +261,7 @@ function rgbArrayToHex(rgb: RGBColor): string {
|
||||
.join('')}`;
|
||||
}
|
||||
|
||||
function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
export function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
const rgb = end.map((channel, index) => {
|
||||
return Math.round(channel + percentage * (start[index] - channel));
|
||||
});
|
||||
@@ -271,21 +270,21 @@ function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
}
|
||||
|
||||
// Utility constants and helper functions
|
||||
function rgbToLinear(rgb: RGBColor): [number, number, number] {
|
||||
export function rgbToLinear(rgb: RGBColor): [number, number, number] {
|
||||
return rgb.map((v) => {
|
||||
const scaled = v / 255;
|
||||
return scaled <= 0.04045 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
|
||||
}) as [number, number, number];
|
||||
}
|
||||
|
||||
function linearToRgb(linear: [number, number, number]): RGBColor {
|
||||
export function linearToRgb(linear: [number, number, number]): RGBColor {
|
||||
return linear.map((v) => {
|
||||
const scaled = v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
|
||||
return Math.round(Math.max(0, Math.min(1, scaled)) * 255);
|
||||
}) as RGBColor;
|
||||
}
|
||||
|
||||
function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
export function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
const [r, g, b] = rgbToLinear(rgb);
|
||||
|
||||
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||
@@ -303,7 +302,7 @@ function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
};
|
||||
}
|
||||
|
||||
function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
export function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
const { L, A, B } = oklab;
|
||||
|
||||
const lRoot = L + 0.3963377774 * A + 0.2158037573 * B;
|
||||
@@ -321,14 +320,14 @@ function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
return linearToRgb([r, g, b]);
|
||||
}
|
||||
|
||||
function oklabToOklch(oklab: OKLABColor): OKLCHColor {
|
||||
export function oklabToOklch(oklab: OKLABColor): OKLCHColor {
|
||||
const { L, A, B } = oklab;
|
||||
const C = Math.sqrt(A ** 2 + B ** 2);
|
||||
const H = (Math.atan2(B, A) * 180) / Math.PI;
|
||||
return { L, C, H: H < 0 ? H + 360 : H };
|
||||
}
|
||||
|
||||
function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
export function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
const { L, C, H } = oklch;
|
||||
const rad = (H * Math.PI) / 180;
|
||||
return {
|
||||
@@ -338,15 +337,15 @@ function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
};
|
||||
}
|
||||
|
||||
function rgbToOklch(rgb: RGBColor): OKLCHColor {
|
||||
export function rgbToOklch(rgb: RGBColor): OKLCHColor {
|
||||
return oklabToOklch(rgbToOklab(rgb));
|
||||
}
|
||||
|
||||
function oklchToRgb(oklch: OKLCHColor): RGBColor {
|
||||
export function oklchToRgb(oklch: OKLCHColor): RGBColor {
|
||||
return oklabToRgb(oklchToOklab(oklch));
|
||||
}
|
||||
|
||||
function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
export function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
const [r, g, b] = rgbToLinear(rgb);
|
||||
return [
|
||||
(r * 0.4124564 + g * 0.3575761 + b * 0.1804375) * 100,
|
||||
@@ -355,7 +354,11 @@ function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
];
|
||||
}
|
||||
|
||||
function xyzToLab65(xyz: [number, number, number]): { L: number; A: number; B: number } {
|
||||
export function xyzToLab65(xyz: [number, number, number]): {
|
||||
L: number;
|
||||
A: number;
|
||||
B: number;
|
||||
} {
|
||||
const [x, y, z] = xyz.map((v, i) => {
|
||||
const scaled = v / D65[i];
|
||||
return scaled > 0.008856 ? Math.cbrt(scaled) : 7.787 * scaled + 16 / 116;
|
||||
@@ -368,7 +371,7 @@ function xyzToLab65(xyz: [number, number, number]): { L: number; A: number; B: n
|
||||
};
|
||||
}
|
||||
|
||||
function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
export function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
return xyzToLab65(rgbToXyz(rgb));
|
||||
}
|
||||
|
||||
@@ -376,7 +379,7 @@ function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
Delta Phi Star perceptual lightness contrast by Andrew Somers:
|
||||
https://github.com/Myndex/deltaphistar
|
||||
*/
|
||||
const PHI = 0.5 + Math.sqrt(1.25);
|
||||
export const PHI = 0.5 + Math.sqrt(1.25);
|
||||
|
||||
export function dpsContrast(a: RGBColor, b: RGBColor) {
|
||||
const dps = Math.abs(rgbTolab65(a).L ** PHI - rgbTolab65(b).L ** PHI);
|
||||
@@ -387,7 +390,10 @@ export function dpsContrast(a: RGBColor, b: RGBColor) {
|
||||
export function colorContrast(background: string, foreground: string[] = [LIGHT_BASE, DARK_BASE]) {
|
||||
const bg = hexToRgbArray(background);
|
||||
|
||||
const best: { color?: RGBColor; contrast: number } = { color: undefined, contrast: 0 };
|
||||
const best: { color?: RGBColor; contrast: number } = {
|
||||
color: undefined,
|
||||
contrast: 0,
|
||||
};
|
||||
for (const color of foreground) {
|
||||
const c = hexToRgbArray(color);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"incremental": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,5 +1,22 @@
|
||||
# gitbook
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 359bb97: Fix opening external links when the GitBook page is embedded in an iframe
|
||||
- 6157583: Improve Markdown parsing
|
||||
- 82cd9f2: Add support for anchor links in OpenAPI blocks
|
||||
- Updated dependencies [445baaa]
|
||||
- Updated dependencies [bb5c6a4]
|
||||
- Updated dependencies [a3f1fea]
|
||||
- Updated dependencies [6157583]
|
||||
- Updated dependencies [7419ee7]
|
||||
- Updated dependencies [82cd9f2]
|
||||
- @gitbook/colors@0.2.0
|
||||
- @gitbook/react-openapi@1.0.2
|
||||
- @gitbook/openapi-parser@1.0.1
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent -f ../../.env.local next dev",
|
||||
@@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.93.0",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -45,6 +46,8 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.15",
|
||||
"micromark": "^4.0.1",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"next": "14.2.23",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
@@ -55,17 +58,11 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"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",
|
||||
"rison": "^0.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "^1.27.2",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.4",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
import { CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import { colorContrast } from '@gitbook/colors';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { NextRequest } from 'next/server';
|
||||
import React from 'react';
|
||||
|
||||
import { googleFontsMap } from '@/fonts';
|
||||
import { colorContrast } from '@/lib/colors';
|
||||
import { getAbsoluteHref } from '@/lib/links';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
import { getContentTitle } from '@/lib/utils';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SiteInsightsAd } from '@gitbook/api';
|
||||
import { hexToRgba } from '@gitbook/colors';
|
||||
import * as React from 'react';
|
||||
|
||||
import { hexToRgba } from '@/lib/colors';
|
||||
import { getResizedImageURL } from '@/lib/images';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Icon } from '@gitbook/icons';
|
||||
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
||||
import React from 'react';
|
||||
|
||||
import { LoadingPane } from '@/components/primitives';
|
||||
import { fetchOpenAPIBlock } from '@/lib/openapi';
|
||||
import { fetchOpenAPIBlock } from '@/lib/openapi/fetch';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from '../Block';
|
||||
import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './style.css';
|
||||
import './scalar.css';
|
||||
@@ -54,6 +54,31 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
CodeBlock: PlainCodeBlock,
|
||||
renderHeading: (headingProps) => (
|
||||
<Heading
|
||||
document={props.document}
|
||||
ancestorBlocks={props.ancestorBlocks}
|
||||
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||
context={props.context}
|
||||
style={headingProps.deprecated ? 'line-through' : undefined}
|
||||
block={{
|
||||
object: 'block',
|
||||
key: `${block.key}-heading`,
|
||||
meta: block.meta,
|
||||
data: {},
|
||||
type: 'heading-2',
|
||||
nodes: [
|
||||
{
|
||||
key: `${block.key}-heading-text`,
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{ text: headingProps.title, object: 'leaf', marks: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
),
|
||||
defaultInteractiveOpened: context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
@@ -62,32 +87,3 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIFallback() {
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
aria-busy
|
||||
className={'openapi-block ' + tcls('flex', 'flex-1', 'flex-col', 'gap-3')}
|
||||
>
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[47px]', '[max-width:calc(48rem-1px)]']}
|
||||
/>
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[35px]', '[max-width:calc(48rem-1px)]']}
|
||||
/>
|
||||
<div className={tcls('flex', 'gap-[25px]')}>
|
||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
||||
</div>
|
||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* Layout Components */
|
||||
.openapi-operation {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-height: 600px;
|
||||
@apply flex-1 flex flex-col gap-4 mb-14;
|
||||
}
|
||||
|
||||
@@ -18,16 +16,8 @@
|
||||
@apply flex flex-col items-start justify-start gap-2;
|
||||
}
|
||||
|
||||
.openapi-summary-title {
|
||||
@apply font-semibold text-xl;
|
||||
}
|
||||
|
||||
.openapi-summary-title[data-deprecated='true'] {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
.openapi-deprecated {
|
||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded-full text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||
}
|
||||
|
||||
.openapi-deprecated-sunset-date {
|
||||
@@ -463,7 +453,7 @@
|
||||
}
|
||||
|
||||
.openapi-section-header-content {
|
||||
@apply flex-1 text-base gap-1.5 flex w-full font-medium text-tint-strong;
|
||||
@apply flex-1 overflow-hidden text-base gap-1.5 flex w-full font-medium text-tint-strong;
|
||||
}
|
||||
|
||||
.openapi-section-header-controls {
|
||||
@@ -500,11 +490,13 @@
|
||||
|
||||
/* Tabs */
|
||||
.openapi-tabs-list {
|
||||
@apply flex flex-row gap-3 py-1.5 px-2.5 border-b border-tint-subtle w-full;
|
||||
@apply flex flex-row gap-1.5 py-1.5 px-2.5 border-b border-tint-subtle w-full overflow-x-scroll;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.openapi-tabs-tab {
|
||||
@apply hover:bg-primary-hover font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded;
|
||||
@apply hover:bg-primary-hover whitespace-nowrap font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded;
|
||||
}
|
||||
|
||||
.openapi-tabs-tab[aria-selected='true'] {
|
||||
|
||||
@@ -15,31 +15,29 @@ import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
export function Text(props: { text: DocumentText }) {
|
||||
const { text } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort(
|
||||
(a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0),
|
||||
)
|
||||
.reduce<React.ReactNode>((children, mark, index) => {
|
||||
const Mark = MARK_STYLES[mark.type];
|
||||
return text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort((a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0))
|
||||
.reduce<React.ReactNode>((children, mark, index) => {
|
||||
const Mark = MARK_STYLES[mark.type];
|
||||
|
||||
if (!Mark) {
|
||||
return children;
|
||||
}
|
||||
if (!Mark) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return <Mark mark={mark}>{children}</Mark>;
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Mark key="mark" mark={mark}>
|
||||
{children}
|
||||
</Mark>
|
||||
);
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const MARK_STYLES = {
|
||||
|
||||
@@ -9,18 +9,18 @@ import {
|
||||
type CustomizationTint,
|
||||
type SiteCustomizationSettings,
|
||||
} from '@gitbook/api';
|
||||
import { IconsProvider, IconStyle } from '@gitbook/icons';
|
||||
|
||||
import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getStaticFileURL } from '@/lib/assets';
|
||||
import {
|
||||
colorContrast,
|
||||
colorScale,
|
||||
type ColorScaleOptions,
|
||||
DEFAULT_TINT_COLOR,
|
||||
hexToRgb,
|
||||
} from '@/lib/colors';
|
||||
} from '@gitbook/colors';
|
||||
import { IconsProvider, IconStyle } from '@gitbook/icons';
|
||||
|
||||
import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getStaticFileURL } from '@/lib/assets';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { ClientContexts } from './ClientContexts';
|
||||
|
||||
@@ -47,6 +47,13 @@ export const Link = React.forwardRef(function Link(
|
||||
});
|
||||
}
|
||||
|
||||
// When the page is embedded in an iframe, for security reasons other urls cannot be opened.
|
||||
// In this case, we open the link in a new tab.
|
||||
if (isExternal && window.self !== window.top) {
|
||||
event.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
|
||||
domProps.onClick?.(event);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`parseMarkdown should parse a simple table 1`] = `
|
||||
"<h2>Table</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th align="left">b</th>
|
||||
<th align="right">c</th>
|
||||
<th align="center">d</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>"
|
||||
`;
|
||||
|
||||
exports[`parseMarkdown should parse a complex table 1`] = `
|
||||
"<p>Returns information for all non-fungible tokens for an account.</p>
|
||||
<h2>Ordering</h2>
|
||||
<p>When considering NFTs, their order is governed by a combination of their numerical <strong>token.Id</strong> and <strong>serialnumber</strong> values, with <strong>token.id</strong> being the parent column.
|
||||
A serialnumbers value governs its order within the given token.id</p>
|
||||
<p>In that regard, if a user acquired a set of NFTs in the order (2-2, 2-4 1-5, 1-1, 1-3, 3-3, 3-4), the following layouts illustrate the ordering expectations for ownership listing</p>
|
||||
<ol>
|
||||
<li><strong>All NFTs in ASC order</strong>: 1-1, 1-3, 1-5, 2-2, 2-4, 3-3, 3-4</li>
|
||||
<li><strong>All NFTs in DESC order</strong>: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3, 1-1</li>
|
||||
<li><strong>NFTs above 1-1 in ASC order</strong>: 1-3, 1-5, 2-2, 2-4, 3-3, 3-4</li>
|
||||
<li><strong>NFTs below 3-3 in ASC order</strong>: 1-1, 1-3, 1-5, 2-2, 2-4</li>
|
||||
<li><strong>NFTs between 1-3 and 3-3 inclusive in DESC order</strong>: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3</li>
|
||||
</ol>
|
||||
<p>Note: The default order for this API is currently DESC</p>
|
||||
<h2>Filtering</h2>
|
||||
<p>When filtering there are some restrictions enforced to ensure correctness and scalability.</p>
|
||||
<p><strong>The table below defines the restrictions and support for the NFT ownership endpoint</strong></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Query Param</th>
|
||||
<th>Comparison Operator</th>
|
||||
<th>Support</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>token.id</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=lte:X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=gte:X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serialnumber</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of a <strong>token.id</strong> query</td>
|
||||
<td>?serialnumber=Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of an <strong>lte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
|
||||
<td>?token.id=lte:X&serialnumber=lt:Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of an <strong>gte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
|
||||
<td>?token.id=gte:X&serialnumber=gt:Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>spender.id</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=Z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=lt:Z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=gt:Z</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Note: When searching across a range for individual NFTs a <strong>serialnumber</strong> with an additional <strong>token.id</strong> query filter must be provided.
|
||||
Both filters must be a single occurrence of <strong>gt(e)</strong> or <strong>lt(e)</strong> which provide a lower and or upper boundary for search.</p>"
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { JSONDocument, ContentRef } from '@gitbook/api';
|
||||
|
||||
import { getNodeText } from './document';
|
||||
import { fetchOpenAPIBlock } from './openapi';
|
||||
import { fetchOpenAPIBlock } from './openapi/fetch';
|
||||
import { ResolvedContentRef } from './references';
|
||||
|
||||
export interface DocumentSection {
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('parseMarkdown', () => {
|
||||
| a | b | c | d |
|
||||
| - | :- | -: | :-: |`);
|
||||
|
||||
expect(result).toContain('<table>');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should parse a complex table', async () => {
|
||||
@@ -52,6 +52,6 @@ When filtering there are some restrictions enforced to ensure correctness and sc
|
||||
Note: When searching across a range for individual NFTs a **serialnumber** with an additional **token.id** query filter must be provided.
|
||||
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.`);
|
||||
|
||||
expect(result).toContain('<table>');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import { unified } from 'unified';
|
||||
import { micromark } from 'micromark';
|
||||
import { gfmHtml, gfm } from 'micromark-extension-gfm';
|
||||
|
||||
/**
|
||||
* Parse markdown and output HTML.
|
||||
*/
|
||||
export async function parseMarkdown(markdown: string): Promise<string> {
|
||||
const promise = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkRehype)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeStringify)
|
||||
.process(markdown)
|
||||
.then((file) => file.toString());
|
||||
|
||||
return promise;
|
||||
export function parseMarkdown(input: string): string {
|
||||
return micromark(input, {
|
||||
extensions: [gfm()],
|
||||
htmlExtensions: [gfmHtml()],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { parseOpenAPI } from '@gitbook/openapi-parser';
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { enrichFilesystem } from './enrich';
|
||||
|
||||
const spec = await Bun.file(new URL('./fixtures/multiline-spec.yaml', import.meta.url)).text();
|
||||
|
||||
describe('#enrichFilesystem', () => {
|
||||
it('supports multiline descriptions', async () => {
|
||||
const filesystem = await parseOpenAPI({
|
||||
value: spec,
|
||||
rootURL: null,
|
||||
});
|
||||
const enriched = await enrichFilesystem(filesystem);
|
||||
expect(enriched[0].specification.paths['/pet'].put['x-gitbook-description-html']).toBe(
|
||||
'<p>Social platform</p>',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { traverse, Filesystem } from '@gitbook/openapi-parser';
|
||||
|
||||
import { parseMarkdown } from '../markdown';
|
||||
|
||||
/**
|
||||
* Enrich a filesystem with HTML descriptions.
|
||||
*/
|
||||
export async function enrichFilesystem(filesystem: Filesystem) {
|
||||
const parseMarkdownWithCache = createMarkdownParser();
|
||||
return traverse(filesystem, async (node, path) => {
|
||||
if (
|
||||
path !== undefined &&
|
||||
'description' in node &&
|
||||
typeof node.description === 'string' &&
|
||||
node.description
|
||||
) {
|
||||
const key = path[path.length - 1];
|
||||
// Avoid parsing descriptions in examples.
|
||||
if (key !== 'example') {
|
||||
const description = node.description.trim();
|
||||
if (description) {
|
||||
node['x-gitbook-description-html'] = parseMarkdownWithCache(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a markdown parser that caches the results of parsing.
|
||||
*/
|
||||
const createMarkdownParser = () => (input: string) => {
|
||||
const cache: Record<string, string> = {};
|
||||
const existing = cache[input];
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const result = parseMarkdown(input);
|
||||
cache[input] = result;
|
||||
return result;
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ContentRef, DocumentBlockOpenAPI } from '@gitbook/api';
|
||||
import { parseOpenAPI, OpenAPIParseError, traverse } from '@gitbook/openapi-parser';
|
||||
import { parseOpenAPI, OpenAPIParseError } from '@gitbook/openapi-parser';
|
||||
import { type OpenAPIOperationData, resolveOpenAPIOperation } from '@gitbook/react-openapi';
|
||||
|
||||
import { cache, noCacheFetchOptions, CacheFunctionOptions } from '@/lib/cache';
|
||||
|
||||
import { parseMarkdown } from './markdown';
|
||||
import { ResolvedContentRef } from './references';
|
||||
import { enrichFilesystem } from './enrich';
|
||||
import { ResolvedContentRef } from '../references';
|
||||
|
||||
/**
|
||||
* Fetch an OpenAPI specification for an operation.
|
||||
@@ -40,7 +40,7 @@ export async function fetchOpenAPIBlock(
|
||||
}
|
||||
|
||||
const fetchFilesystem = cache({
|
||||
name: 'openapi.fetch.v5',
|
||||
name: 'openapi.fetch.v6',
|
||||
get: async (url: string, options: CacheFunctionOptions) => {
|
||||
// Wrap the raw string to prevent invalid URLs from being passed to fetch.
|
||||
// This can happen if the URL has whitespace, which is currently handled differently by Cloudflare's implementation of fetch:
|
||||
@@ -58,38 +58,13 @@ const fetchFilesystem = cache({
|
||||
|
||||
const text = await response.text();
|
||||
const filesystem = await parseOpenAPI({ value: text, rootURL: url });
|
||||
const parseMarkdownWithCache = createMarkdownParser();
|
||||
const transformedFs = await traverse(filesystem, async (node, path) => {
|
||||
if ('description' in node && typeof node.description === 'string' && node.description) {
|
||||
const lastKey = path && path[path.length - 1];
|
||||
// Avoid parsing descriptions in examples.
|
||||
if (lastKey === 'example') {
|
||||
return node;
|
||||
}
|
||||
node['x-description-html'] = await parseMarkdownWithCache(node.description);
|
||||
}
|
||||
return node;
|
||||
});
|
||||
const richFilesystem = await enrichFilesystem(filesystem);
|
||||
return {
|
||||
// Cache for 4 hours
|
||||
ttl: 24 * 60 * 60,
|
||||
// Revalidate every 2 hours
|
||||
revalidateBefore: 22 * 60 * 60,
|
||||
data: transformedFs,
|
||||
data: richFilesystem,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a markdown parser that caches the results of parsing.
|
||||
*/
|
||||
const createMarkdownParser = () => async (input: string) => {
|
||||
const cache = new Map<string, Promise<string>>();
|
||||
if (cache.has(input)) {
|
||||
return cache.get(input) as Promise<string>;
|
||||
}
|
||||
|
||||
const promise = parseMarkdown(input);
|
||||
cache.set(input, promise);
|
||||
return promise;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
openapi: 3.0.2
|
||||
info:
|
||||
title: OpenAPI spec
|
||||
version: 0.0.0
|
||||
servers:
|
||||
- url: '/api/v3'
|
||||
paths:
|
||||
'/pet':
|
||||
put:
|
||||
summary: Update an existing pet
|
||||
description: |
|
||||
Social platform
|
||||
operationId: updatePet
|
||||
requestBody:
|
||||
description: Update an existent pet in the store
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
example: 10
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
example: 10
|
||||
@@ -3,7 +3,7 @@ import typography from '@tailwindcss/typography';
|
||||
import type { Config } from 'tailwindcss';
|
||||
import plugin from 'tailwindcss/plugin';
|
||||
|
||||
import { ColorCategory, hexToRgb, scale, shadesOfColor } from './src/lib/colors';
|
||||
import { ColorCategory, hexToRgb, scale, shadesOfColor } from '@gitbook/colors';
|
||||
|
||||
export const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
export const opacities = [0, 4, 8, 12, 16, 24, 40, 64, 72, 88, 96, 100];
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @gitbook/openapi-parser
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6157583: Improve Markdown parsing
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.4",
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface OpenAPICustomSpecProperties {
|
||||
/**
|
||||
* Description in HTML format.
|
||||
*/
|
||||
'x-description-html'?: string;
|
||||
'x-gitbook-description-html'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ export interface OpenAPICustomOperationProperties {
|
||||
/**
|
||||
* Description in HTML format.
|
||||
*/
|
||||
'x-description-html'?: string;
|
||||
'x-gitbook-description-html'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @gitbook/react-openapi
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bb5c6a4: Support multiple response media types and examples
|
||||
- a3f1fea: Fix display of OpenAPI header description
|
||||
- 6157583: Improve Markdown parsing
|
||||
- 7419ee7: Show additional fields in OpenAPI block
|
||||
- 82cd9f2: Add support for anchor links in OpenAPI blocks
|
||||
- Updated dependencies [6157583]
|
||||
- @gitbook/openapi-parser@1.0.1
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -16,6 +16,7 @@
|
||||
"@scalar/oas-utils": "^0.2.101",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
"react-aria-components": "^1.6.0",
|
||||
"react-aria": "^3.37.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
|
||||
@@ -58,7 +58,9 @@ export function OpenAPICodeSample(props: {
|
||||
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
||||
method: data.method,
|
||||
body: requestBodyContent
|
||||
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
|
||||
? generateMediaTypeExample(requestBodyContent[1], {
|
||||
omitEmptyAndOptionalProperties: true,
|
||||
})
|
||||
: undefined,
|
||||
headers: {
|
||||
...getSecurityHeaders(data.securities),
|
||||
|
||||
@@ -25,14 +25,17 @@ export function OpenAPIOperation(props: {
|
||||
blockKey: context.blockKey,
|
||||
};
|
||||
|
||||
const description = resolveDescription(operation)?.trim();
|
||||
const description = resolveDescription(operation);
|
||||
|
||||
return (
|
||||
<div className={clsx('openapi-operation', className)}>
|
||||
<div className="openapi-summary" id={context.id}>
|
||||
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
||||
{operation.summary}
|
||||
</h2>
|
||||
<div className="openapi-summary">
|
||||
{operation.summary
|
||||
? context.renderHeading({
|
||||
deprecated: operation.deprecated ?? false,
|
||||
title: operation.summary,
|
||||
})
|
||||
: null}
|
||||
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
||||
</div>
|
||||
<div className="openapi-columns">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||
import { resolveDescription } from './utils';
|
||||
import { parameterToProperty, resolveDescription } from './utils';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
|
||||
@@ -27,13 +27,11 @@ export function OpenAPIResponse(props: {
|
||||
return (
|
||||
<div className="openapi-response-body">
|
||||
{headers.length > 0 ? (
|
||||
<OpenAPIDisclosure context={context} label={'Headers'}>
|
||||
<OpenAPIDisclosure context={context} label="Headers">
|
||||
<OpenAPISchemaProperties
|
||||
properties={headers.map(([name, header]) => ({
|
||||
propertyName: name,
|
||||
schema: header.schema ?? {},
|
||||
required: header.required,
|
||||
}))}
|
||||
properties={headers.map(([name, header]) => {
|
||||
return parameterToProperty({ name, ...header });
|
||||
})}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
@@ -41,11 +39,7 @@ export function OpenAPIResponse(props: {
|
||||
<div className="openapi-responsebody">
|
||||
<OpenAPISchemaProperties
|
||||
id={`response-${context.blockKey}`}
|
||||
properties={[
|
||||
{
|
||||
schema: mediaType.schema ?? {},
|
||||
},
|
||||
]}
|
||||
properties={mediaType.schema ? [{ schema: mediaType.schema }] : []}
|
||||
context={context}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { generateSchemaExample } from './generateSchemaExample';
|
||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||
import { InteractiveSection } from './InteractiveSection';
|
||||
import { json2xml } from './json2xml';
|
||||
|
||||
/**
|
||||
* Display an example of the response content.
|
||||
@@ -38,84 +38,51 @@ export function OpenAPIResponseExample(props: {
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
|
||||
const examples = responses
|
||||
.map(([key, value]) => {
|
||||
const responseObject = value;
|
||||
const mediaTypeObject = (() => {
|
||||
if (!responseObject.content) {
|
||||
return null;
|
||||
}
|
||||
const key = Object.keys(responseObject.content)[0];
|
||||
return (
|
||||
responseObject.content['application/json'] ??
|
||||
(key ? responseObject.content[key] : null)
|
||||
);
|
||||
})();
|
||||
const tabs = responses
|
||||
.map(([key, responseObject]) => {
|
||||
const description = resolveDescription(responseObject);
|
||||
|
||||
if (!mediaTypeObject) {
|
||||
if (checkIsReference(responseObject)) {
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description: resolveDescription(responseObject),
|
||||
body: <OpenAPIEmptyResponseExample />,
|
||||
description,
|
||||
body: (
|
||||
<OpenAPIExample
|
||||
example={getExampleFromReference(responseObject)}
|
||||
context={context}
|
||||
syntax="json"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const example = handleUnresolvedReference(
|
||||
(() => {
|
||||
const { examples, example } = mediaTypeObject;
|
||||
if (examples) {
|
||||
const key = Object.keys(examples)[0];
|
||||
if (key) {
|
||||
// @TODO handle multiple examples
|
||||
const firstExample = examples[key];
|
||||
if (firstExample) {
|
||||
return firstExample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (example) {
|
||||
return { value: example };
|
||||
}
|
||||
|
||||
const schema = mediaTypeObject.schema;
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value: generateSchemaExample(schema) };
|
||||
})(),
|
||||
);
|
||||
if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description,
|
||||
body: <OpenAPIEmptyResponseExample />,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description: resolveDescription(responseObject),
|
||||
body: example?.value ? (
|
||||
<context.CodeBlock
|
||||
code={
|
||||
typeof example.value === 'string'
|
||||
? example.value
|
||||
: stringifyOpenAPI(example.value, null, 2)
|
||||
}
|
||||
syntax="json"
|
||||
/>
|
||||
) : (
|
||||
<OpenAPIEmptyResponseExample />
|
||||
),
|
||||
body: <OpenAPIResponse context={context} content={responseObject.content} />,
|
||||
};
|
||||
})
|
||||
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
||||
Boolean(val),
|
||||
);
|
||||
|
||||
if (examples.length === 0) {
|
||||
if (tabs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={examples}>
|
||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
||||
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
@@ -123,6 +90,212 @@ export function OpenAPIResponseExample(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIResponse(props: {
|
||||
context: OpenAPIContextProps;
|
||||
content: {
|
||||
[media: string]: OpenAPIV3.MediaTypeObject;
|
||||
};
|
||||
}) {
|
||||
const { context, content } = props;
|
||||
|
||||
const entries = Object.entries(content);
|
||||
const firstEntry = entries[0];
|
||||
|
||||
if (!firstEntry) {
|
||||
throw new Error('One media type is required');
|
||||
}
|
||||
|
||||
if (entries.length === 1) {
|
||||
const [mediaType, mediaTypeObject] = firstEntry;
|
||||
return (
|
||||
<OpenAPIResponseMediaType
|
||||
context={context}
|
||||
mediaType={mediaType}
|
||||
mediaTypeObject={mediaTypeObject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = entries.map((entry) => {
|
||||
const [mediaType, mediaTypeObject] = entry;
|
||||
return {
|
||||
key: mediaType,
|
||||
label: mediaType,
|
||||
body: (
|
||||
<OpenAPIResponseMediaType
|
||||
context={context}
|
||||
mediaType={mediaType}
|
||||
mediaTypeObject={mediaTypeObject}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}>
|
||||
<InteractiveSection
|
||||
header={<OpenAPITabsList />}
|
||||
className="openapi-response-media-types"
|
||||
>
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
</OpenAPITabs>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIResponseMediaType(props: {
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
mediaType: string;
|
||||
context: OpenAPIContextProps;
|
||||
}) {
|
||||
const { mediaTypeObject, mediaType } = props;
|
||||
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
||||
const syntax = getSyntaxFromMediaType(mediaType);
|
||||
const firstExample = examples[0];
|
||||
|
||||
if (!firstExample) {
|
||||
return <OpenAPIEmptyResponseExample />;
|
||||
}
|
||||
|
||||
if (examples.length === 1) {
|
||||
return (
|
||||
<OpenAPIExample
|
||||
example={firstExample.example}
|
||||
context={props.context}
|
||||
syntax={syntax}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = examples.map((example) => {
|
||||
return {
|
||||
key: example.key,
|
||||
label: example.example.summary || example.key,
|
||||
body: (
|
||||
<OpenAPIExample
|
||||
example={firstExample.example}
|
||||
context={props.context}
|
||||
syntax={syntax}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
|
||||
<InteractiveSection
|
||||
header={<OpenAPITabsList />}
|
||||
className="openapi-response-media-type-examples"
|
||||
>
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
</OpenAPITabs>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an example.
|
||||
*/
|
||||
function OpenAPIExample(props: {
|
||||
example: OpenAPIV3.ExampleObject;
|
||||
context: OpenAPIContextProps;
|
||||
syntax: string;
|
||||
}) {
|
||||
const { example, context, syntax } = props;
|
||||
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
||||
|
||||
if (code === null) {
|
||||
return <OpenAPIEmptyResponseExample />;
|
||||
}
|
||||
|
||||
return <context.CodeBlock code={code} syntax={syntax} />;
|
||||
}
|
||||
|
||||
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
||||
const { example, xml } = args;
|
||||
|
||||
if (!example.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof example.value === 'string') {
|
||||
return example.value;
|
||||
}
|
||||
|
||||
if (xml) {
|
||||
return json2xml(example.value);
|
||||
}
|
||||
|
||||
return JSON.stringify(example.value, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples from a media type object.
|
||||
*/
|
||||
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',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: { value: generateSchemaExample(mediaTypeObject.schema) },
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty response example.
|
||||
*/
|
||||
function OpenAPIEmptyResponseExample() {
|
||||
return (
|
||||
<pre className="openapi-response-example-empty">
|
||||
@@ -131,15 +304,9 @@ function OpenAPIEmptyResponseExample() {
|
||||
);
|
||||
}
|
||||
|
||||
function handleUnresolvedReference(
|
||||
input: OpenAPIV3.ExampleObject | null,
|
||||
): OpenAPIV3.ExampleObject | null {
|
||||
const isReference = checkIsReference(input?.value);
|
||||
|
||||
if (isReference) {
|
||||
// If we find a reference that wasn't resolved or needed to be resolved externally, render out the URL
|
||||
return { value: input.value.$ref };
|
||||
}
|
||||
|
||||
return input;
|
||||
/**
|
||||
* Generate an example from a reference object.
|
||||
*/
|
||||
function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
||||
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
||||
|
||||
export interface OpenAPISchemaPropertyEntry {
|
||||
propertyName?: string;
|
||||
required?: boolean;
|
||||
propertyName?: string | undefined;
|
||||
required?: boolean | undefined;
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export function OpenAPISchemaProperty(
|
||||
? null
|
||||
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
||||
|
||||
if ((properties && !!properties.length) || schema.type === 'object') {
|
||||
if ((properties && properties.length > 0) || schema.type === 'object') {
|
||||
return (
|
||||
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
||||
<OpenAPISchemaPresentation {...props} />
|
||||
@@ -397,7 +397,7 @@ export function getSchemaTitle(
|
||||
let type = 'any';
|
||||
|
||||
if (schema.enum) {
|
||||
type = 'enum';
|
||||
type = `${schema.type} · enum`;
|
||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||
} else if (schema.type === 'array' && !!schema.items) {
|
||||
type = `${getSchemaTitle(schema.items)}[]`;
|
||||
@@ -407,7 +407,7 @@ export function getSchemaTitle(
|
||||
type = schema.type ?? 'object';
|
||||
|
||||
if (schema.format) {
|
||||
type += ` ${schema.format}`;
|
||||
type += ` · ${schema.format}`;
|
||||
}
|
||||
} else if ('anyOf' in schema) {
|
||||
type = 'any of';
|
||||
@@ -419,8 +419,20 @@ export function getSchemaTitle(
|
||||
type = 'not';
|
||||
}
|
||||
|
||||
if (schema.minimum || schema.minLength) {
|
||||
type += ` · min: ${schema.minimum || schema.minLength}`;
|
||||
}
|
||||
|
||||
if (schema.maximum || schema.maxLength) {
|
||||
type += ` · max: ${schema.maximum || schema.maxLength}`;
|
||||
}
|
||||
|
||||
if (schema.default) {
|
||||
type += ` · default: ${schema.default}`;
|
||||
}
|
||||
|
||||
if (schema.nullable) {
|
||||
type = `nullable ${type}`;
|
||||
type = `${type} | nullable`;
|
||||
}
|
||||
|
||||
return type;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OpenAPIResponses } from './OpenAPIResponses';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||
import { OpenAPISecurities } from './OpenAPISecurities';
|
||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
||||
import { resolveDescription } from './utils';
|
||||
import { parameterToProperty } from './utils';
|
||||
|
||||
/**
|
||||
* Client component to render the spec for the request and response.
|
||||
@@ -38,22 +38,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
||||
header={group.label}
|
||||
>
|
||||
<OpenAPISchemaProperties
|
||||
properties={group.parameters.map((parameter) => {
|
||||
const description = resolveDescription(parameter);
|
||||
return {
|
||||
propertyName: parameter.name,
|
||||
schema: {
|
||||
// Description of the parameter is defined at the parameter level
|
||||
// we use display it if the schema doesn't override it
|
||||
description: description,
|
||||
example: parameter.example,
|
||||
// Deprecated can be defined at the parameter level
|
||||
deprecated: parameter.deprecated,
|
||||
...(parameter.schema ?? {}),
|
||||
},
|
||||
required: parameter.required,
|
||||
};
|
||||
})}
|
||||
properties={group.parameters.map(parameterToProperty)}
|
||||
context={context}
|
||||
/>
|
||||
</InteractiveSection>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getUrlFromServerState indents correctly 1`] = `
|
||||
"<?xml version="1.0"?>
|
||||
<id>10</id>
|
||||
<name>doggie</name>
|
||||
<category>
|
||||
<id>1</id>
|
||||
<name>Dogs</name>
|
||||
</category>
|
||||
<photoUrls>string</photoUrls>
|
||||
<tags>
|
||||
<id>0</id>
|
||||
<name>string</name>
|
||||
</tags>
|
||||
<status>available</status>
|
||||
"
|
||||
`;
|
||||
@@ -3,18 +3,21 @@ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
||||
|
||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||
|
||||
type ScalarGetExampleFromSchemaOptions = NonNullable<Parameters<typeof getExampleFromSchema>[1]>;
|
||||
type GenerateSchemaExampleOptions = Pick<
|
||||
ScalarGetExampleFromSchemaOptions,
|
||||
'xml' | 'omitEmptyAndOptionalProperties' | 'mode'
|
||||
>;
|
||||
|
||||
/**
|
||||
* Generate a JSON example from a schema
|
||||
*/
|
||||
export function generateSchemaExample(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
options: {
|
||||
onlyRequired?: boolean;
|
||||
} = {},
|
||||
options?: GenerateSchemaExampleOptions,
|
||||
): JSONValue | undefined {
|
||||
return getExampleFromSchema(schema, {
|
||||
emptyString: 'text',
|
||||
omitEmptyAndOptionalProperties: options.onlyRequired,
|
||||
variables: {
|
||||
'date-time': new Date().toISOString(),
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
@@ -28,6 +31,7 @@ export function generateSchemaExample(
|
||||
byte: 'Ynl0ZXM=',
|
||||
password: 'password',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,9 +40,7 @@ export function generateSchemaExample(
|
||||
*/
|
||||
export function generateMediaTypeExample(
|
||||
mediaType: OpenAPIV3.MediaTypeObject,
|
||||
options: {
|
||||
onlyRequired?: boolean;
|
||||
} = {},
|
||||
options?: GenerateSchemaExampleOptions,
|
||||
): JSONValue | undefined {
|
||||
if (mediaType.example) {
|
||||
return mediaType.example;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { json2xml } from './json2xml';
|
||||
|
||||
describe('getUrlFromServerState', () => {
|
||||
it('transforms JSON to xml', () => {
|
||||
const xml = json2xml({
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
expect(xml).toBe('<?xml version="1.0"?>\n<foo>bar</foo>\n');
|
||||
});
|
||||
|
||||
it('wraps array items', () => {
|
||||
const xml = json2xml({
|
||||
urls: {
|
||||
url: ['https://example.com', 'https://example.com'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(xml).toBe(
|
||||
'<?xml version="1.0"?>\n<urls>\n\t<url>https://example.com</url>\n\t<url>https://example.com</url>\n</urls>\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('indents correctly', () => {
|
||||
const xml = json2xml({
|
||||
id: 10,
|
||||
name: 'doggie',
|
||||
category: {
|
||||
id: 1,
|
||||
name: 'Dogs',
|
||||
},
|
||||
photoUrls: ['string'],
|
||||
tags: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'string',
|
||||
},
|
||||
],
|
||||
status: 'available',
|
||||
});
|
||||
|
||||
expect(xml).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { jsXml } from 'json-xml-parse';
|
||||
|
||||
/**
|
||||
* This function converts an object to XML.
|
||||
*/
|
||||
export function json2xml(data: Record<string, any>) {
|
||||
return jsXml.toXmlString(data, { beautify: true });
|
||||
}
|
||||
@@ -9,7 +9,7 @@ async function fetchFilesystem(url: string) {
|
||||
const filesystem = await parseOpenAPI({ value: text, rootURL: url });
|
||||
const transformedFs = await traverse(filesystem, async (node) => {
|
||||
if ('description' in node && typeof node.description === 'string' && node.description) {
|
||||
node['x-description-html'] = node.description;
|
||||
node['x-gitbook-description-html'] = node.description;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
|
||||
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
||||
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
||||
renderHeading: (props: { deprecated: boolean; title: string }) => React.ReactNode;
|
||||
|
||||
/** Spec url for the Scalar Api Client */
|
||||
specUrl: string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||
|
||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||
export function checkIsReference(
|
||||
input: unknown,
|
||||
): input is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
|
||||
return typeof input === 'object' && !!input && '$ref' in input;
|
||||
}
|
||||
|
||||
@@ -12,9 +14,83 @@ export function createStateKey(key: string, scope?: string) {
|
||||
* Resolve the description of an object.
|
||||
*/
|
||||
export function resolveDescription(object: AnyObject) {
|
||||
return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
|
||||
? object['x-description-html']
|
||||
return 'x-gitbook-description-html' in object &&
|
||||
typeof object['x-gitbook-description-html'] === 'string'
|
||||
? object['x-gitbook-description-html'].trim()
|
||||
: typeof object.description === 'string'
|
||||
? object.description
|
||||
? object.description.trim()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract descriptions from an object.
|
||||
*/
|
||||
export function extractDescriptions(object: AnyObject) {
|
||||
return {
|
||||
description: object.description,
|
||||
['x-gitbook-description-html']:
|
||||
'x-gitbook-description-html' in object
|
||||
? object['x-gitbook-description-html']
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the first example from an object.
|
||||
*/
|
||||
export function resolveFirstExample(object: AnyObject) {
|
||||
if ('examples' in object && typeof object.examples === 'object' && object.examples) {
|
||||
const keys = Object.keys(object.examples);
|
||||
const firstKey = keys[0];
|
||||
if (firstKey && object.examples[firstKey]) {
|
||||
return object.examples[firstKey];
|
||||
}
|
||||
}
|
||||
if ('example' in object && object.example !== undefined) {
|
||||
return object.example;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the schema of a parameter.
|
||||
* Extract the description, example and deprecated from parameter.
|
||||
*/
|
||||
export function resolveParameterSchema(
|
||||
parameter: OpenAPIV3.ParameterBaseObject,
|
||||
): OpenAPIV3.SchemaObject {
|
||||
const schema = checkIsReference(parameter.schema) ? undefined : parameter.schema;
|
||||
return {
|
||||
// Description of the parameter is defined at the parameter level
|
||||
// we use display it if the schema doesn't override it
|
||||
...extractDescriptions(parameter),
|
||||
example: resolveFirstExample(parameter),
|
||||
// Deprecated can be defined at the parameter level
|
||||
deprecated: parameter.deprecated,
|
||||
...schema,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a parameter object to a property object.
|
||||
*/
|
||||
export function parameterToProperty(
|
||||
parameter: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject,
|
||||
): {
|
||||
propertyName: string | undefined;
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
required: boolean | undefined;
|
||||
} {
|
||||
if (checkIsReference(parameter)) {
|
||||
return {
|
||||
propertyName: parameter.$ref ?? 'Unknown ref',
|
||||
schema: {},
|
||||
required: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
propertyName: parameter.name,
|
||||
schema: resolveParameterSchema(parameter),
|
||||
required: parameter.required,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user