Compare commits

..

52 Commits

Author SHA1 Message Date
Samy Pessé 938125e772 Version Packages (#3180)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-19 19:10:57 +02:00
conico974 1c8d9febb3 Custom Incremental Cache (#3239)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-19 17:00:09 +02:00
conico974 223edae17d Add env variables for the DOQueue (#3243)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-19 13:47:33 +02:00
Nolann B. 12a455dab5 Fix OpenAPI layout issues (#3242) 2025-05-18 14:50:05 +02:00
Nolann B. d00dc8ca72 Pass spec validation errors through OpenAPIParseError (#3240) 2025-05-18 13:20:17 +02:00
Samy Pessé 2c85a302fc Update opennext and leverage "use cache" on CF (#3204)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-16 13:58:35 +01:00
Addison 3460b78b45 Update docs.gitbook.com to gitbook.com/docs (#3187)
Co-authored-by: Tal Gluck <talagluck@gmail.com>
Co-authored-by: Taran Vohra <taranvohra@outlook.com>
2025-05-16 17:59:53 +05:30
Zeno Kapitein 95a1f652f9 Better print layouts: wrap code blocks & force table column auto-sizing (#3236) 2025-05-15 13:24:10 +02:00
spastorelli cc37e2ae15 Persist visitor params values in public visitor session cookie (#3231) 2025-05-14 14:16:13 +02:00
Claire Chabas 08382ce62f Improve tabs link support (#3211) 2025-05-14 11:10:09 +02:00
Nolann B. 80cb52a237 Handle OpenAPI alternatives from schema.items (#3235) 2025-05-14 08:05:08 +02:00
Nolann B. c6637b09e2 Use default value if string/number/boolean in generateSchemaExample (#3234) 2025-05-13 22:07:41 +02:00
Samy Pessé 778624af00 Only resize images with supported extensions (#3229) 2025-05-13 10:52:58 +02:00
Samy Pessé e15757d01f Use next@15.3.2 instead of canary (#3227) 2025-05-09 15:17:49 +02:00
Zeno Kapitein aed79fd6c3 Remove header logo rounding (#3225) 2025-05-09 14:45:38 +02:00
Greg Bergé 0c973a3292 Always link main logo to the root of the site (#3226) 2025-05-09 10:31:52 +00:00
spastorelli 56c923c0ed Adapt site resolution/lookup to pass visitor unsigned claims in addition to token (#3202)
Co-authored-by: taranvohra <taranvohra@outlook.com>
2025-05-09 15:34:20 +05:30
Samy Pessé 7d7806df30 Passthrough SVG images in image resizing (#3224) 2025-05-08 18:57:50 +02:00
Nolann B. cb5598dc19 Handle invalid OpenAPI Responses (#3223) 2025-05-08 11:54:46 +02:00
Greg Bergé 47f01edcb8 Fix E2E icons (#3222) 2025-05-08 13:04:04 +05:30
Greg Bergé e6ddc0f07f Fix sitemap URLs (#3220) 2025-05-08 08:54:28 +02:00
Zeno Kapitein 373f18f451 Prevent section group popovers from opening on click (#3213) 2025-05-07 13:57:58 +00:00
Taran Vohra 3f292065cd Update site redirect regex for validation (#3216) 2025-05-07 12:42:28 +05:30
Greg Bergé 5e975ab95b Fix code highlighting for HTTP (#3214) 2025-05-06 13:41:15 +00:00
Greg Bergé a3ec264764 Fix Python code sample "null vs None" (#3215) 2025-05-06 15:40:56 +02:00
Samy Pessé 5d504ffa4c Fix resolution of links in reusable content (#3212) 2025-05-06 10:30:55 +02:00
Samy Pessé 04999662db Use absolute URLs in sitemap.xml when pointing to sitemap-pages.xml (#3210) 2025-05-05 09:03:28 +02:00
Zeno Kapitein f7a34706c7 Allow high-contrast input colors to be used as-is (#3209) 2025-05-02 19:41:05 +02:00
Taran Vohra f328a41982 Revert getPublishedContentByUrl timeout back to 10s (#3208) 2025-05-02 11:16:31 +00:00
Nolann B. 20ebecb114 Missing top-level required OpenAPI alternatives (#3207) 2025-05-02 11:48:15 +02:00
Taran Vohra 5a69692f54 Temporarily bump getPublishedContentByUrl timeout to 30s on v1 (#3199) 2025-04-30 19:14:09 +05:30
Nolann B. 42ca7e1580 Fix openapi CR preview (#3197) 2025-04-30 11:44:39 +02:00
Nolann B. c4ebb3fecd Fix openapi-select hover in responses (#3196) 2025-04-30 11:27:03 +02:00
Zeno Kapitein 2a805cc082 Change OpenAPI schema-optional from info to tint color (#3194) 2025-04-29 19:30:11 +02:00
Zeno Kapitein 89a5816ee4 Fix OpenAPI disclosure label ("Show properties") misalignment on mobile (#3193) 2025-04-29 19:29:56 +02:00
Zeno Kapitein c3f6b8c003 Reduce chroma of text layers (#3188) 2025-04-29 19:29:42 +02:00
Zeno Kapitein 0e201d5899 Add border to filled sidebar on gradient theme (#3185) 2025-04-29 19:29:32 +02:00
Zeno Kapitein ae5f1abc58 Change Dropdowns to use Radix's DropdownMenu (#3189) 2025-04-29 18:30:47 +02:00
Nolann B. 580101d04e Schemas disclosure label causing client error (#3192) 2025-04-29 16:16:58 +02:00
Johan Preynat 81b4a4db53 Allow /~gitbook/image and /~gitbook/icon despite querystring parameters for SEO in robots.txt (#3191) 2025-04-28 12:07:34 +02:00
Steven H 8339e91e2b Fix images and other content refs in reusable content across spaces. (#3190) 2025-04-25 15:28:09 +01:00
Steven H 3119066728 Support for reusable content across spaces. (#3173) 2025-04-24 16:54:11 +01:00
Zeno Kapitein 326e28e9b0 Restyle openapi blocks (#3169) 2025-04-24 16:53:29 +02:00
Johan Preynat 90f0127ada Use react@19 on v2 and v1 (#3184) 2025-04-24 16:53:23 +02:00
Viktor Renkema 0353a8186c Have all nodes within a text-cells adhere to alignments (#3175) 2025-04-24 11:43:20 +02:00
Steven H dd043df00f Revert investigation work around URL caches. (#3183) 2025-04-23 16:53:33 +01:00
Steven H 97b7c79bc6 Increase logging around caching behaviour causing page crashes. (#3182) 2025-04-23 15:04:00 +01:00
Steven H 634e0b4302 Improve error messages around undefined site sections. (#3179) 2025-04-23 11:47:28 +00:00
Greg Bergé 2a23f1f003 Improve performances of TOC (#3176) 2025-04-23 09:31:22 +02:00
Samy Pessé 88ffdcc303 Version Packages (#3172)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-22 11:20:21 +02:00
Nolann B. b6b09d42af Fix OpenAPI responses select placement and icon (#3174) 2025-04-22 10:59:42 +02:00
Nolann B. ebc39e9db9 Missing select icon (#3171) 2025-04-18 16:26:59 +02:00
115 changed files with 3451 additions and 2524 deletions
+2 -2
View File
@@ -63,9 +63,9 @@ To start your local version of GitBook, run the command `bun dev`.
When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/`. When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/`.
For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/docs.gitbook.com` after running the development server. For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/gitbook.com/docs` after running the development server.
You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://docs.gitbook.com/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version. You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://gitbook.com/docs/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version.
### Commit your update ### Commit your update
+1 -3
View File
@@ -152,7 +152,7 @@ jobs:
| Site | `v1` | `2v` | `2c` | | Site | `v1` | `2v` | `2c` |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| GitBook | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/docs.gitbook.com) | | GitBook | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.com/docs](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.com/docs) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs) |
| E2E | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | | E2E | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) |
edit-mode: replace edit-mode: replace
visual-testing-v1: visual-testing-v1:
@@ -200,7 +200,6 @@ jobs:
name: Visual Testing v2 (Cloudflare) name: Visual Testing v2 (Cloudflare)
needs: deploy-v2-cloudflare needs: deploy-v2-cloudflare
timeout-minutes: 10 timeout-minutes: 10
if: startsWith(github.head_ref || github.ref_name, 'cloudflare/') || github.ref == 'refs/heads/main'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -263,7 +262,6 @@ jobs:
name: Visual Testing Customers v2 (Cloudflare) name: Visual Testing Customers v2 (Cloudflare)
needs: deploy-v2-cloudflare needs: deploy-v2-cloudflare
timeout-minutes: 8 timeout-minutes: 8
if: startsWith(github.head_ref || github.ref_name, 'cloudflare/') || github.ref == 'refs/heads/main'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
+1
View File
@@ -9,6 +9,7 @@
], ],
"tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"], "tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"],
"prettier.enable": false, "prettier.enable": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome", "editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit", "source.organizeImports.biome": "explicit",
+2 -2
View File
@@ -1,7 +1,7 @@
<h1 align="center">GitBook</h1> <h1 align="center">GitBook</h1>
<p align="center"> <p align="center">
<a href="https://docs.gitbook.com/">Docs</a> - <a href="https://github.com/GitbookIO/community">Community</a> - <a href="https://developer.gitbook.com/">Developer Docs</a> - <a href="https://changelog.gitbook.com/">Changelog</a> - <a href="https://github.com/GitbookIO/gitbook/issues/new?assignees=&labels=bug&template=bug_report.md">Bug reports</a> <a href="https://gitbook.com/docs/">Docs</a> - <a href="https://github.com/GitbookIO/community">Community</a> - <a href="https://developer.gitbook.com/">Developer Docs</a> - <a href="https://changelog.gitbook.com/">Changelog</a> - <a href="https://github.com/GitbookIO/gitbook/issues/new?assignees=&labels=bug&template=bug_report.md">Bug reports</a>
</p> </p>
<p align="center"> <p align="center">
@@ -66,7 +66,7 @@ bun dev:v2
examples: examples:
- http://localhost:3000/url/docs.gitbook.com - http://localhost:3000/url/gitbook.com/docs
- http://localhost:3000/url/open-source.gitbook.io/midjourney - http://localhost:3000/url/open-source.gitbook.io/midjourney
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser. Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
+159 -164
View File
@@ -26,7 +26,7 @@
"name": "@gitbook/cache-tags", "name": "@gitbook/cache-tags",
"version": "0.3.1", "version": "0.3.1",
"dependencies": { "dependencies": {
"@gitbook/api": "^0.111.0", "@gitbook/api": "^0.115.0",
"assert-never": "^1.2.1", "assert-never": "^1.2.1",
}, },
"devDependencies": { "devDependencies": {
@@ -49,9 +49,9 @@
}, },
"packages/gitbook": { "packages/gitbook": {
"name": "gitbook", "name": "gitbook",
"version": "0.10.1", "version": "0.11.1",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.115.0",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*", "@gitbook/colors": "workspace:*",
@@ -62,6 +62,7 @@
"@gitbook/react-math": "workspace:*", "@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*", "@gitbook/react-openapi": "workspace:*",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-navigation-menu": "^1.2.3", "@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.1.8",
@@ -89,8 +90,8 @@
"p-map": "^7.0.0", "p-map": "^7.0.0",
"parse-cache-control": "^1.0.1", "parse-cache-control": "^1.0.1",
"partial-json": "^0.1.7", "partial-json": "^0.1.7",
"react": "18.3.1", "react": "^19.0.0",
"react-dom": "18.3.1", "react-dom": "^19.0.0",
"react-hotkeys-hook": "^4.4.1", "react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1", "rehype-stringify": "^10.0.1",
@@ -111,7 +112,7 @@
}, },
"devDependencies": { "devDependencies": {
"@argos-ci/playwright": "^5.0.3", "@argos-ci/playwright": "^5.0.3",
"@cloudflare/next-on-pages": "1.13.7", "@cloudflare/next-on-pages": "1.13.12",
"@cloudflare/workers-types": "^4.20241230.0", "@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
@@ -142,12 +143,13 @@
"name": "gitbook-v2", "name": "gitbook-v2",
"version": "0.2.5", "version": "0.2.5",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.115.0",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@opennextjs/cloudflare": "1.0.3",
"@sindresorhus/fnv1a": "^3.1.0", "@sindresorhus/fnv1a": "^3.1.0",
"assert-never": "^1.2.1", "assert-never": "^1.2.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"next": "canary", "next": "^15.3.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"rison": "^0.1.1", "rison": "^0.1.1",
@@ -155,7 +157,6 @@
"warn-once": "^0.1.1", "warn-once": "^0.1.1",
}, },
"devDependencies": { "devDependencies": {
"@opennextjs/cloudflare": "^1.0.0-beta.3",
"@types/rison": "^0.0.9", "@types/rison": "^0.0.9",
"gitbook": "*", "gitbook": "*",
"postcss": "^8", "postcss": "^8",
@@ -201,7 +202,7 @@
"name": "@gitbook/react-contentkit", "name": "@gitbook/react-contentkit",
"version": "0.7.0", "version": "0.7.0",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.115.0",
"@gitbook/icons": "workspace:*", "@gitbook/icons": "workspace:*",
"classnames": "^2.5.1", "classnames": "^2.5.1",
}, },
@@ -231,7 +232,7 @@
}, },
"packages/react-openapi": { "packages/react-openapi": {
"name": "@gitbook/react-openapi", "name": "@gitbook/react-openapi",
"version": "1.1.10", "version": "1.2.1",
"dependencies": { "dependencies": {
"@gitbook/openapi-parser": "workspace:*", "@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.2.19", "@scalar/api-client-react": "^1.2.19",
@@ -259,9 +260,9 @@
}, },
"overrides": { "overrides": {
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"@gitbook/api": "0.111.0", "@gitbook/api": "^0.115.0",
"react": "18.3.1", "react": "^19.0.0",
"react-dom": "18.3.1", "react-dom": "^19.0.0",
}, },
"packages": { "packages": {
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="], "@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
@@ -472,7 +473,7 @@
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
"@cloudflare/next-on-pages": ["@cloudflare/next-on-pages@1.13.7", "", { "dependencies": { "acorn": "^8.8.0", "ast-types": "^0.14.2", "chalk": "^5.2.0", "chokidar": "^3.5.3", "commander": "^11.1.0", "cookie": "^0.5.0", "esbuild": "^0.15.3", "js-yaml": "^4.1.0", "miniflare": "^3.20231218.1", "package-manager-manager": "^0.2.0", "pcre-to-regexp": "^1.1.0", "semver": "^7.5.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240208.0", "vercel": ">=30.0.0", "wrangler": "^3.28.2" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "next-on-pages": "bin/index.js" } }, "sha512-TSMVy+1fmxzeyykOC9guMEj7G2FgENw1T8V1sIFnah6piaJNBmybdyikPdQSdikP5w6v9eQhBt/TrXDPMDw0dw=="], "@cloudflare/next-on-pages": ["@cloudflare/next-on-pages@1.13.12", "", { "dependencies": { "acorn": "^8.8.0", "ast-types": "^0.14.2", "chalk": "^5.2.0", "chokidar": "^3.5.3", "commander": "^11.1.0", "cookie": "^0.5.0", "esbuild": "^0.15.3", "js-yaml": "^4.1.0", "miniflare": "^3.20231218.1", "package-manager-manager": "^0.2.0", "pcre-to-regexp": "^1.1.0", "semver": "^7.5.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240208.0", "vercel": ">=30.0.0", "wrangler": "^3.28.2 || ^4.0.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "next-on-pages": "bin/index.js" } }, "sha512-rPy7x9c2+0RDDdJ5o0TeRUwXJ1b7N1epnqF6qKSp5Wz1r9KHOyvaZh1ACoOC6Vu5k9su5WZOgy+8fPLIyrldMQ=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250320.0" }, "optionalPeers": ["workerd"] }, "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250320.0" }, "optionalPeers": ["workerd"] }, "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg=="],
@@ -546,10 +547,6 @@
"@emotion/memoize": ["@emotion/memoize@0.7.4", "", {}, "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="], "@emotion/memoize": ["@emotion/memoize@0.7.4", "", {}, "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="],
"@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="],
"@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
@@ -628,7 +625,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="], "@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.115.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-Lyj+1WVNnE/Zuuqa/1ZdnUQfUiNE6es89RFK6CJ+Tb36TFwls6mbHKXCZsBwSYyoMYTVK39WQ3Nob6Nw6+TWCA=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"], "@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -794,9 +791,9 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@opennextjs/aws": ["@opennextjs/aws@3.5.7", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-YjyHJrkIHI7YwQRCp8GjDOudu86oOc1RiwxvBBpPHrplsS18H4ZmkzGggAKhK6B4myGsJQ/q9kNP2TraoZiNzg=="], "@opennextjs/aws": ["@opennextjs/aws@3.6.1", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-RYU9K58vEUPXqc3pZO6kr9vBy1MmJZFQZLe0oXBskC005oGju/m4e3DCCP4eZ/Q/HdYQXCoqNXgSGi8VCAYgew=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.0.0-beta.3", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.5.7", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^3.114.3 || ^4.7.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-qKBXQZhUeQ+iGvfJeF7PO30g59LHnPOlRVZd77zxwn6Uc9C+c0LSwo8N28XRIWyQPkY007rKk9pSIxOrP4MHtQ=="], "@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.0.3", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "^3.6.1", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^4.14.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-+SNhz2JmKkMlWzndTSYZeq0eseAPfEXUUYJA7H9MkgFe36i1no0OdiLuwu9e4uR3yuOzaNWB+skCYHsD6Im9nA=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -822,11 +819,15 @@
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="], "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.12", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VJoMs+BWWE7YhzEQyVwvF9n22Eiyr83HotCVrMQzla/OwRovXCgah7AcaEr4hMNj4gJxSdtIbcHGvmJXOoJVHA=="],
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="], "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA=="], "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="], "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.4", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.7", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.4", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.4", "@radix-ui/react-portal": "1.1.6", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-roving-focus": "1.1.7", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+qYq6LfbiGo97Zz9fioX83HCiIYYFNs8zAsVCMQrIakoNYylIzWuoD/anAD3UzvvR6cnswmfRFJFq/zYYq/k7Q=="],
"@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wUi01RrTDTOoGtjEPHsxlzPtVzVc3R/AZ5wfh0dyqMAqolhHAHvG5iQjBCTi2AjQqa77FWWbA3kE3RkD+bDMgQ=="], "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wUi01RrTDTOoGtjEPHsxlzPtVzVc3R/AZ5wfh0dyqMAqolhHAHvG5iQjBCTi2AjQqa77FWWbA3kE3RkD+bDMgQ=="],
@@ -840,6 +841,8 @@
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.0", "", { "dependencies": { "@radix-ui/react-slot": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw=="], "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.0", "", { "dependencies": { "@radix-ui/react-slot": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.4", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw=="],
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="], "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="],
@@ -848,6 +851,8 @@
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="], "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="],
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="], "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="],
@@ -1094,7 +1099,7 @@
"@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=="], "@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=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.10", "", { "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-6MSgvpNKu/anZy96dn8tXQZo1PuDCoeB4m2ZLLDS4vC2zaTnuNBvvQHx+gjwXNKWhTbIVy8bQpYBzlMAYnFNcQ=="], "@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/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="], "@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
@@ -1390,7 +1395,7 @@
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.12.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg=="], "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
@@ -1510,8 +1515,6 @@
"caniuse-lite": ["caniuse-lite@1.0.30001668", "", {}, "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw=="], "caniuse-lite": ["caniuse-lite@1.0.30001668", "", {}, "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw=="],
"capnp-ts": ["capnp-ts@0.7.0", "", { "dependencies": { "debug": "^4.3.1", "tslib": "^2.2.0" } }, "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
@@ -1564,8 +1567,6 @@
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"configstore": ["configstore@5.0.1", "", { "dependencies": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", "make-dir": "^3.0.0", "unique-string": "^2.0.0", "write-file-atomic": "^3.0.0", "xdg-basedir": "^4.0.0" } }, "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA=="], "configstore": ["configstore@5.0.1", "", { "dependencies": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", "make-dir": "^3.0.0", "unique-string": "^2.0.0", "write-file-atomic": "^3.0.0", "xdg-basedir": "^4.0.0" } }, "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA=="],
"consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="], "consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="],
@@ -1756,7 +1757,7 @@
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="], "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
@@ -2308,8 +2309,6 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="],
"mnemonist": ["mnemonist@0.38.3", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="], "mnemonist": ["mnemonist@0.38.3", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
@@ -2454,8 +2453,6 @@
"pirates": ["pirates@4.0.6", "", {}, "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="], "pirates": ["pirates@4.0.6", "", {}, "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="],
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"playwright": ["playwright@1.51.1", "", { "dependencies": { "playwright-core": "1.51.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw=="], "playwright": ["playwright@1.51.1", "", { "dependencies": { "playwright-core": "1.51.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw=="],
"playwright-core": ["playwright-core@1.51.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw=="], "playwright-core": ["playwright-core@1.51.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw=="],
@@ -2518,13 +2515,13 @@
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
"react-aria": ["react-aria@3.37.0", "", { "dependencies": { "@internationalized/string": "^3.2.5", "@react-aria/breadcrumbs": "^3.5.20", "@react-aria/button": "^3.11.1", "@react-aria/calendar": "^3.7.0", "@react-aria/checkbox": "^3.15.1", "@react-aria/color": "^3.0.3", "@react-aria/combobox": "^3.11.1", "@react-aria/datepicker": "^3.13.0", "@react-aria/dialog": "^3.5.21", "@react-aria/disclosure": "^3.0.1", "@react-aria/dnd": "^3.8.1", "@react-aria/focus": "^3.19.1", "@react-aria/gridlist": "^3.10.1", "@react-aria/i18n": "^3.12.5", "@react-aria/interactions": "^3.23.0", "@react-aria/label": "^3.7.14", "@react-aria/link": "^3.7.8", "@react-aria/listbox": "^3.14.0", "@react-aria/menu": "^3.17.0", "@react-aria/meter": "^3.4.19", "@react-aria/numberfield": "^3.11.10", "@react-aria/overlays": "^3.25.0", "@react-aria/progress": "^3.4.19", "@react-aria/radio": "^3.10.11", "@react-aria/searchfield": "^3.8.0", "@react-aria/select": "^3.15.1", "@react-aria/selection": "^3.22.0", "@react-aria/separator": "^3.4.5", "@react-aria/slider": "^3.7.15", "@react-aria/ssr": "^3.9.7", "@react-aria/switch": "^3.6.11", "@react-aria/table": "^3.16.1", "@react-aria/tabs": "^3.9.9", "@react-aria/tag": "^3.4.9", "@react-aria/textfield": "^3.16.0", "@react-aria/tooltip": "^3.7.11", "@react-aria/utils": "^3.27.0", "@react-aria/visually-hidden": "^3.8.19", "@react-types/shared": "^3.27.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-u3WUEMTcbQFaoHauHO3KhPaBYzEv1o42EdPcLAs05GBw9Q6Axlqwo73UFgMrsc2ElwLAZ4EKpSdWHLo1R5gfiw=="], "react-aria": ["react-aria@3.37.0", "", { "dependencies": { "@internationalized/string": "^3.2.5", "@react-aria/breadcrumbs": "^3.5.20", "@react-aria/button": "^3.11.1", "@react-aria/calendar": "^3.7.0", "@react-aria/checkbox": "^3.15.1", "@react-aria/color": "^3.0.3", "@react-aria/combobox": "^3.11.1", "@react-aria/datepicker": "^3.13.0", "@react-aria/dialog": "^3.5.21", "@react-aria/disclosure": "^3.0.1", "@react-aria/dnd": "^3.8.1", "@react-aria/focus": "^3.19.1", "@react-aria/gridlist": "^3.10.1", "@react-aria/i18n": "^3.12.5", "@react-aria/interactions": "^3.23.0", "@react-aria/label": "^3.7.14", "@react-aria/link": "^3.7.8", "@react-aria/listbox": "^3.14.0", "@react-aria/menu": "^3.17.0", "@react-aria/meter": "^3.4.19", "@react-aria/numberfield": "^3.11.10", "@react-aria/overlays": "^3.25.0", "@react-aria/progress": "^3.4.19", "@react-aria/radio": "^3.10.11", "@react-aria/searchfield": "^3.8.0", "@react-aria/select": "^3.15.1", "@react-aria/selection": "^3.22.0", "@react-aria/separator": "^3.4.5", "@react-aria/slider": "^3.7.15", "@react-aria/ssr": "^3.9.7", "@react-aria/switch": "^3.6.11", "@react-aria/table": "^3.16.1", "@react-aria/tabs": "^3.9.9", "@react-aria/tag": "^3.4.9", "@react-aria/textfield": "^3.16.0", "@react-aria/tooltip": "^3.7.11", "@react-aria/utils": "^3.27.0", "@react-aria/visually-hidden": "^3.8.19", "@react-types/shared": "^3.27.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-u3WUEMTcbQFaoHauHO3KhPaBYzEv1o42EdPcLAs05GBw9Q6Axlqwo73UFgMrsc2ElwLAZ4EKpSdWHLo1R5gfiw=="],
"react-aria-components": ["react-aria-components@1.6.0", "", { "dependencies": { "@internationalized/date": "^3.7.0", "@internationalized/string": "^3.2.5", "@react-aria/autocomplete": "3.0.0-alpha.37", "@react-aria/collections": "3.0.0-alpha.7", "@react-aria/color": "^3.0.3", "@react-aria/disclosure": "^3.0.1", "@react-aria/dnd": "^3.8.1", "@react-aria/focus": "^3.19.1", "@react-aria/interactions": "^3.23.0", "@react-aria/live-announcer": "^3.4.1", "@react-aria/menu": "^3.17.0", "@react-aria/toolbar": "3.0.0-beta.12", "@react-aria/tree": "3.0.0-beta.3", "@react-aria/utils": "^3.27.0", "@react-aria/virtualizer": "^4.1.1", "@react-stately/autocomplete": "3.0.0-alpha.0", "@react-stately/color": "^3.8.2", "@react-stately/disclosure": "^3.0.1", "@react-stately/layout": "^4.1.1", "@react-stately/menu": "^3.9.1", "@react-stately/selection": "^3.19.0", "@react-stately/table": "^3.13.1", "@react-stately/utils": "^3.10.5", "@react-stately/virtualizer": "^4.2.1", "@react-types/color": "^3.0.2", "@react-types/form": "^3.7.9", "@react-types/grid": "^3.2.11", "@react-types/shared": "^3.27.0", "@react-types/table": "^3.10.4", "@swc/helpers": "^0.5.0", "client-only": "^0.0.1", "react-aria": "^3.37.0", "react-stately": "^3.35.0", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-YfG9PUE7XrXtDDAqT4pLTGyYQaiHHTBFdAK/wNgGsypVnQSdzmyYlV3Ty8aHlZJI6hP9RWkbywvosXkU7KcPHg=="], "react-aria-components": ["react-aria-components@1.6.0", "", { "dependencies": { "@internationalized/date": "^3.7.0", "@internationalized/string": "^3.2.5", "@react-aria/autocomplete": "3.0.0-alpha.37", "@react-aria/collections": "3.0.0-alpha.7", "@react-aria/color": "^3.0.3", "@react-aria/disclosure": "^3.0.1", "@react-aria/dnd": "^3.8.1", "@react-aria/focus": "^3.19.1", "@react-aria/interactions": "^3.23.0", "@react-aria/live-announcer": "^3.4.1", "@react-aria/menu": "^3.17.0", "@react-aria/toolbar": "3.0.0-beta.12", "@react-aria/tree": "3.0.0-beta.3", "@react-aria/utils": "^3.27.0", "@react-aria/virtualizer": "^4.1.1", "@react-stately/autocomplete": "3.0.0-alpha.0", "@react-stately/color": "^3.8.2", "@react-stately/disclosure": "^3.0.1", "@react-stately/layout": "^4.1.1", "@react-stately/menu": "^3.9.1", "@react-stately/selection": "^3.19.0", "@react-stately/table": "^3.13.1", "@react-stately/utils": "^3.10.5", "@react-stately/virtualizer": "^4.2.1", "@react-types/color": "^3.0.2", "@react-types/form": "^3.7.9", "@react-types/grid": "^3.2.11", "@react-types/shared": "^3.27.0", "@react-types/table": "^3.10.4", "@swc/helpers": "^0.5.0", "client-only": "^0.0.1", "react-aria": "^3.37.0", "react-stately": "^3.35.0", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-YfG9PUE7XrXtDDAqT4pLTGyYQaiHHTBFdAK/wNgGsypVnQSdzmyYlV3Ty8aHlZJI6hP9RWkbywvosXkU7KcPHg=="],
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-hotkeys-hook": ["react-hotkeys-hook@4.5.1", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg=="], "react-hotkeys-hook": ["react-hotkeys-hook@4.5.1", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg=="],
@@ -2598,12 +2595,6 @@
"rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="], "rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
"rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="],
"rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="],
"rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="],
"router": ["router@2.0.0", "", { "dependencies": { "array-flatten": "3.0.0", "is-promise": "4.0.0", "methods": "~1.1.2", "parseurl": "~1.3.3", "path-to-regexp": "^8.0.0", "setprototypeof": "1.2.0", "utils-merge": "1.0.1" } }, "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ=="], "router": ["router@2.0.0", "", { "dependencies": { "array-flatten": "3.0.0", "is-promise": "4.0.0", "methods": "~1.1.2", "parseurl": "~1.3.3", "path-to-regexp": "^8.0.0", "setprototypeof": "1.2.0", "utils-merge": "1.0.1" } }, "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -2612,7 +2603,7 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
@@ -2660,8 +2651,6 @@
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="],
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="], "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="],
@@ -3558,15 +3547,13 @@
"@changesets/parse/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], "@changesets/parse/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"@cloudflare/next-on-pages/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="],
"@cloudflare/next-on-pages/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=="], "@cloudflare/next-on-pages/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=="],
"@cloudflare/next-on-pages/esbuild": ["esbuild@0.15.18", "", { "optionalDependencies": { "@esbuild/android-arm": "0.15.18", "@esbuild/linux-loong64": "0.15.18", "esbuild-android-64": "0.15.18", "esbuild-android-arm64": "0.15.18", "esbuild-darwin-64": "0.15.18", "esbuild-darwin-arm64": "0.15.18", "esbuild-freebsd-64": "0.15.18", "esbuild-freebsd-arm64": "0.15.18", "esbuild-linux-32": "0.15.18", "esbuild-linux-64": "0.15.18", "esbuild-linux-arm": "0.15.18", "esbuild-linux-arm64": "0.15.18", "esbuild-linux-mips64le": "0.15.18", "esbuild-linux-ppc64le": "0.15.18", "esbuild-linux-riscv64": "0.15.18", "esbuild-linux-s390x": "0.15.18", "esbuild-netbsd-64": "0.15.18", "esbuild-openbsd-64": "0.15.18", "esbuild-sunos-64": "0.15.18", "esbuild-windows-32": "0.15.18", "esbuild-windows-64": "0.15.18", "esbuild-windows-arm64": "0.15.18" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q=="], "@cloudflare/next-on-pages/esbuild": ["esbuild@0.15.18", "", { "optionalDependencies": { "@esbuild/android-arm": "0.15.18", "@esbuild/linux-loong64": "0.15.18", "esbuild-android-64": "0.15.18", "esbuild-android-arm64": "0.15.18", "esbuild-darwin-64": "0.15.18", "esbuild-darwin-arm64": "0.15.18", "esbuild-freebsd-64": "0.15.18", "esbuild-freebsd-arm64": "0.15.18", "esbuild-linux-32": "0.15.18", "esbuild-linux-64": "0.15.18", "esbuild-linux-arm": "0.15.18", "esbuild-linux-arm64": "0.15.18", "esbuild-linux-mips64le": "0.15.18", "esbuild-linux-ppc64le": "0.15.18", "esbuild-linux-riscv64": "0.15.18", "esbuild-linux-s390x": "0.15.18", "esbuild-netbsd-64": "0.15.18", "esbuild-openbsd-64": "0.15.18", "esbuild-sunos-64": "0.15.18", "esbuild-windows-32": "0.15.18", "esbuild-windows-64": "0.15.18", "esbuild-windows-arm64": "0.15.18" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q=="],
"@cloudflare/next-on-pages/miniflare": ["miniflare@3.20241018.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "^8.8.0", "acorn-walk": "^8.2.0", "capnp-ts": "^0.7.0", "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", "workerd": "1.20241018.1", "ws": "^8.17.1", "youch": "^3.2.2", "zod": "^3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-g7i5oGAoJOk8+hJp77A5/wAdu7PEvi5hQc+0wzwzjhUNM2I5DHd2Cc29ACPhAe1kIXvCCVkxs3+REF52qnX0aw=="], "@cloudflare/next-on-pages/miniflare": ["miniflare@3.20250214.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250214.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-t+lT4p2lbOcKv4PS3sx1F/wcDAlbEYZCO2VooLp4H7JErWWYIi9yjD3UillC3CGOpiBahVg5nrPCoFltZf6UlA=="],
"@cloudflare/next-on-pages/wrangler": ["wrangler@3.112.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250214.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.1", "workerd": "1.20250214.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250214.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-PNQWGze3ODlWwG33LPr8kNhbht3eB3L9fogv+fapk2fjaqj0kNweRapkwmvtz46ojcqWzsxmTe4nOC0hIVUfPA=="], "@cloudflare/next-on-pages/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"@codemirror/lang-html/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="], "@codemirror/lang-html/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="],
@@ -3676,24 +3663,88 @@
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@radix-ui/react-dropdown-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.0", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
"@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"@radix-ui/react-menu/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg=="],
"@radix-ui/react-menu/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-menu/@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
"@radix-ui/react-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw=="],
"@radix-ui/react-menu/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="],
"@radix-ui/react-menu/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA=="],
"@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.4", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.4", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA=="],
"@radix-ui/react-menu/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw=="],
"@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="],
"@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.0", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw=="],
"@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-menu/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-menu/react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
"@radix-ui/react-navigation-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="], "@radix-ui/react-navigation-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], "@radix-ui/react-navigation-menu/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], "@radix-ui/react-navigation-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], "@radix-ui/react-navigation-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@radix-ui/react-popover/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ=="], "@radix-ui/react-popover/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ=="],
"@radix-ui/react-popover/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
"@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A=="], "@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A=="],
"@radix-ui/react-roving-focus/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg=="],
"@radix-ui/react-roving-focus/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-roving-focus/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-roving-focus/@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
"@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.0", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw=="],
"@radix-ui/react-roving-focus/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-roving-focus/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
"@radix-ui/react-tooltip/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="], "@radix-ui/react-tooltip/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
"@radix-ui/react-tooltip/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], "@radix-ui/react-tooltip/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-tooltip/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="], "@radix-ui/react-tooltip/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
"@radix-ui/react-tooltip/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
"@radix-ui/react-tooltip/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="], "@radix-ui/react-tooltip/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="],
"@radix-ui/react-tooltip/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="], "@radix-ui/react-tooltip/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="],
@@ -3706,6 +3757,8 @@
"@radix-ui/react-tooltip/@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q=="], "@radix-ui/react-tooltip/@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q=="],
"@radix-ui/react-use-effect-event/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@react-aria/focus/clsx": ["clsx@2.0.0", "", {}, "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="], "@react-aria/focus/clsx": ["clsx@2.0.0", "", {}, "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="],
@@ -3714,14 +3767,10 @@
"@rollup/pluginutils/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], "@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/@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/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/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], "@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="], "@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
@@ -3922,8 +3971,6 @@
"@vercel/gatsby-plugin-vercel-builder/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], "@vercel/gatsby-plugin-vercel-builder/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="],
"@vercel/nft/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"@vercel/nft/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "@vercel/nft/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@vercel/nft/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], "@vercel/nft/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
@@ -3980,8 +4027,6 @@
"cacheable-request/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "cacheable-request/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
"capnp-ts/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"codemirror/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="], "codemirror/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="],
"codemirror/@codemirror/commands": ["@codemirror/commands@6.7.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw=="], "codemirror/@codemirror/commands": ["@codemirror/commands@6.7.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw=="],
@@ -4032,7 +4077,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=="], "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.2", "", { "dependencies": { "@next/env": "15.3.2", "@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.2", "@next/swc-darwin-x64": "15.3.2", "@next/swc-linux-arm64-gnu": "15.3.2", "@next/swc-linux-arm64-musl": "15.3.2", "@next/swc-linux-x64-gnu": "15.3.2", "@next/swc-linux-x64-musl": "15.3.2", "@next/swc-win32-arm64-msvc": "15.3.2", "@next/swc-win32-x64-msvc": "15.3.2", "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-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ=="],
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="], "global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
@@ -4070,8 +4115,6 @@
"make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"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/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=="], "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=="],
@@ -4090,8 +4133,6 @@
"micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="], "miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="],
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
@@ -4102,8 +4143,6 @@
"minizlib/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="], "minizlib/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
"mlly/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], "next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
"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=="], "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=="],
@@ -4156,12 +4195,6 @@
"rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], "rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="],
"rollup-plugin-inject/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="],
"router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], "router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
@@ -4200,10 +4233,10 @@
"terminal-link/supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="], "terminal-link/supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
"terser/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"ts-node/acorn": ["acorn@8.12.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg=="],
"ts-node/acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], "ts-node/acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
"ts-node/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="], "ts-node/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
@@ -4650,21 +4683,13 @@
"@cloudflare/next-on-pages/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.15.18", "", { "os": "linux", "cpu": "none" }, "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ=="], "@cloudflare/next-on-pages/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.15.18", "", { "os": "linux", "cpu": "none" }, "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ=="],
"@cloudflare/next-on-pages/miniflare/acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], "@cloudflare/next-on-pages/miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="],
"@cloudflare/next-on-pages/miniflare/workerd": ["workerd@1.20241018.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20241018.1", "@cloudflare/workerd-darwin-arm64": "1.20241018.1", "@cloudflare/workerd-linux-64": "1.20241018.1", "@cloudflare/workerd-linux-arm64": "1.20241018.1", "@cloudflare/workerd-windows-64": "1.20241018.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-JPW2oAbYOnJj1c5boyDOdjl/Yvur45jhVE8lf+I9oxR6myyAvuH2tdXO62kye68jRluJOMUeyssLes+JRwLmaA=="], "@cloudflare/next-on-pages/miniflare/workerd": ["workerd@1.20250214.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250214.0", "@cloudflare/workerd-darwin-arm64": "1.20250214.0", "@cloudflare/workerd-linux-64": "1.20250214.0", "@cloudflare/workerd-linux-arm64": "1.20250214.0", "@cloudflare/workerd-windows-64": "1.20250214.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-QWcqXZLiMpV12wiaVnb3nLmfs/g4ZsFQq2mX85z546r3AX4CTIkXl0VP50W3CwqLADej3PGYiRDOTelDOwVG1g=="],
"@cloudflare/next-on-pages/miniflare/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], "@cloudflare/next-on-pages/miniflare/youch": ["youch@3.2.3", "", { "dependencies": { "cookie": "^0.5.0", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw=="],
"@cloudflare/next-on-pages/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], "@cloudflare/next-on-pages/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
"@cloudflare/next-on-pages/wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="],
"@cloudflare/next-on-pages/wrangler/miniflare": ["miniflare@3.20250214.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250214.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-t+lT4p2lbOcKv4PS3sx1F/wcDAlbEYZCO2VooLp4H7JErWWYIi9yjD3UillC3CGOpiBahVg5nrPCoFltZf6UlA=="],
"@cloudflare/next-on-pages/wrangler/unenv": ["unenv@2.0.0-rc.1", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg=="],
"@cloudflare/next-on-pages/wrangler/workerd": ["workerd@1.20250214.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250214.0", "@cloudflare/workerd-darwin-arm64": "1.20250214.0", "@cloudflare/workerd-linux-64": "1.20250214.0", "@cloudflare/workerd-linux-arm64": "1.20250214.0", "@cloudflare/workerd-windows-64": "1.20250214.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-QWcqXZLiMpV12wiaVnb3nLmfs/g4ZsFQq2mX85z546r3AX4CTIkXl0VP50W3CwqLADej3PGYiRDOTelDOwVG1g=="],
"@codemirror/lang-json/@codemirror/language/@codemirror/view": ["@codemirror/view@6.34.1", "", { "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ=="], "@codemirror/lang-json/@codemirror/language/@codemirror/view": ["@codemirror/view@6.34.1", "", { "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ=="],
@@ -4742,8 +4767,44 @@
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@radix-ui/react-menu/@radix-ui/react-portal/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/react-remove-scroll/react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
"@radix-ui/react-menu/react-remove-scroll/react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"@radix-ui/react-menu/react-remove-scroll/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@radix-ui/react-menu/react-remove-scroll/use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"@radix-ui/react-menu/react-remove-scroll/use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], "@radix-ui/react-navigation-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-roving-focus/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-roving-focus/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="], "@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
@@ -4908,23 +4969,23 @@
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "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.2", "", {}, "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="],
"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.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g=="],
"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.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w=="],
"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.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA=="],
"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.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg=="],
"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.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg=="],
"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.2", "", { "os": "linux", "cpu": "x64" }, "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w=="],
"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.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ=="],
"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.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA=="],
"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=="], "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=="],
@@ -5114,81 +5175,15 @@
"@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], "@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20241018.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-CRySEzjNRoR8frP5AbtJXd1tgVJa5v7bZon9Dh6nljYlhG+piDv8jvOVEUqF3cXXS+M5aXwr4NlozdMvl5g5mg=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250214.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cDvvedWDc5zrgDnuXe2qYcz/TwBvzmweO55C7XpPuAWJ9Oqxv81PkdekYxD8mH989aQ/GI5YD0Fe6fDYlM+T3Q=="],
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20241018.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y63yWJNTgETDFkY3Ony71/k/G1HRDFIhEzwbT+OWmg1Qbsqa4TquHPVFkgv+OJhpmD3HV9gTBcn/M2QJ/+pGmg=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250214.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NytCvRveVzu0mRKo+tvZo3d/gCUway3B2ZVqSi/TS6NXDGBYIJo7g6s3BnTLS74kgyzeDOjhu9j/RBJBS809qw=="],
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20241018.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a2AbSAXNMMiREvN+PwjHdJ5zOzI4+qf8+rb6H/y4HcVbPZN5C2fanxv5Bx7NUHLiMD/W0FrGug1aU+RPUVZC9Q=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250214.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pQ7+aHNHj8SiYEs4d/6cNoimE5xGeCMfgU1yfDFtA9YGN9Aj2BITZgOWPec+HW7ZkOy9oWlNrO6EvVjGgB4tbQ=="],
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20241018.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-iCJ7bjD/+zhlp3IWnkiry180DwdNvak/sVoS98pIAS41aR3gJVzE5BCz/2yTWFdCoUVZ5yKJrv1HhSKgQRBIEw=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250214.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vhlfah6Yd9ny1npNQjNgElLIjR6OFdEbuR3LCfbLDCwzWEBFhIf7yC+Tpp/a0Hq7kLz3sLdktaP7xl3PJhyOjA=="],
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20241018.1", "", { "os": "win32", "cpu": "x64" }, "sha512-qwDVh/KrwEPY82h6tZ1O4BXBAKeGy30BeTr9wvTUVeY9eX/KT73GuEG+ttwiashRfqjOa0Gcqjsfpd913ITFyg=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250214.0", "", { "os": "win32", "cpu": "x64" }, "sha512-GMwMyFbkjBKjYJoKDhGX8nuL4Gqe3IbVnVWf2Q6086CValyIknupk5J6uQWGw2EBU3RGO3x4trDXT5WphQJZDQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/youch": ["youch@3.2.3", "", { "dependencies": { "cookie": "^0.5.0", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw=="],
"@cloudflare/next-on-pages/wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
"@cloudflare/next-on-pages/wrangler/unenv/ohash": ["ohash@1.1.4", "", {}, "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="],
"@cloudflare/next-on-pages/wrangler/unenv/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250214.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cDvvedWDc5zrgDnuXe2qYcz/TwBvzmweO55C7XpPuAWJ9Oqxv81PkdekYxD8mH989aQ/GI5YD0Fe6fDYlM+T3Q=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250214.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NytCvRveVzu0mRKo+tvZo3d/gCUway3B2ZVqSi/TS6NXDGBYIJo7g6s3BnTLS74kgyzeDOjhu9j/RBJBS809qw=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250214.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pQ7+aHNHj8SiYEs4d/6cNoimE5xGeCMfgU1yfDFtA9YGN9Aj2BITZgOWPec+HW7ZkOy9oWlNrO6EvVjGgB4tbQ=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250214.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vhlfah6Yd9ny1npNQjNgElLIjR6OFdEbuR3LCfbLDCwzWEBFhIf7yC+Tpp/a0Hq7kLz3sLdktaP7xl3PJhyOjA=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250214.0", "", { "os": "win32", "cpu": "x64" }, "sha512-GMwMyFbkjBKjYJoKDhGX8nuL4Gqe3IbVnVWf2Q6086CValyIknupk5J6uQWGw2EBU3RGO3x4trDXT5WphQJZDQ=="],
"@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+4 -4
View File
@@ -7,12 +7,12 @@
"turbo": "^2.5.0", "turbo": "^2.5.0",
"vercel": "^39.3.0" "vercel": "^39.3.0"
}, },
"packageManager": "bun@1.2.8", "packageManager": "bun@1.2.11",
"overrides": { "overrides": {
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"react": "18.3.1", "@gitbook/api": "^0.115.0",
"react-dom": "18.3.1", "react": "^19.0.0",
"@gitbook/api": "0.111.0" "react-dom": "^19.0.0"
}, },
"private": true, "private": true,
"scripts": { "scripts": {
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"version": "0.3.1", "version": "0.3.1",
"dependencies": { "dependencies": {
"@gitbook/api": "^0.111.0", "@gitbook/api": "^0.115.0",
"assert-never": "^1.2.1" "assert-never": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
+8
View File
@@ -1,5 +1,13 @@
# @gitbook/colors # @gitbook/colors
## 0.3.3
### Patch Changes
- c3f6b8c: Update chroma ratio per step
- 5e975ab: Fix code highlighting for HTTP
- f7a3470: Change lightness check for color step 9 to allow input colors with a higher-than-needed contrast
## 0.3.2 ## 0.3.2
### Patch Changes ### Patch Changes
+1 -1
View File
@@ -8,7 +8,7 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"version": "0.3.2", "version": "0.3.3",
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3" "typescript": "^5.5.3"
}, },
+20 -2
View File
@@ -214,13 +214,31 @@ export function colorScale(
const targetL = const targetL =
foregroundColor.L * mapping[index] + backgroundColor.L * (1 - mapping[index]); foregroundColor.L * mapping[index] + backgroundColor.L * (1 - mapping[index]);
if (index === 8 && !mix && Math.abs(baseColor.L - targetL) < 0.2) { if (
index === 8 &&
!mix &&
(darkMode ? targetL - baseColor.L < 0.2 : baseColor.L - targetL < 0.2)
) {
// Original colour is close enough to target, so let's use the original colour as step 9. // Original colour is close enough to target, so let's use the original colour as step 9.
result.push(hex); result.push(hex);
continue; continue;
} }
const chromaRatio = index === 8 || index === 9 ? 1 : index * 0.05; const chromaRatio = (() => {
switch (index) {
// Step 9 and 10 have max chroma, meaning they are fully saturated.
case 8:
case 9:
return 1;
// Step 11 and 12 have a reduced chroma
case 10:
return 0.4;
case 11:
return 0.1;
default:
return index * 0.05;
}
})();
const shade = { const shade = {
L: targetL, // Blend lightness L: targetL, // Blend lightness
+1
View File
@@ -6,6 +6,7 @@
# cloudflare # cloudflare
.open-next .open-next
.wrangler
# Symbolic links # Symbolic links
public public
+19
View File
@@ -1,5 +1,24 @@
# gitbook-v2 # gitbook-v2
## 0.3.0
### Minor Changes
- 3119066: Add support for reusable content across spaces.
- 7d7806d: Pass SVG images through image resizing without resizing them to serve them from optimal host.
### Patch Changes
- 1c8d9fe: keep data cache in OpenNext between deployment
- 778624a: Only resize images with supported extensions.
- e6ddc0f: Fix URL in sitemap
- 5e975ab: Fix code highlighting for HTTP
- e15757d: Fix crash on Cloudflare by using latest stable version of Next.js instead of canary
- 634e0b4: Improve error messages around undefined site sections.
- 97b7c79: Increase logging around caching behaviour causing page crashes.
- 3f29206: Update the regex for validating site redirect
- dd043df: Revert investigation work around URL caches.
## 0.2.5 ## 0.2.5
### Patch Changes ### Patch Changes
+12 -3
View File
@@ -1,11 +1,14 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare'; import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache';
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue'; import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
import {
softTagFilter,
withFilter,
} from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
export default defineCloudflareConfig({ export default defineCloudflareConfig({
incrementalCache: withRegionalCache(r2IncrementalCache, { mode: 'long-lived' }), incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: withFilter({
tagCache: doShardedTagCache({ tagCache: doShardedTagCache({
baseShardSize: 12, baseShardSize: 12,
regionalCache: true, regionalCache: true,
@@ -14,5 +17,11 @@ export default defineCloudflareConfig({
numberOfHardReplicas: 1, numberOfHardReplicas: 1,
}, },
}), }),
// We don't use `revalidatePath`, so we filter out soft tags
filterFn: softTagFilter,
}),
queue: doQueue, queue: doQueue,
// Performance improvements as we don't use PPR
enableCacheInterception: true,
}); });
@@ -0,0 +1,186 @@
import { createHash } from 'node:crypto';
import { trace } from '@/lib/tracing';
import type {
CacheEntryType,
CacheValue,
IncrementalCache,
WithLastModified,
} from '@opennextjs/aws/types/overrides.js';
import { getCloudflareContext } from '@opennextjs/cloudflare';
export const BINDING_NAME = 'NEXT_INC_CACHE_R2_BUCKET';
export const DEFAULT_PREFIX = 'incremental-cache';
export type KeyOptions = {
cacheType?: CacheEntryType;
};
/**
*
* It is very similar to the `R2IncrementalCache` in the `@opennextjs/cloudflare` package, but it allow us to trace
* the cache operations. It also integrates both R2 and Cache API in a single class.
* Having our own, will allow us to customize it in the future if needed.
*/
class GitbookIncrementalCache implements IncrementalCache {
name = 'GitbookIncrementalCache';
protected localCache: Cache | undefined;
async get<CacheType extends CacheEntryType = 'cache'>(
key: string,
cacheType?: CacheType
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
const cacheKey = this.getR2Key(key, cacheType);
return trace(
{
operation: 'openNextIncrementalCacheGet',
name: cacheKey,
},
async (span) => {
span.setAttribute('cacheType', cacheType ?? 'cache');
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
try {
// Check local cache first if available
const localCacheEntry = await localCache.match(this.getCacheUrlKey(cacheKey));
if (localCacheEntry) {
span.setAttribute('cacheHit', 'local');
return localCacheEntry.json();
}
const r2Object = await r2.get(cacheKey);
if (!r2Object) return null;
span.setAttribute('cacheHit', 'r2');
return {
value: await r2Object.json(),
lastModified: r2Object.uploaded.getTime(),
};
} catch (e) {
console.error('Failed to get from cache', e);
return null;
}
}
);
}
async set<CacheType extends CacheEntryType = 'cache'>(
key: string,
value: CacheValue<CacheType>,
cacheType?: CacheType
): Promise<void> {
const cacheKey = this.getR2Key(key, cacheType);
return trace(
{
operation: 'openNextIncrementalCacheSet',
name: cacheKey,
},
async (span) => {
span.setAttribute('cacheType', cacheType ?? 'cache');
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
try {
await r2.put(cacheKey, JSON.stringify(value));
//TODO: Check if there is any places where we don't have tags
// Ideally we should always have tags, but in case we don't, we need to decide how to handle it
// For now we default to a build ID tag, which allow us to invalidate the cache in case something is wrong in this deployment
const tags = this.getTagsFromCacheEntry(value) ?? [
`build_id/${process.env.NEXT_BUILD_ID}`,
];
// We consider R2 as the source of truth, so we update the local cache
// only after a successful R2 write
await localCache.put(
this.getCacheUrlKey(cacheKey),
new Response(
JSON.stringify({
value,
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
// See https://developers.cloudflare.com/workers/reference/security-model/
lastModified: Date.now(),
}),
{
headers: {
// Cache-Control default to 30 minutes, will be overridden by `revalidate`
// In theory we should always get the `revalidate` value
'cache-control': `max-age=${value.revalidate ?? 60 * 30}`,
'cache-tag': tags.join(','),
},
}
)
);
} catch (e) {
console.error('Failed to set to cache', e);
}
}
);
}
async delete(key: string): Promise<void> {
const cacheKey = this.getR2Key(key);
return trace(
{
operation: 'openNextIncrementalCacheDelete',
name: cacheKey,
},
async () => {
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
try {
await r2.delete(cacheKey);
// Here again R2 is the source of truth, so we delete from local cache first
await localCache.delete(this.getCacheUrlKey(cacheKey));
} catch (e) {
console.error('Failed to delete from cache', e);
}
}
);
}
async getCacheInstance(): Promise<Cache> {
if (this.localCache) return this.localCache;
this.localCache = await caches.open('incremental-cache');
return this.localCache;
}
// Utility function to generate keys for R2/Cache API
getR2Key(key: string, cacheType: CacheEntryType = 'cache'): string {
const hash = createHash('sha256').update(key).digest('hex');
return `${DEFAULT_PREFIX}/${cacheType === 'cache' ? process.env?.NEXT_BUILD_ID : 'dataCache'}/${hash}.${cacheType}`.replace(
/\/+/g,
'/'
);
}
getCacheUrlKey(cacheKey: string): string {
return `http://cache.local/${cacheKey}`;
}
getTagsFromCacheEntry<CacheType extends CacheEntryType>(
entry: CacheValue<CacheType>
): string[] | undefined {
if ('tags' in entry && entry.tags) {
return entry.tags;
}
if ('meta' in entry && entry.meta && 'headers' in entry.meta && entry.meta.headers) {
const rawTags = entry.meta.headers['x-next-cache-tags'];
if (typeof rawTags === 'string') {
return rawTags.split(',');
}
}
if ('value' in entry) {
return entry.tags;
}
}
}
export default new GitbookIncrementalCache();
+11 -11
View File
@@ -1,23 +1,23 @@
{ {
"name": "gitbook-v2", "name": "gitbook-v2",
"version": "0.2.5", "version": "0.3.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"next": "canary", "@gitbook/api": "^0.115.0",
"@gitbook/cache-tags": "workspace:*",
"@opennextjs/cloudflare": "1.0.3",
"@sindresorhus/fnv1a": "^3.1.0",
"assert-never": "^1.2.1",
"jwt-decode": "^4.0.0",
"next": "^15.3.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0",
"server-only": "^0.0.1",
"warn-once": "^0.1.1",
"rison": "^0.1.1", "rison": "^0.1.1",
"jwt-decode": "^4.0.0", "server-only": "^0.0.1",
"assert-never": "^1.2.1" "warn-once": "^0.1.1"
}, },
"devDependencies": { "devDependencies": {
"gitbook": "*", "gitbook": "*",
"@opennextjs/cloudflare": "^1.0.0-beta.3",
"@types/rison": "^0.0.9", "@types/rison": "^0.0.9",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"postcss": "^8" "postcss": "^8"
@@ -29,7 +29,7 @@
"build:v2": "next build", "build:v2": "next build",
"start": "next start", "start": "next start",
"build:v2:cloudflare": "opennextjs-cloudflare build", "build:v2:cloudflare": "opennextjs-cloudflare build",
"dev:v2:cloudflare": "wrangler dev --port 8771", "dev:v2:cloudflare": "wrangler dev --port 8771 --env preview",
"unit": "bun test", "unit": "bun test",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
} }
+38 -11
View File
@@ -19,6 +19,7 @@ import {
getDataOrNull, getDataOrNull,
throwIfDataError, throwIfDataError,
} from '@v2/lib/data'; } from '@v2/lib/data';
import assertNever from 'assert-never';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { assert } from 'ts-essentials'; import { assert } from 'ts-essentials';
import { GITBOOK_URL } from './env'; import { GITBOOK_URL } from './env';
@@ -242,19 +243,45 @@ export async function fetchSiteContextByIds(
? parseSiteSectionsAndGroups(siteStructure, ids.siteSection) ? parseSiteSectionsAndGroups(siteStructure, ids.siteSection)
: null; : null;
const siteSpace = ( // Parse the current siteSpace and siteSpaces based on the site structure type.
siteStructure.type === 'siteSpaces' && siteStructure.structure const { siteSpaces, siteSpace }: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace } = (() => {
? siteStructure.structure if (siteStructure.type === 'siteSpaces') {
: sections?.current.siteSpaces const siteSpaces = siteStructure.structure;
)?.find((siteSpace) => siteSpace.id === ids.siteSpace); const siteSpace = siteSpaces.find((siteSpace) => siteSpace.id === ids.siteSpace);
if (!siteSpace) { if (!siteSpace) {
throw new Error('Site space not found'); throw new Error(
`Site space "${ids.siteSpace}" not found in structure type="siteSpaces"`
);
} }
const siteSpaces = return { siteSpaces, siteSpace };
siteStructure.type === 'siteSpaces' }
? siteStructure.structure
: (sections?.current.siteSpaces ?? []); if (siteStructure.type === 'sections') {
assert(
sections,
`cannot find site space "${ids.siteSpace}" because parsed sections are missing siteStructure.type="sections" siteSection="${ids.siteSection}"`
);
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 };
}
// @ts-expect-error
assertNever(siteStructure, `cannot handle site structure of type ${siteStructure.type}`);
})();
const customization = (() => { const customization = (() => {
if (ids.siteSpace) { if (ids.siteSpace) {
@@ -380,7 +407,7 @@ export function checkIsRootSiteContext(context: GitBookSiteContext): boolean {
function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) { function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
const sectionsAndGroups = getSiteStructureSections(structure, { ignoreGroups: false }); const sectionsAndGroups = getSiteStructureSections(structure, { ignoreGroups: false });
const section = parseCurrentSection(structure, siteSectionId); const section = parseCurrentSection(structure, siteSectionId);
assert(section, 'A section must be defined when there are multiple sections'); assert(section, `couldn't find section "${siteSectionId}" in site structure`);
return { list: sectionsAndGroups, current: section } satisfies SiteSections; return { list: sectionsAndGroups, current: section } satisfies SiteSections;
} }
File diff suppressed because it is too large Load Diff
+80 -33
View File
@@ -1,55 +1,102 @@
import { race, tryCatch } from '@/lib/async'; import { race, tryCatch } from '@/lib/async';
import { joinPath, joinPathWithBaseURL } from '@/lib/paths'; import { joinPath, joinPathWithBaseURL } from '@/lib/paths';
import { trace } from '@/lib/tracing'; import { trace } from '@/lib/tracing';
import type { PublishedSiteContentLookup } from '@gitbook/api'; import type { GitBookAPI, PublishedSiteContentLookup, SiteVisitorPayload } from '@gitbook/api';
import { apiClient } from './api'; import { apiClient } from './api';
import { getExposableError } from './errors'; import { getExposableError } from './errors';
import type { DataFetcherResponse } from './types'; import type { DataFetcherResponse } from './types';
import { getURLLookupAlternatives, stripURLSearch } from './urls'; import { getURLLookupAlternatives, stripURLSearch } from './urls';
/** interface LookupPublishedContentByUrlInput {
* Lookup a content by its URL using the GitBook API.
* To optimize caching, we try multiple lookup alternatives and return the first one that matches.
*/
export async function getPublishedContentByURL(input: {
url: string; url: string;
visitorAuthToken: string | null;
redirectOnError: boolean; redirectOnError: boolean;
apiToken: string | null; apiToken: string | null;
visitorPayload: SiteVisitorPayload;
}
/**
* Lookup a content by its URL using the GitBook resolvePublishedContentByUrl API endpoint.
* To optimize caching, we try multiple lookup alternatives and return the first one that matches.
*/
export async function resolvePublishedContentByUrl(input: LookupPublishedContentByUrlInput) {
return lookupPublishedContentByUrl({
url: input.url,
fetchLookupAPIResult: ({ url, signal }) => {
const api = apiClient({ apiToken: input.apiToken });
return trace(
{
operation: 'resolvePublishedContentByUrl',
name: url,
},
() =>
tryCatch(
api.urls.resolvePublishedContentByUrl(
{
url,
...(input.visitorPayload ? { visitor: input.visitorPayload } : {}),
redirectOnError: input.redirectOnError,
},
{ signal }
)
)
);
},
});
}
/**
* Lookup a content by its URL using the GitBook getPublishedContentByUrl API endpoint.
* To optimize caching, we try multiple lookup alternatives and return the first one that matches.
*
* @deprecated use resolvePublishedContentByUrl.
*
*/
export async function getPublishedContentByURL(input: LookupPublishedContentByUrlInput) {
return lookupPublishedContentByUrl({
url: input.url,
fetchLookupAPIResult: ({ url, signal }) => {
const api = apiClient({ apiToken: input.apiToken });
return trace(
{
operation: 'getPublishedContentByURL',
name: url,
},
() =>
tryCatch(
api.urls.getPublishedContentByUrl(
{
url,
visitorAuthToken: input.visitorPayload.jwtToken ?? undefined,
redirectOnError: input.redirectOnError,
// @ts-expect-error - cacheVersion is not a real query param
cacheVersion: 'v2',
},
{ signal }
)
)
);
},
});
}
type TryCatch<T> = ReturnType<typeof tryCatch<T>>;
async function lookupPublishedContentByUrl(input: {
url: string;
fetchLookupAPIResult: (args: {
url: string;
signal: AbortSignal;
}) => TryCatch<Awaited<ReturnType<GitBookAPI['urls']['resolvePublishedContentByUrl']>>>;
}): Promise<DataFetcherResponse<PublishedSiteContentLookup>> { }): Promise<DataFetcherResponse<PublishedSiteContentLookup>> {
const lookupURL = new URL(input.url); const lookupURL = new URL(input.url);
const url = stripURLSearch(lookupURL); const url = stripURLSearch(lookupURL);
const lookup = getURLLookupAlternatives(url); const lookup = getURLLookupAlternatives(url);
const result = await race(lookup.urls, async (alternative, { signal }) => { const result = await race(lookup.urls, async (alternative, { signal }) => {
const api = await apiClient({ apiToken: input.apiToken }); const callResult = await input.fetchLookupAPIResult({
const callResult = await trace(
{
operation: 'getPublishedContentByURL',
name: alternative.url,
},
() =>
tryCatch(
api.urls.getPublishedContentByUrl(
{
url: alternative.url, url: alternative.url,
visitorAuthToken: input.visitorAuthToken ?? undefined,
redirectOnError: input.redirectOnError,
// As this endpoint is cached by our API, we version the request
// to void getting stale data with missing properties.
// this could be improved by ensuring our API cache layer is versioned
// or invalidated when needed
// @ts-expect-error - cacheVersion is not a real query param
cacheVersion: 'v2',
},
{
signal, signal,
} });
)
)
);
if (callResult.error) { if (callResult.error) {
if (alternative.primary) { if (alternative.primary) {
@@ -0,0 +1,81 @@
import { describe, expect, it } from 'bun:test';
import { SizableImageAction, checkIsSizableImageURL } from './checkIsSizableImageURL';
describe('checkIsSizableImageURL', () => {
it('should return Skip for non-parsable URLs', () => {
expect(checkIsSizableImageURL('not a url')).toBe(SizableImageAction.Skip);
});
it('should return Skip for non-http(s) URLs', () => {
expect(checkIsSizableImageURL('data:image/png;base64,abc')).toBe(SizableImageAction.Skip);
expect(checkIsSizableImageURL('file:///path/to/image.jpg')).toBe(SizableImageAction.Skip);
});
it('should return Skip for localhost URLs', () => {
expect(checkIsSizableImageURL('http://localhost:3000/image.jpg')).toBe(
SizableImageAction.Skip
);
expect(checkIsSizableImageURL('https://localhost/image.png')).toBe(SizableImageAction.Skip);
});
it('should return Skip for GitBook image URLs', () => {
expect(checkIsSizableImageURL('https://example.com/~gitbook/image/test.jpg')).toBe(
SizableImageAction.Skip
);
});
it('should return Resize for supported image extensions', () => {
expect(checkIsSizableImageURL('https://example.com/image.jpg')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.jpeg')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.png')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.gif')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.webp')).toBe(
SizableImageAction.Resize
);
});
it('should return Resize for URLs without extensions', () => {
expect(checkIsSizableImageURL('https://example.com/image')).toBe(SizableImageAction.Resize);
});
it('should return Passthrough for unsupported image extensions', () => {
expect(checkIsSizableImageURL('https://example.com/image.svg')).toBe(
SizableImageAction.Passthrough
);
expect(checkIsSizableImageURL('https://example.com/image.bmp')).toBe(
SizableImageAction.Passthrough
);
expect(checkIsSizableImageURL('https://example.com/image.tiff')).toBe(
SizableImageAction.Passthrough
);
expect(checkIsSizableImageURL('https://example.com/image.ico')).toBe(
SizableImageAction.Passthrough
);
});
it('should handle URLs with query parameters correctly', () => {
expect(checkIsSizableImageURL('https://example.com/image.jpg?width=100')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.svg?height=200')).toBe(
SizableImageAction.Passthrough
);
});
it('should be case-insensitive for extensions', () => {
expect(checkIsSizableImageURL('https://example.com/image.JPG')).toBe(
SizableImageAction.Resize
);
expect(checkIsSizableImageURL('https://example.com/image.PNG')).toBe(
SizableImageAction.Resize
);
});
});
@@ -1,4 +1,15 @@
import { checkIsHttpURL } from '@/lib/urls'; import { getExtension } from '@/lib/paths';
export enum SizableImageAction {
Resize = 'resize',
Skip = 'skip',
Passthrough = 'passthrough',
}
/**
* https://developers.cloudflare.com/images/transform-images/#supported-input-formats
*/
const SUPPORTED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
/** /**
* Check if an image URL is resizable. * Check if an image URL is resizable.
@@ -6,22 +17,27 @@ import { checkIsHttpURL } from '@/lib/urls';
* Skip it for SVGs. * Skip it for SVGs.
* Skip it for GitBook images (to avoid recursion). * Skip it for GitBook images (to avoid recursion).
*/ */
export function checkIsSizableImageURL(input: string): boolean { export function checkIsSizableImageURL(input: string): SizableImageAction {
if (!URL.canParse(input)) { if (!URL.canParse(input)) {
return false; return SizableImageAction.Skip;
}
if (input.includes('/~gitbook/image')) {
return false;
} }
const parsed = new URL(input); const parsed = new URL(input);
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) { if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return false; return SizableImageAction.Skip;
} }
if (!checkIsHttpURL(parsed)) { if (parsed.hostname === 'localhost') {
return false; return SizableImageAction.Skip;
}
if (parsed.pathname.includes('/~gitbook/image')) {
return SizableImageAction.Skip;
} }
return true; const extension = getExtension(parsed.pathname).toLowerCase();
if (!extension || SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
// If no extension, we consider it resizable.
return SizableImageAction.Resize;
}
return SizableImageAction.Passthrough;
} }
@@ -1,7 +1,7 @@
import 'server-only'; import 'server-only';
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env'; import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
import type { GitBookLinker } from '../links'; import type { GitBookLinker } from '../links';
import { checkIsSizableImageURL } from './checkIsSizableImageURL'; import { SizableImageAction, checkIsSizableImageURL } from './checkIsSizableImageURL';
import { getImageSize } from './resizer'; import { getImageSize } from './resizer';
import { type SignatureVersion, generateImageSignature } from './signatures'; import { type SignatureVersion, generateImageSignature } from './signatures';
import type { ImageResizer } from './types'; import type { ImageResizer } from './types';
@@ -24,7 +24,7 @@ export function createImageResizer({
return { return {
getResizedImageURL: (urlInput) => { getResizedImageURL: (urlInput) => {
if (!checkIsSizableImageURL(urlInput)) { if (checkIsSizableImageURL(urlInput) === SizableImageAction.Skip) {
return null; return null;
} }
@@ -64,7 +64,7 @@ export function createImageResizer({
}, },
getImageSize: async (input, options) => { getImageSize: async (input, options) => {
if (!checkIsSizableImageURL(input)) { if (checkIsSizableImageURL(input) !== SizableImageAction.Resize) {
return null; return null;
} }
@@ -1,7 +1,7 @@
import 'server-only'; import 'server-only';
import assertNever from 'assert-never'; import assertNever from 'assert-never';
import { GITBOOK_IMAGE_RESIZE_MODE } from '../../env'; import { GITBOOK_IMAGE_RESIZE_MODE } from '../../env';
import { checkIsSizableImageURL } from '../checkIsSizableImageURL'; import { SizableImageAction, checkIsSizableImageURL } from '../checkIsSizableImageURL';
import { resizeImageWithCDNCgi } from './cdn-cgi'; import { resizeImageWithCDNCgi } from './cdn-cgi';
import { resizeImageWithCFFetch } from './cf-fetch'; import { resizeImageWithCFFetch } from './cf-fetch';
import type { CloudflareImageJsonFormat, CloudflareImageOptions } from './types'; import type { CloudflareImageJsonFormat, CloudflareImageOptions } from './types';
@@ -13,7 +13,7 @@ export async function getImageSize(
input: string, input: string,
defaultSize: Partial<CloudflareImageOptions> = {} defaultSize: Partial<CloudflareImageOptions> = {}
): Promise<{ width: number; height: number } | null> { ): Promise<{ width: number; height: number } | null> {
if (!checkIsSizableImageURL(input)) { if (checkIsSizableImageURL(input) !== SizableImageAction.Resize) {
return null; return null;
} }
@@ -48,13 +48,17 @@ export async function resizeImage(
signal?: AbortSignal; signal?: AbortSignal;
} }
): Promise<Response> { ): Promise<Response> {
const parsed = new URL(input); const action = checkIsSizableImageURL(input);
if (parsed.protocol === 'data:') { if (action === SizableImageAction.Skip) {
throw new Error('Cannot resize data: URLs'); throw new Error(
'Cannot resize this image, this function should have never been called on this url'
);
} }
if (parsed.hostname === 'localhost') { if (action === SizableImageAction.Passthrough) {
throw new Error('Cannot resize localhost URLs'); return fetch(input, {
signal: options.signal,
});
} }
switch (GITBOOK_IMAGE_RESIZE_MODE) { switch (GITBOOK_IMAGE_RESIZE_MODE) {
+5
View File
@@ -93,6 +93,11 @@ export function createLinker(
}, },
toAbsoluteURL(absolutePath: string): string { toAbsoluteURL(absolutePath: string): string {
// If the path is already a full URL, we return it as is.
if (URL.canParse(absolutePath)) {
return absolutePath;
}
if (!servedOn.host) { if (!servedOn.host) {
return absolutePath; return absolutePath;
} }
+28 -7
View File
@@ -10,15 +10,16 @@ import {
type ResponseCookies, type ResponseCookies,
getPathScopedCookieName, getPathScopedCookieName,
getResponseCookiesForVisitorAuth, getResponseCookiesForVisitorAuth,
getVisitorToken, getVisitorData,
normalizeVisitorAuthURL, normalizeVisitorAuthURL,
} from '@/lib/visitor-token'; } from '@/lib/visitors';
import { serveResizedImage } from '@/routes/image'; import { serveResizedImage } from '@/routes/image';
import { import {
DataFetcherError, DataFetcherError,
getPublishedContentByURL, getPublishedContentByURL,
getVisitorAuthBasePath, getVisitorAuthBasePath,
normalizeURL, normalizeURL,
resolvePublishedContentByUrl,
throwIfDataError, throwIfDataError,
} from '@v2/lib/data'; } from '@v2/lib/data';
import { isGitBookAssetsHostURL, isGitBookHostURL } from '@v2/lib/env'; import { isGitBookAssetsHostURL, isGitBookHostURL } from '@v2/lib/env';
@@ -33,6 +34,15 @@ export const config = {
type URLWithMode = { url: URL; mode: 'url' | 'url-host' }; type URLWithMode = { url: URL; mode: 'url' | 'url-host' };
/**
* Temporary list of hosts to test adaptive content using the new resolution API.
*/
const ADAPTIVE_CONTENT_HOSTS = [
'docs.gitbook.com',
'adaptive-docs.gitbook-staging.com',
'enriched-content-playground.gitbook-staging.io',
];
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
try { try {
const requestURL = new URL(request.url); const requestURL = new URL(request.url);
@@ -85,17 +95,22 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
// //
// Detect and extract the visitor authentication token from the request // Detect and extract the visitor authentication token from the request
// //
// @ts-ignore - request typing const { visitorToken, unsignedClaims, visitorParamsCookie } = getVisitorData({
const visitorToken = getVisitorToken({
cookies: request.cookies.getAll(), cookies: request.cookies.getAll(),
url: siteRequestURL, url: siteRequestURL,
}); });
const withAPIToken = async (apiToken: string | null) => { const withAPIToken = async (apiToken: string | null) => {
const resolve = ADAPTIVE_CONTENT_HOSTS.includes(siteRequestURL.hostname)
? resolvePublishedContentByUrl
: getPublishedContentByURL;
const siteURLData = await throwIfDataError( const siteURLData = await throwIfDataError(
getPublishedContentByURL({ resolve({
url: siteRequestURL.toString(), url: siteRequestURL.toString(),
visitorAuthToken: visitorToken?.token ?? null, visitorPayload: {
jwtToken: visitorToken?.token ?? undefined,
unsignedClaims,
},
// When the visitor auth token is pulled from the cookie, set redirectOnError when calling getPublishedContentByUrl to allow // When the visitor auth token is pulled from the cookie, set redirectOnError when calling getPublishedContentByUrl to allow
// redirecting when the token is invalid as we could be dealing with stale token stored in the cookie. // redirecting when the token is invalid as we could be dealing with stale token stored in the cookie.
// For example when the VA backend signature has changed but the token stored in the cookie is not yet expired. // For example when the VA backend signature has changed but the token stored in the cookie is not yet expired.
@@ -106,7 +121,13 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
apiToken, apiToken,
}) })
); );
const cookies: ResponseCookies = [];
const cookies: ResponseCookies = visitorParamsCookie
? [
// If visitor.* params were passed to the site URL, include a session cookie to persist these params across navigation.
visitorParamsCookie,
]
: [];
// //
// Handle redirects // Handle redirects
+13 -1
View File
@@ -2,7 +2,11 @@
"main": ".open-next/worker.js", "main": ".open-next/worker.js",
"name": "gitbook-open-v2", "name": "gitbook-open-v2",
"compatibility_date": "2025-04-14", "compatibility_date": "2025-04-14",
"compatibility_flags": ["nodejs_compat", "allow_importable_env"], "compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"assets": { "assets": {
"directory": ".open-next/assets", "directory": ".open-next/assets",
"binding": "ASSETS" "binding": "ASSETS"
@@ -10,6 +14,9 @@
"observability": { "observability": {
"enabled": true "enabled": true
}, },
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"env": { "env": {
"preview": { "preview": {
"r2_buckets": [ "r2_buckets": [
@@ -83,6 +90,11 @@
] ]
}, },
"production": { "production": {
"vars": {
// This is a bit misleading, but it means that we can have 100 concurrent revalidations
// This means that we'll have up to 20 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "20"
},
"routes": [ "routes": [
{ {
"pattern": "open-2c.gitbook.com/*", "pattern": "open-2c.gitbook.com/*",
+55 -1
View File
@@ -1,5 +1,59 @@
# gitbook # gitbook
## 0.12.0
### Minor Changes
- 8339e91: Fix images in reusable content across spaces.
- 326e28e: Design tweaks to code blocks and OpenAPI pages
- 3119066: Add support for reusable content across spaces.
- 7d7806d: Pass SVG images through image resizing without resizing them to serve them from optimal host.
### Patch Changes
- c4ebb3f: Fix openapi-select hover in responses
- aed79fd: Decrease rounding of header logo
- 42ca7e1: Fix openapi CR preview
- e6ddc0f: Fix URL in sitemap
- 5e975ab: Fix code highlighting for HTTP
- 5d504ff: Fix resolution of links in reusable contents
- 95a1f65: Better print layouts: wrap code blocks & force table column auto-sizing
- 0499966: Fix invalid sitemap.xml generated with relative URLs instead of absolute ones
- 2a805cc: Change OpenAPI schema-optional from `info` to `tint` color
- 580101d: Fix schemas disclosure label causing client error
- 12a455d: Fix OpenAPI layout issues
- 97b7c79: Increase logging around caching behaviour causing page crashes.
- 373f18f: Prevent section group popovers from opening on click
- 3f29206: Update the regex for validating site redirect
- 0c973a3: Always link main logo to the root of the site
- ae5f1ab: Change `Dropdown`s to use Radix's `DropdownMenu`
- 0e201d5: Add border to filled sidebar on gradient theme
- dd043df: Revert investigation work around URL caches.
- 89a5816: Fix OpenAPI disclosure label ("Show properties") misalignment on mobile
- Updated dependencies [c3f6b8c]
- Updated dependencies [d00dc8c]
- Updated dependencies [42ca7e1]
- Updated dependencies [326e28e]
- Updated dependencies [5e975ab]
- Updated dependencies [f7a3470]
- Updated dependencies [580101d]
- Updated dependencies [20ebecb]
- Updated dependencies [80cb52a]
- Updated dependencies [cb5598d]
- Updated dependencies [c6637b0]
- Updated dependencies [a3ec264]
- @gitbook/colors@0.3.3
- @gitbook/openapi-parser@2.1.4
- @gitbook/react-openapi@1.3.0
## 0.11.1
### Patch Changes
- Updated dependencies [ebc39e9]
- Updated dependencies [b6b09d4]
- @gitbook/react-openapi@1.2.1
## 0.11.0 ## 0.11.0
### Minor Changes ### Minor Changes
@@ -556,7 +610,7 @@
- 4cbcc5b: Rollback of scalar modal while fixing perf issue - 4cbcc5b: Rollback of scalar modal while fixing perf issue
- 3996110: Optimize images rendered in community ads - 3996110: Optimize images rendered in community ads
- 133c3e7: Update design of Checkbox to be more consistent and readable - 133c3e7: Update design of Checkbox to be more consistent and readable
- 5096f7f: Disable KV cache for docs.gitbook.com as a test, also disable it for change-request to improve consistency - 5096f7f: Disable KV cache for gitbook.com/docs as a test, also disable it for change-request to improve consistency
- 0f1565c: Add optional env `GITBOOK_INTEGRATIONS_HOST` to configure the host serving the integrations - 0f1565c: Add optional env `GITBOOK_INTEGRATIONS_HOST` to configure the host serving the integrations
- 2ff7ed1: Fix table of contents being visible on mobile when disabled at the page level - 2ff7ed1: Fix table of contents being visible on mobile when disabled at the page level
- b075f0f: Fix accessibility of the table of contents by using `aria-current` instead of `aria-selected` - b075f0f: Fix accessibility of the table of contents by using `aria-current` instead of `aria-selected`
+30 -19
View File
@@ -12,7 +12,7 @@ import {
VISITOR_TOKEN_COOKIE, VISITOR_TOKEN_COOKIE,
getVisitorAuthCookieName, getVisitorAuthCookieName,
getVisitorAuthCookieValue, getVisitorAuthCookieValue,
} from '@/lib/visitor-token'; } from '@/lib/visitors';
import { getSiteAPIToken } from '../tests/utils'; import { getSiteAPIToken } from '../tests/utils';
import { import {
@@ -111,24 +111,24 @@ const testCases: TestsCase[] = [
name: 'Customized variant titles are displayed', name: 'Customized variant titles are displayed',
url: '', url: '',
run: async (page) => { run: async (page) => {
const spaceDrowpdown = page const spaceDropdown = page
.locator('[data-testid="space-dropdown-button"]') .locator('[data-testid="space-dropdown-button"]')
.locator('visible=true'); .locator('visible=true');
await spaceDrowpdown.click(); await spaceDropdown.click();
const variantSelectionDropdown = page.locator( const variantSelectionDropdown = page.locator(
'css=[data-testid="space-dropdown-button"] + div' 'css=[data-testid="dropdown-menu"]'
); );
// the customized space title // the customized space title
await expect( await expect(
variantSelectionDropdown.getByRole('link', { variantSelectionDropdown.getByRole('menuitem', {
name: 'Multi-Variants', name: 'Multi-Variants',
}) })
).toBeVisible(); ).toBeVisible();
// the NON-customized space title // the NON-customized space title
await expect( await expect(
variantSelectionDropdown.getByRole('link', { variantSelectionDropdown.getByRole('menuitem', {
name: 'RFCs', name: 'RFCs',
}) })
).toBeVisible(); ).toBeVisible();
@@ -145,14 +145,17 @@ const testCases: TestsCase[] = [
url: 'api-multi-versions/reference/api-reference/pets', url: 'api-multi-versions/reference/api-reference/pets',
screenshot: false, screenshot: false,
run: async (page) => { run: async (page) => {
const spaceDrowpdown = await page const spaceDropdown = await page
.locator('[data-testid="space-dropdown-button"]') .locator('[data-testid="space-dropdown-button"]')
.locator('visible=true'); .locator('visible=true');
await spaceDrowpdown.click(); await spaceDropdown.click();
const variantSelectionDropdown = page.locator(
'css=[data-testid="dropdown-menu"]'
);
// Click the second variant in the dropdown // Click the second variant in the dropdown
await page await variantSelectionDropdown
.getByRole('link', { .getByRole('menuitem', {
name: '2.0', name: '2.0',
}) })
.click(); .click();
@@ -168,14 +171,18 @@ const testCases: TestsCase[] = [
url: 'api-multi-versions-share-links/8tNo6MeXg7CkFMzSSz81/reference/api-reference/pets', url: 'api-multi-versions-share-links/8tNo6MeXg7CkFMzSSz81/reference/api-reference/pets',
screenshot: false, screenshot: false,
run: async (page) => { run: async (page) => {
const spaceDrowpdown = await page const spaceDropdown = await page
.locator('[data-testid="space-dropdown-button"]') .locator('[data-testid="space-dropdown-button"]')
.locator('visible=true'); .locator('visible=true');
await spaceDrowpdown.click(); await spaceDropdown.click();
const variantSelectionDropdown = page.locator(
'css=[data-testid="dropdown-menu"]'
);
// Click the second variant in the dropdown // Click the second variant in the dropdown
await page await variantSelectionDropdown
.getByRole('link', { .getByRole('menuitem', {
name: '2.0', name: '2.0',
}) })
.click(); .click();
@@ -205,14 +212,18 @@ const testCases: TestsCase[] = [
return `api-multi-versions-va/reference/api-reference/pets?jwt_token=${token}`; return `api-multi-versions-va/reference/api-reference/pets?jwt_token=${token}`;
}, },
run: async (page) => { run: async (page) => {
const spaceDrowpdown = await page const spaceDropdown = await page
.locator('[data-testid="space-dropdown-button"]') .locator('[data-testid="space-dropdown-button"]')
.locator('visible=true'); .locator('visible=true');
await spaceDrowpdown.click(); await spaceDropdown.click();
const variantSelectionDropdown = page.locator(
'css=[data-testid="dropdown-menu"]'
);
// Click the second variant in the dropdown // Click the second variant in the dropdown
await page await variantSelectionDropdown
.getByRole('link', { .getByRole('menuitem', {
name: '2.0', name: '2.0',
}) })
.click(); .click();
@@ -258,7 +269,7 @@ const testCases: TestsCase[] = [
}, },
{ {
name: 'GitBook', name: 'GitBook',
contentBaseURL: 'https://docs.gitbook.com', contentBaseURL: 'https://gitbook.com/docs/',
tests: [ tests: [
{ {
name: 'Home', name: 'Home',
+23 -18
View File
@@ -168,6 +168,7 @@ export function runTestCases(testCases: TestsCase[]) {
new URL(testEntryPathname, testCase.contentBaseURL).toString() new URL(testEntryPathname, testCase.contentBaseURL).toString()
) )
: getTestURL(testEntryPathname); : getTestURL(testEntryPathname);
if (testEntry.cookies) { if (testEntry.cookies) {
await context.addCookies( await context.addCookies(
testEntry.cookies.map((cookie) => ({ testEntry.cookies.map((cookie) => ({
@@ -345,19 +346,30 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
*/ */
async function waitForIcons(page: Page) { async function waitForIcons(page: Page) {
await page.waitForFunction(() => { await page.waitForFunction(() => {
const urlStates: Record<string, 'pending' | 'loaded'> = const urlStates: Record<
(window as any).__ICONS_STATES__ || {}; string,
{ state: 'pending'; uri: null } | { state: 'loaded'; uri: string }
> = (window as any).__ICONS_STATES__ || {};
(window as any).__ICONS_STATES__ = urlStates; (window as any).__ICONS_STATES__ = urlStates;
const fetchSvgAsDataUri = async (url: string): Promise<string> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch SVG: ${response.status}`);
}
const svgText = await response.text();
const encoded = encodeURIComponent(svgText).replace(/'/g, '%27').replace(/"/g, '%22');
return `data:image/svg+xml;charset=utf-8,${encoded}`;
};
const loadUrl = (url: string) => { const loadUrl = (url: string) => {
// Mark the URL as pending. // Mark the URL as pending.
urlStates[url] = 'pending'; urlStates[url] = { state: 'pending', uri: null };
fetchSvgAsDataUri(url).then((uri) => {
const img = new Image(); urlStates[url] = { state: 'loaded', uri };
img.onload = () => { });
urlStates[url] = 'loaded';
};
img.src = url;
}; };
const icons = Array.from(document.querySelectorAll('svg.gb-icon')); const icons = Array.from(document.querySelectorAll('svg.gb-icon'));
@@ -393,19 +405,12 @@ async function waitForIcons(page: Page) {
// If the URL is already queued for loading, we return the state. // If the URL is already queued for loading, we return the state.
if (urlStates[url]) { if (urlStates[url]) {
if (urlStates[url] === 'loaded') { if (urlStates[url].state === 'loaded') {
icon.setAttribute('data-argos-state', 'pending'); icon.setAttribute('data-argos-state', 'pending');
const bckMaskImage = icon.style.maskImage; icon.style.maskImage = `url("${urlStates[url].uri}")`;
const bckDisplay = icon.style.display;
icon.style.maskImage = '';
icon.style.display = 'none';
requestAnimationFrame(() => {
icon.style.maskImage = bckMaskImage;
icon.style.display = bckDisplay;
requestAnimationFrame(() => { requestAnimationFrame(() => {
icon.setAttribute('data-argos-state', 'loaded'); icon.setAttribute('data-argos-state', 'loaded');
}); });
});
return false; return false;
} }
+8 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "gitbook", "name": "gitbook",
"version": "0.11.0", "version": "0.12.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "env-cmd --silent -f ../../.env.local next dev", "dev": "env-cmd --silent -f ../../.env.local next dev",
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math" "clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
}, },
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.115.0",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*", "@gitbook/colors": "workspace:*",
@@ -27,6 +27,7 @@
"@gitbook/react-math": "workspace:*", "@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*", "@gitbook/react-openapi": "workspace:*",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-navigation-menu": "^1.2.3", "@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.1.8",
@@ -37,6 +38,7 @@
"assert-never": "^1.2.1", "assert-never": "^1.2.1",
"bun-types": "^1.1.20", "bun-types": "^1.1.20",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"event-iterator": "^2.0.0",
"framer-motion": "^10.16.14", "framer-motion": "^10.16.14",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsontoxml": "^1.0.1", "jsontoxml": "^1.0.1",
@@ -52,8 +54,9 @@
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"p-map": "^7.0.0", "p-map": "^7.0.0",
"parse-cache-control": "^1.0.1", "parse-cache-control": "^1.0.1",
"react": "18.3.1", "partial-json": "^0.1.7",
"react-dom": "18.3.1", "react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hotkeys-hook": "^4.4.1", "react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1", "rehype-stringify": "^10.0.1",
@@ -70,13 +73,11 @@
"usehooks-ts": "^3.1.0", "usehooks-ts": "^3.1.0",
"zod": "^3.24.2", "zod": "^3.24.2",
"zod-to-json-schema": "^3.24.5", "zod-to-json-schema": "^3.24.5",
"event-iterator": "^2.0.0",
"partial-json": "^0.1.7",
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@argos-ci/playwright": "^5.0.3", "@argos-ci/playwright": "^5.0.3",
"@cloudflare/next-on-pages": "1.13.7", "@cloudflare/next-on-pages": "1.13.12",
"@cloudflare/workers-types": "^4.20241230.0", "@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
@@ -24,7 +24,7 @@
} }
.highlight-line-number { .highlight-line-number {
@apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-gradient-to-r from-80% from-tint to-transparent; @apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-gradient-to-r from-80% from-tint-subtle contrast-more:from-tint-base theme-muted:from-tint-base [html.theme-bold.sidebar-filled_&]:from-tint-base to-transparent;
@apply before:text-tint before:content-[counter(line)]; @apply before:text-tint before:content-[counter(line)];
.highlight-line.highlighted > & { .highlight-line.highlighted > & {
@@ -39,7 +39,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
> >
<div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]"> <div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]">
{title ? ( {title ? (
<div className="inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s bg-tint px-3 py-2 text-tint text-xs leading-none tracking-wide"> <div className="relative top-px z-20 inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s border border-tint-subtle border-b-0 bg-tint-subtle theme-muted:bg-tint-base px-3 py-2 text-tint text-xs leading-none tracking-wide contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base">
{title} {title}
</div> </div>
) : null} ) : null}
@@ -50,15 +50,15 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
/> />
<pre <pre
className={tcls( className={tcls(
'hide-scroll relative overflow-auto bg-tint theme-gradient:bg-tint-12/1 ring-tint-subtle [grid-area:2/1]', 'hide-scroll relative overflow-auto border border-tint-subtle bg-tint-subtle theme-muted:bg-tint-base [grid-area:2/1] contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base',
'rounded-md straight-corners:rounded-sm', 'rounded-md straight-corners:rounded-sm shadow-sm',
title && 'rounded-ss-none' title && 'rounded-ss-none'
)} )}
> >
<code <code
id={id} id={id}
className={tcls( className={tcls(
'inline-grid min-w-full grid-cols-[auto_1fr] p-2 [count-reset:line]', 'inline-grid min-w-full grid-cols-[auto_1fr] p-2 [count-reset:line] print:whitespace-pre-wrap',
withWrap && 'whitespace-pre-wrap' withWrap && 'whitespace-pre-wrap'
)} )}
> >
@@ -1,31 +1,67 @@
:root { :root {
--shiki-color-text: theme("colors.tint.11"); --shiki-color-text: theme("colors.tint.11");
--shiki-token-constant: #0a6355; --shiki-token-punctuation: theme("colors.tint.11");
--shiki-token-string: #8b6d32; --shiki-token-comment: theme("colors.neutral.9/.7");
--shiki-token-comment: theme("colors.teal.700/.64"); --shiki-token-link: theme("colors.primary.10");
--shiki-token-keyword: theme("colors.pomegranate.600");
--shiki-token-parameter: #0a3069; --shiki-token-constant: theme("colors.warning.10");
--shiki-token-function: #8250df; --shiki-token-string: theme("colors.warning.10");
--shiki-token-string-expression: #6a4906; --shiki-token-string-expression: theme("colors.success.10");
--shiki-token-punctuation: theme("colors.pomegranate.700/.92"); --shiki-token-keyword: theme("colors.danger.10");
--shiki-token-link: theme("colors.tint.12"); --shiki-token-parameter: theme("colors.warning.10");
--shiki-token-inserted: #22863a; --shiki-token-function: theme("colors.primary.10");
--shiki-token-deleted: #b31d28;
--shiki-token-changed: #8250df; --shiki-token-inserted: theme("colors.success.10");
--shiki-token-deleted: theme("colors.danger.10");
--shiki-token-changed: theme("colors.tint.12");
}
@media (prefers-contrast: more) {
:root {
--shiki-color-text: theme("colors.tint.12");
--shiki-token-punctuation: theme("colors.tint.12");
--shiki-token-comment: theme("colors.neutral.11");
--shiki-token-link: theme("colors.primary.11");
--shiki-token-constant: theme("colors.warning.11");
--shiki-token-string: theme("colors.warning.11");
--shiki-token-string-expression: theme("colors.success.11");
--shiki-token-keyword: theme("colors.danger.11");
--shiki-token-parameter: theme("colors.warning.11");
--shiki-token-function: theme("colors.primary.11");
--shiki-token-inserted: theme("colors.success.11");
--shiki-token-deleted: theme("colors.danger.11");
--shiki-token-changed: theme("colors.tint.12");
}
} }
html.dark { html.dark {
--shiki-color-text: theme("colors.tint.11"); /* Override select colors to have more contrast */
--shiki-token-constant: #d19a66; --shiki-token-comment: theme("colors.neutral.9");
--shiki-token-string: theme("colors.pomegranate.300");
--shiki-token-comment: theme("colors.teal.300/.64"); --shiki-token-constant: theme("colors.warning.11");
--shiki-token-keyword: theme("colors.pomegranate.400"); --shiki-token-string: theme("colors.warning.11");
--shiki-token-parameter: theme("colors.yellow.500"); --shiki-token-string-expression: theme("colors.success.11");
--shiki-token-function: #56b6c2; --shiki-token-keyword: theme("colors.danger.11");
--shiki-token-string-expression: theme("colors.tint.11"); --shiki-token-parameter: theme("colors.warning.11");
--shiki-token-punctuation: #acc6ee; --shiki-token-function: theme("colors.primary.11");
--shiki-token-link: theme("colors.pomegranate.400"); }
--shiki-token-inserted: #85e89d;
--shiki-token-deleted: #fdaeb7; .code-monochrome {
--shiki-token-changed: #56b6c2; --shiki-token-constant: theme("colors.tint.11");
--shiki-token-string: theme("colors.tint.12");
--shiki-token-string-expression: theme("colors.tint.12");
--shiki-token-keyword: theme("colors.primary.10");
--shiki-token-parameter: theme("colors.tint.9");
--shiki-token-function: theme("colors.primary.9");
}
html.dark.code-monochrome {
--shiki-token-constant: theme("colors.tint.11");
--shiki-token-string: theme("colors.tint.12");
--shiki-token-string-expression: theme("colors.tint.12");
--shiki-token-keyword: theme("colors.primary.11");
--shiki-token-parameter: theme("colors.tint.10");
--shiki-token-function: theme("colors.primary.10");
} }
@@ -6,6 +6,7 @@ import { Card } from '@/components/primitives';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import { getDataOrNull } from '@v2/lib/data'; import { getDataOrNull } from '@v2/lib/data';
import { Image } from '../utils';
import type { BlockProps } from './Block'; import type { BlockProps } from './Block';
import { Caption } from './Caption'; import { Caption } from './Caption';
import { IntegrationBlock } from './Integration'; import { IntegrationBlock } from './Integration';
@@ -52,7 +53,14 @@ export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
<Card <Card
leadingIcon={ leadingIcon={
embed.icon ? ( embed.icon ? (
<img src={embed.icon} className={tcls('w-5', 'h-5')} alt="Logo" /> <Image
src={embed.icon}
className={tcls('w-5', 'h-5')}
alt="Logo"
sources={{ light: { src: embed.icon } }}
sizes={[{ width: 20 }]}
resize={context.contentContext.imageResizer}
/>
) : null ) : null
} }
href={block.data.url} href={block.data.url}
@@ -0,0 +1,58 @@
import { type ClassValue, tcls } from '@/lib/tailwind';
import type { DocumentBlockHeading, DocumentBlockTabs } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { getBlockTextStyle } from './spacing';
/**
* A hash icon which adds the block or active block item's ID in the URL hash.
* The button needs to be wrapped in a container with `hashLinkButtonWrapperStyles`.
*/
export const hashLinkButtonWrapperStyles = tcls('relative', 'group/hash');
export function HashLinkButton(props: {
id: string;
block: DocumentBlockTabs | DocumentBlockHeading;
label?: string;
className?: ClassValue;
iconClassName?: ClassValue;
}) {
const { id, block, className, iconClassName, label = 'Direct link to block' } = props;
const textStyle = getBlockTextStyle(block);
return (
<div
className={tcls(
'relative',
'hash',
'grid',
'grid-area-1-1',
'h-[1em]',
'border-0',
'opacity-0',
'group-hover/hash:opacity-[0]',
'group-focus/hash:opacity-[0]',
'md:group-hover/hash:md:opacity-[1]',
'md:group-focus/hash:md:opacity-[1]',
className
)}
>
<a
href={`#${id}`}
aria-label={label}
className={tcls('inline-flex', 'h-full', 'items-start', textStyle.lineHeight)}
>
<Icon
icon="hashtag"
className={tcls(
'size-3',
'self-center',
'transition-colors',
'text-transparent',
'group-hover/hash:text-tint-subtle',
'contrast-more:group-hover/hash:text-tint-strong',
iconClassName
)}
/>
</a>
</div>
);
}
@@ -1,9 +1,9 @@
import type { DocumentBlockHeading } from '@gitbook/api'; import type { DocumentBlockHeading } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import type { BlockProps } from './Block'; import type { BlockProps } from './Block';
import { HashLinkButton, hashLinkButtonWrapperStyles } from './HashLinkButton';
import { Inlines } from './Inlines'; import { Inlines } from './Inlines';
import { getBlockTextStyle } from './spacing'; import { getBlockTextStyle } from './spacing';
@@ -23,50 +23,20 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
className={tcls( className={tcls(
textStyle.textSize, textStyle.textSize,
'heading', 'heading',
'group',
'relative',
'grid', 'grid',
'scroll-m-12', 'scroll-m-12',
hashLinkButtonWrapperStyles,
style style
)} )}
> >
<div <HashLinkButton
className={tcls( id={id}
'hash', block={block}
'grid', className={tcls('-ml-6', textStyle.anchorButtonMarginTop)}
'grid-area-1-1', iconClassName={tcls('size-4')}
'relative', label="Direct link to heading"
'-ml-6',
'w-7',
'border-0',
'opacity-0',
'group-hover:opacity-[0]',
'group-focus:opacity-[0]',
'md:group-hover:md:opacity-[1]',
'md:group-focus:md:opacity-[1]',
textStyle.marginTop
)}
>
<a
href={`#${id}`}
aria-label="Direct link to heading"
className={tcls('inline-flex', 'h-full', 'items-start', textStyle.lineHeight)}
>
<Icon
icon="hashtag"
className={tcls(
'w-3.5',
'h-[1em]',
'mt-0.5',
'transition-colors',
'text-transparent',
'group-hover:text-tint-subtle',
'contrast-more:group-hover:text-tint-strong',
'lg:w-4'
)}
/> />
</a>
</div>
<div <div
className={tcls( className={tcls(
'grid-area-1-1', 'grid-area-1-1',
@@ -20,6 +20,8 @@ export const contentKitServerContext: ContentKitServerContext = {
'link-external': (props) => <Icon icon="arrow-up-right-from-square" {...props} />, 'link-external': (props) => <Icon icon="arrow-up-right-from-square" {...props} />,
eye: (props) => <Icon icon="eye" {...props} />, eye: (props) => <Icon icon="eye" {...props} />,
lock: (props) => <Icon icon="lock" {...props} />, lock: (props) => <Icon icon="lock" {...props} />,
check: (props) => <Icon icon="check" {...props} />,
'check-circle': (props) => <Icon icon="check-circle" {...props} />,
}, },
codeBlock: (props) => { codeBlock: (props) => {
return <PlainCodeBlock code={props.code} syntax={props.syntax} />; return <PlainCodeBlock code={props.code} syntax={props.syntax} />;
@@ -145,7 +145,7 @@
} }
.openapi-column-preview-body { .openapi-column-preview-body {
@apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header-sections:top-32 page-api-block:xl:max-2xl:top-32 print-mode:static; @apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header:xl:max-2xl:top-32 site-header-sections:top-32 site-header-sections:xl:max-2xl:top-44 print-mode:static;
} }
.openapi-column-preview pre { .openapi-column-preview pre {
@@ -169,41 +169,18 @@
@apply flex flex-col; @apply flex flex-col;
} }
.openapi-schema { .openapi-schema,
.openapi-disclosure {
@apply py-2.5 flex flex-col gap-2; @apply py-2.5 flex flex-col gap-2;
} }
.openapi-section-body .openapi-schema-properties {
@apply divide-y divide-tint-subtle;
}
.openapi-disclosure-group-panel > .openapi-schema-properties > *:first-child > .openapi-schema {
@apply pt-0;
}
.openapi-responsebody > .openapi-schema-properties > .openapi-schema:last-child {
@apply pb-0;
}
.openapi-responsebody > .openapi-schema-properties > .openapi-schema:only-child {
@apply py-0;
}
.openapi-schema-properties .openapi-schema:last-child { .openapi-schema-properties .openapi-schema:last-child {
@apply border-b-0; @apply border-b-0;
} }
.openapi-schema-properties .openapi-schema-opened {
@apply pb-3;
}
.openapi-schema > .openapi-schema-properties {
@apply mt-3;
}
/* Schema Presentation */ /* Schema Presentation */
.openapi-schema-presentation { .openapi-schema-presentation {
@apply flex flex-col gap-1.5 font-normal; @apply flex flex-col gap-1 font-normal;
} }
.openapi-schema-properties:last-child { .openapi-schema-properties:last-child {
@@ -221,7 +198,7 @@
} }
.openapi-schema-propertyname { .openapi-schema-propertyname {
@apply select-all font-mono font-normal text-tint-strong; @apply select-all font-mono font-semibold text-tint-strong;
} }
.openapi-schema-propertyname[data-deprecated="true"] { .openapi-schema-propertyname[data-deprecated="true"] {
@@ -233,7 +210,7 @@
} }
.openapi-schema-optional { .openapi-schema-optional {
@apply text-info-subtle text-[0.813rem] lowercase; @apply text-tint-subtle text-[0.813rem] lowercase;
} }
.openapi-schema-readonly { .openapi-schema-readonly {
@@ -244,6 +221,10 @@
@apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase; @apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase;
} }
.openapi-schema-types {
@apply flex items-baseline flex-wrap gap-1;
}
.openapi-schema-type { .openapi-schema-type {
@apply text-tint select-text text-[0.813rem] font-mono [word-spacing:-0.25rem]; @apply text-tint select-text text-[0.813rem] font-mono [word-spacing:-0.25rem];
} }
@@ -321,7 +302,7 @@
.openapi-schema-pattern code, .openapi-schema-pattern code,
.openapi-schema-enum-value code, .openapi-schema-enum-value code,
.openapi-schema-default code { .openapi-schema-default code {
@apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none; @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
} }
/* Authentication */ /* Authentication */
@@ -373,28 +354,25 @@
} }
.openapi-response-tab-content { .openapi-response-tab-content {
@apply overflow-hidden max-w-full flex items-baseline; @apply flex items-baseline truncate grow shrink max-w-max basis-[60%] mr-auto;
@apply text-left text-pretty relative leading-[1.125rem] text-tint !font-normal truncate select-text; @apply text-left text-pretty relative leading-tight text-tint select-text;
} }
.openapi-response-description.openapi-markdown { .openapi-response-description.openapi-markdown {
@apply text-left prose-sm text-[0.813rem] text-pretty h-auto relative leading-[1.125rem] text-tint !font-normal truncate select-text prose-strong:font-semibold prose-strong:text-inherit; @apply text-left truncate prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
} }
.openapi-response-description.openapi-markdown::-webkit-scrollbar { .openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-tab-content {
display: none; @apply basis-full;
} }
.openapi-response-description p { .openapi-disclosure-group-trigger[aria-expanded="true"]
@apply truncate max-w-full inline pr-1; .openapi-response-description.openapi-markdown {
} @apply whitespace-normal;
.openapi-response-content-type {
@apply text-xs text-tint-8 ml-auto shrink-0;
} }
.openapi-response-body { .openapi-response-body {
@apply flex flex-col gap-3; @apply flex flex-col;
} }
/* Response Body and Headers */ /* Response Body and Headers */
@@ -411,16 +389,7 @@
@apply px-3 py-1; @apply px-3 py-1;
} }
.openapi-responsebody-header-content,
.openapi-responseheaders-header-content {
/* unstyled */
}
/* Code Sample */ /* Code Sample */
.openapi-codesample {
@apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
}
.openapi-codesample-header { .openapi-codesample-header {
@apply flex flex-row items-center; @apply flex flex-row items-center;
} }
@@ -429,6 +398,12 @@
@apply flex flex-row items-center gap-2.5; @apply flex flex-row items-center gap-2.5;
} }
.openapi-panel-heading,
.openapi-codesample-header,
.openapi-response-examples-header {
@apply border-b border-tint-subtle;
}
.openapi-response-examples-header .openapi-select > button { .openapi-response-examples-header .openapi-select > button {
@apply max-w-full overflow-hidden shrink pl-0.5 py-0.5; @apply max-w-full overflow-hidden shrink pl-0.5 py-0.5;
} }
@@ -503,8 +478,16 @@
} }
/* Panel */ /* Panel */
.openapi-panel { .openapi-panel,
@apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle; .openapi-codesample,
.openapi-response-examples {
@apply border rounded-md straight-corners:rounded-none bg-tint-subtle border-tint-subtle shadow-sm;
}
.openapi-panel pre,
.openapi-codesample pre,
.openapi-response-examples pre {
@apply bg-transparent border-none rounded-none shadow-none;
} }
.openapi-panel-heading { .openapi-panel-heading {
@@ -513,12 +496,11 @@
.openapi-panel-body { .openapi-panel-body {
@apply relative; @apply relative;
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
} }
.openapi-panel-footer, .openapi-panel-footer,
.openapi-codesample-footer { .openapi-codesample-footer {
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint; @apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint empty:hidden;
} }
.openapi-panel-footer .openapi-markdown { .openapi-panel-footer .openapi-markdown {
@@ -526,10 +508,6 @@
} }
/* Example */ /* Example */
.openapi-response-examples {
@apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
}
.openapi-response-examples-header { .openapi-response-examples-header {
@apply flex flex-row items-center p-2.5; @apply flex flex-row items-center p-2.5;
} }
@@ -551,7 +529,6 @@
.openapi-response-examples-panel, .openapi-response-examples-panel,
.openapi-codesample-panel { .openapi-codesample-panel {
@apply flex-1 text-sm relative focus-visible:outline-none; @apply flex-1 text-sm relative focus-visible:outline-none;
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
} }
.openapi-example-empty { .openapi-example-empty {
@@ -703,29 +680,33 @@ body:has(.openapi-select-popover) {
/* Disclosure group */ /* Disclosure group */
.openapi-disclosure-group { .openapi-disclosure-group {
@apply border-tint-subtle border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none first:rounded-t-md straight-corners:first:rounded-none first:border-t relative; @apply border-tint-subtle transition-all border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none first:rounded-t-md straight-corners:first:rounded-none first:border-t relative;
} }
.openapi-disclosure-group-header { .openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover) {
@apply flex flex-row items-baseline justify-between gap-3 relative; @apply bg-tint-subtle;
}
.openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover):has(.openapi-select:hover) {
@apply !bg-transparent;
} }
.openapi-disclosure-group-trigger { .openapi-disclosure-group-trigger {
@apply flex items-baseline transition-all hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1; @apply flex w-full cursor-pointer items-baseline gap-3 transition-all relative flex-1 p-3 -outline-offset-1;
} }
.openapi-disclosure-group-trigger:disabled { .openapi-disclosure-group-label {
@apply flex flex-wrap items-baseline gap-x-3 gap-y-1 flex-1 truncate;
}
.openapi-disclosure-group-trigger[aria-disabled="true"] {
@apply cursor-default hover:bg-inherit; @apply cursor-default hover:bg-inherit;
} }
.openapi-disclosure-group-trigger:disabled .openapi-disclosure-group-icon { .openapi-disclosure-group-trigger[aria-disabled="true"] .openapi-disclosure-group-icon {
@apply invisible; @apply invisible;
} }
.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-description {
@apply whitespace-normal;
}
.openapi-disclosure-group-icon > svg { .openapi-disclosure-group-icon > svg {
@apply size-3 text-tint-subtle transition-all duration-300; @apply size-3 text-tint-subtle transition-all duration-300;
} }
@@ -735,29 +716,20 @@ body:has(.openapi-select-popover) {
} }
.openapi-disclosure-group-panel { .openapi-disclosure-group-panel {
@apply p-3 pt-1 transition-all; @apply px-3 transition-all;
} }
.openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg { .openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg {
@apply rotate-90; @apply rotate-90;
} }
.openapi-disclosure-group:hover .openapi-disclosure-group-mediatype, .openapi-disclosure-group-mediatype:not(:has(.openapi-select)) {
.openapi-disclosure-group-mediatype:has(> .openapi-select[data-open="true"]) { @apply text-[0.625rem] font-mono shrink-0 grow-0 text-tint-subtle contrast-more:text-tint;
@apply opacity-11 visible flex;
}
.openapi-disclosure-group-mediatype {
@apply opacity-0 invisible text-xs transition-opacity duration-300 shrink-0 absolute right-2.5 top-2.5;
}
.openapi-disclosure-group-mediatype > span {
@apply px-1 bg-tint-6 text-tint-12 rounded-full straight-corners:rounded-md;
} }
/* Disclosure */ /* Disclosure */
.openapi-schemas-disclosure > .openapi-disclosure-trigger { .openapi-schemas-disclosure > .openapi-disclosure-trigger {
@apply flex items-center font-mono !w-full transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1; @apply flex items-center font-mono transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
} }
.openapi-schemas-disclosure > .openapi-disclosure-trigger, .openapi-schemas-disclosure > .openapi-disclosure-trigger,
@@ -765,51 +737,97 @@ body:has(.openapi-select-popover) {
@apply straight-corners:!rounded-none; @apply straight-corners:!rounded-none;
} }
.openapi-disclosure-panel {
@apply ml-1.5 pl-3 border-l border-tint-subtle;
}
.openapi-schema .openapi-schema-properties .openapi-schema {
@apply animate-fadeIn [animation-fill-mode:both];
}
.openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg { .openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg {
@apply rotate-90; @apply rotate-90;
} }
.openapi-disclosure-trigger { .openapi-disclosure-trigger {
@apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl straight-corners:rounded border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1; @apply flex flex-row justify-between flex-wrap relative items-start gap-2 text-left -mx-3 px-3 -my-2.5 py-2.5 pr-10;
} }
.openapi-disclosure-trigger span { .openapi-disclosure {
@apply truncate; @apply -mx-3 px-3 py-2.5 transition-all flex flex-col ring-tint-subtle;
} }
.openapi-disclosure svg { .openapi-disclosure:not(
@apply size-3 shrink-0 transition-transform duration-300; .openapi-disclosure-group .openapi-disclosure,
.openapi-schema-alternatives .openapi-disclosure,
.openapi-schemas-disclosure .openapi-schema.openapi-disclosure
) {
@apply rounded-xl;
}
.openapi-disclosure .openapi-schemas-disclosure .openapi-schema.openapi-disclosure {
@apply !rounded-none;
}
.openapi-disclosure:has(> .openapi-disclosure-trigger:hover) {
@apply bg-tint-subtle overflow-hidden;
}
.openapi-disclosure:has(> .openapi-disclosure-trigger:hover),
.openapi-disclosure[data-expanded="true"] {
@apply ring-1 shadow-sm;
}
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:first-child) {
@apply mt-2;
}
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:last-child) {
@apply mb-2;
}
.openapi-disclosure-trigger-label {
@apply absolute right-3 px-2 h-5 justify-end shrink-0 ring-tint-subtle truncate text-tint duration-300 transition-all rounded straight-corners:rounded-none flex flex-row gap-1 items-center text-xs;
}
.openapi-disclosure-trigger-label span {
@apply hidden;
}
.openapi-disclosure-trigger-label svg {
@apply size-3 shrink-0 transition-transform duration-300 text-tint-subtle;
}
.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label,
.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label {
@apply shadow ring-1 bg-tint-base;
}
.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label span,
.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label span {
@apply block animate-fadeIn;
}
@media (hover: none) {
/* Make button label always visible on non-hover devices like phones */
.openapi-disclosure-trigger-label {
@apply relative ring-1 bg-tint-base right-0;
}
.openapi-disclosure-trigger-label span {
@apply block;
}
.openapi-disclosure-trigger {
@apply pr-3;
}
} }
.openapi-disclosure-trigger[aria-expanded="true"] svg { .openapi-disclosure-trigger[aria-expanded="true"] svg {
@apply rotate-45; @apply rotate-45;
} }
.openapi-disclosure-trigger[aria-expanded="true"] {
@apply w-full rounded-lg border-b rounded-b-none straight-corners:rounded-b-none;
}
.openapi-disclosure-trigger[aria-expanded="false"] { .openapi-disclosure-trigger[aria-expanded="false"] {
@apply w-auto; @apply w-auto;
} }
.openapi-disclosure-panel[aria-hidden="false"] {
@apply border-b border-x border-tint-subtle rounded-b-lg straight-corners:rounded-b;
}
.openapi-disclosure-panel .openapi-schema {
@apply p-2.5;
}
.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child,
.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child .openapi-schema-name {
@apply !m-0;
}
.openapi-disclosure .openapi-schema-properties .openapi-schema-enum {
@apply pt-0 mt-0;
}
.openapi-section-body.openapi-schema.openapi-schema-root { .openapi-section-body.openapi-schema.openapi-schema-root {
@apply space-y-2.5; @apply space-y-2.5;
} }
@@ -819,6 +837,16 @@ body:has(.openapi-select-popover) {
@apply p-2.5; @apply p-2.5;
} }
.openapi-schema-alternatives {
@apply ml-1.5 pl-3 border-l border-tint-subtle;
}
.openapi-schema-alternative {
@apply relative;
}
.openapi-schema-alternative-separator {
@apply p-0.5 tracking-wide leading-none uppercase text-[0.625rem] text-tint-subtle whitespace-nowrap absolute -left-3 -bottom-2.5 -translate-x-1/2 z-10 bg-tint-base border-y border-tint-subtle -rotate-6;
}
.openapi-tooltip { .openapi-tooltip {
@apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none font-medium px-1.5 py-0.5 shadow-sm text-[13px]; @apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none font-medium px-1.5 py-0.5 shadow-sm text-[13px];
} }
@@ -865,11 +893,11 @@ body:has(.openapi-select-popover) {
} }
@keyframes popover-leave { @keyframes popover-leave {
0% { from {
opacity: 1; opacity: 1;
transform: translateY(0) scale(1); transform: translateY(0) scale(1);
} }
100% { to {
opacity: 0; opacity: 0;
transform: translateY(4px) scale(0.95); transform: translateY(4px) scale(0.95);
} }
@@ -2,6 +2,7 @@ import type { DocumentBlockReusableContent } from '@gitbook/api';
import { resolveContentRef } from '@/lib/references'; import { resolveContentRef } from '@/lib/references';
import type { GitBookSpaceContext } from '@v2/lib/context';
import { getDataOrNull } from '@v2/lib/data'; import { getDataOrNull } from '@v2/lib/data';
import type { BlockProps } from './Block'; import type { BlockProps } from './Block';
import { UnwrappedBlocks } from './Blocks'; import { UnwrappedBlocks } from './Blocks';
@@ -13,15 +14,28 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
throw new Error('Expected a content context to render a reusable content block'); throw new Error('Expected a content context to render a reusable content block');
} }
const resolved = await resolveContentRef(block.data.ref, context.contentContext); const dataFetcher = block.meta?.token
if (!resolved?.reusableContent?.document) { ? context.contentContext.dataFetcher.withToken({ apiToken: block.meta.token })
: context.contentContext.dataFetcher;
const resolved = await resolveContentRef(block.data.ref, {
...context.contentContext,
dataFetcher,
});
if (!resolved?.reusableContent) {
return null;
}
const reusableContent = resolved.reusableContent.revisionReusableContent;
if (!reusableContent.document) {
return null; return null;
} }
const document = await getDataOrNull( const document = await getDataOrNull(
context.contentContext.dataFetcher.getDocument({ dataFetcher.getDocument({
spaceId: context.contentContext.space.id, spaceId: resolved.reusableContent.space.id,
documentId: resolved.reusableContent.document, documentId: reusableContent.document,
}) })
); );
@@ -29,12 +43,34 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
return null; return null;
} }
// Create a new context for reusable content block, including
// the data fetcher with the token from the block meta and the correct
// space and revision pointers.
const reusableContentContext: GitBookSpaceContext =
context.contentContext.space.id === resolved.reusableContent.space.id
? context.contentContext
: {
...context.contentContext,
dataFetcher,
space: resolved.reusableContent.space,
revisionId: resolved.reusableContent.revision,
// When the reusable content is in a different space, we don't resolve relative links to pages
// as this space might not be part of the current site.
// In the future, we might expand the logic to look up the space from the list of all spaces in the site
// and adapt the relative links to point to the correct variant.
pages: [],
shareKey: undefined,
};
return ( return (
<UnwrappedBlocks <UnwrappedBlocks
nodes={document.nodes} nodes={document.nodes}
document={document} document={document}
ancestorBlocks={[...ancestorBlocks, block]} ancestorBlocks={[...ancestorBlocks, block]}
context={context} context={{
...context,
contentContext: reusableContentContext,
}}
/> />
); );
} }
@@ -115,7 +115,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
return <Tag className={tcls(['w-full', verticalAlignment])}>{''}</Tag>; return <Tag className={tcls(['w-full', verticalAlignment])}>{''}</Tag>;
} }
const alignment = getColumnAlignment(definition); const horizontalAlignment = `[&_*]:${getColumnAlignment(definition)} ${getColumnAlignment(definition)}`;
return ( return (
<Blocks <Blocks
@@ -130,8 +130,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
'lg:space-y-3', 'lg:space-y-3',
'leading-normal', 'leading-normal',
verticalAlignment, verticalAlignment,
alignment === 'right' ? 'text-right' : null, horizontalAlignment,
alignment === 'center' ? 'text-center' : null,
]} ]}
context={context} context={context}
blockStyle={['w-full', 'max-w-[unset]']} blockStyle={['w-full', 'max-w-[unset]']}
@@ -15,14 +15,14 @@ export function RecordRow(
fixedColumns: string[]; fixedColumns: string[];
} }
) { ) {
const { view, autoSizedColumns, fixedColumns, block } = props; const { view, autoSizedColumns, fixedColumns, block, context } = props;
return ( return (
<div className={styles.row} role="row"> <div className={styles.row} role="row">
{view.columns.map((column) => { {view.columns.map((column) => {
const columnWidth = getColumnWidth({ const columnWidth = getColumnWidth({
column, column,
columnWidths: view.columnWidths, columnWidths: context.mode === 'print' ? undefined : view.columnWidths,
autoSizedColumns, autoSizedColumns,
fixedColumns, fixedColumns,
}); });
@@ -13,10 +13,10 @@ import { getColumnAlignment } from './utils';
3. Auto-size is turned off without setting a width, we then default to a fixed width of 100px 3. Auto-size is turned off without setting a width, we then default to a fixed width of 100px
*/ */
export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) { export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
const { block, view, records, style } = props; const { block, view, records, style, context } = props;
/* Calculate how many columns are auto-sized vs fixed width */ /* Calculate how many columns are auto-sized vs fixed width */
const columnWidths = view.columnWidths; const columnWidths = context.mode === 'print' ? undefined : view.columnWidths;
const autoSizedColumns = view.columns.filter((column) => !columnWidths?.[column]); const autoSizedColumns = view.columns.filter((column) => !columnWidths?.[column]);
const fixedColumns = view.columns.filter((column) => columnWidths?.[column]); const fixedColumns = view.columns.filter((column) => columnWidths?.[column]);
@@ -42,16 +42,13 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
)} )}
> >
<div role="row" className={tcls('flex', 'w-full')}> <div role="row" className={tcls('flex', 'w-full')}>
{view.columns.map((column) => { {view.columns.map((column) => (
const alignment = getColumnAlignment(block.data.definition[column]);
return (
<div <div
key={column} key={column}
role="columnheader" role="columnheader"
className={tcls( className={tcls(
styles.columnHeader, styles.columnHeader,
alignment === 'right' ? 'text-right' : null, getColumnAlignment(block.data.definition[column])
alignment === 'center' ? 'text-center' : null
)} )}
style={{ style={{
width: getColumnWidth({ width: getColumnWidth({
@@ -66,8 +63,7 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
> >
{block.data.definition[column].title} {block.data.definition[column].title}
</div> </div>
); ))}
})}
</div> </div>
</div> </div>
)} )}
@@ -23,7 +23,7 @@
} }
.columnHeader { .columnHeader {
@apply text-sm font-medium py-2 px-4 text-tint-strong; @apply text-sm font-medium py-2 px-3 text-tint-strong;
} }
.row { .row {
@@ -1,4 +1,5 @@
import type { ContentRef, DocumentTableDefinition, DocumentTableRecord } from '@gitbook/api'; import type { ContentRef, DocumentTableDefinition, DocumentTableRecord } from '@gitbook/api';
import assertNever from 'assert-never';
/** /**
* Get the value for a column in a record. * Get the value for a column in a record.
@@ -14,11 +15,24 @@ export function getRecordValue<T extends number | string | boolean | string[] |
/** /**
* Get the text alignment for a column. * Get the text alignment for a column.
*/ */
export function getColumnAlignment(column: DocumentTableDefinition): 'left' | 'right' | 'center' { export function getColumnAlignment(column: DocumentTableDefinition) {
const defaultAlignment = 'text-left';
if (column.type === 'text') { if (column.type === 'text') {
return column.textAlignment ?? 'left'; switch (column.textAlignment) {
case undefined:
case 'left':
return defaultAlignment;
case 'center':
return 'text-center';
case 'right':
return 'text-right';
default:
assertNever(column.textAlignment);
} }
return 'left'; }
return defaultAlignment;
} }
/** /**
@@ -5,6 +5,8 @@ import React, { useCallback, useMemo } from 'react';
import { useHash, useIsMounted } from '@/components/hooks'; import { useHash, useIsMounted } from '@/components/hooks';
import * as storage from '@/lib/local-storage'; import * as storage from '@/lib/local-storage';
import { type ClassValue, tcls } from '@/lib/tailwind'; import { type ClassValue, tcls } from '@/lib/tailwind';
import type { DocumentBlockTabs } from '@gitbook/api';
import { HashLinkButton, hashLinkButtonWrapperStyles } from '../HashLinkButton';
interface TabsState { interface TabsState {
activeIds: { activeIds: {
@@ -68,9 +70,10 @@ export function DynamicTabs(
props: TabsInput & { props: TabsInput & {
tabsBody: React.ReactNode[]; tabsBody: React.ReactNode[];
style: ClassValue; style: ClassValue;
block: DocumentBlockTabs;
} }
) { ) {
const { id, tabs, tabsBody, style } = props; const { id, block, tabs, tabsBody, style } = props;
const hash = useHash(); const hash = useHash();
const [tabsState, setTabsState] = useTabsState(); const [tabsState, setTabsState] = useTabsState();
@@ -146,8 +149,8 @@ export function DynamicTabs(
'ring-inset', 'ring-inset',
'ring-tint-subtle', 'ring-tint-subtle',
'flex', 'flex',
'overflow-hidden',
'flex-col', 'flex-col',
'overflow-hidden',
style style
)} )}
> >
@@ -165,16 +168,14 @@ export function DynamicTabs(
)} )}
> >
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <div
key={tab.id} key={tab.id}
role="tab"
aria-selected={active.id === tab.id}
aria-controls={getTabPanelId(tab.id)}
id={getTabButtonId(tab.id)}
onClick={() => {
onSelectTab(tab);
}}
className={tcls( className={tcls(
hashLinkButtonWrapperStyles,
'flex',
'items-center',
'gap-3.5',
//prev from active-tab //prev from active-tab
'[&:has(+_.active-tab)]:rounded-br-md', '[&:has(+_.active-tab)]:rounded-br-md',
@@ -184,14 +185,6 @@ export function DynamicTabs(
//next from active-tab //next from active-tab
'[.active-tab_+_:after]:rounded-br-md', '[.active-tab_+_:after]:rounded-br-md',
'inline-block',
'text-sm',
'px-3.5',
'py-2',
'transition-[color]',
'font-[500]',
'relative',
'after:transition-colors', 'after:transition-colors',
'after:border-r', 'after:border-r',
'after:absolute', 'after:absolute',
@@ -202,14 +195,16 @@ export function DynamicTabs(
'after:h-[70%]', 'after:h-[70%]',
'after:w-[1px]', 'after:w-[1px]',
'px-3.5',
'py-2',
'last:after:border-transparent', 'last:after:border-transparent',
'text-tint', 'text-tint',
'bg-tint-12/1', 'bg-tint-12/1',
'hover:text-tint-strong', 'hover:text-tint-strong',
'truncate',
'max-w-full', 'max-w-full',
'truncate',
active.id === tab.id active.id === tab.id
? [ ? [
@@ -223,9 +218,35 @@ export function DynamicTabs(
] ]
: null : null
)} )}
>
<button
type="button"
role="tab"
aria-selected={active.id === tab.id}
aria-controls={getTabPanelId(tab.id)}
id={getTabButtonId(tab.id)}
onClick={() => {
onSelectTab(tab);
}}
className={tcls(
'inline-block',
'text-sm',
'transition-[color]',
'font-[500]',
'relative',
'max-w-full',
'truncate'
)}
> >
{tab.title} {tab.title}
</button> </button>
<HashLinkButton
id={getTabButtonId(tab.id)}
block={block}
label="Direct link to tab"
/>
</div>
))} ))}
</div> </div>
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
@@ -39,6 +39,7 @@ export function Tabs(props: BlockProps<DocumentBlockTabs>) {
<DynamicTabs <DynamicTabs
key={tab.id} key={tab.id}
id={block.key!} id={block.key!}
block={block}
tabs={[tab]} tabs={[tab]}
tabsBody={[tabsBody[index]]} tabsBody={[tabsBody[index]]}
style={style} style={style}
@@ -48,5 +49,7 @@ export function Tabs(props: BlockProps<DocumentBlockTabs>) {
); );
} }
return <DynamicTabs id={block.key!} tabs={tabs} tabsBody={tabsBody} style={style} />; return (
<DynamicTabs id={block.key!} block={block} tabs={tabs} tabsBody={tabsBody} style={style} />
);
} }
@@ -10,6 +10,8 @@ export function getBlockTextStyle(block: DocumentBlock): {
lineHeight: string; lineHeight: string;
/** Tailwind class for the margin top (mt-*) */ /** Tailwind class for the margin top (mt-*) */
marginTop?: string; marginTop?: string;
/** Tailwind class for the margin top to apply on the anchor link button */
anchorButtonMarginTop?: string;
} { } {
switch (block.type) { switch (block.type) {
case 'paragraph': case 'paragraph':
@@ -22,18 +24,21 @@ export function getBlockTextStyle(block: DocumentBlock): {
textSize: 'text-3xl font-semibold', textSize: 'text-3xl font-semibold',
lineHeight: 'leading-tight', lineHeight: 'leading-tight',
marginTop: 'mt-[1em]', marginTop: 'mt-[1em]',
anchorButtonMarginTop: 'mt-[1.05em]',
}; };
case 'heading-2': case 'heading-2':
return { return {
textSize: 'text-2xl font-semibold', textSize: 'text-2xl font-semibold',
lineHeight: 'leading-snug', lineHeight: 'leading-snug',
marginTop: 'mt-[0.75em]', marginTop: 'mt-[0.75em]',
anchorButtonMarginTop: 'mt-[0.9em]',
}; };
case 'heading-3': case 'heading-3':
return { return {
textSize: 'text-xl font-semibold', textSize: 'text-xl font-semibold',
lineHeight: 'leading-snug', lineHeight: 'leading-snug',
marginTop: 'mt-[0.5em]', marginTop: 'mt-[0.5em]',
anchorButtonMarginTop: 'mt-[0.65em]',
}; };
case 'divider': case 'divider':
return { return {
@@ -1,143 +0,0 @@
import { Icon } from '@gitbook/icons';
import { type DetailedHTMLProps, type HTMLAttributes, useId } from 'react';
import { type ClassValue, tcls } from '@/lib/tailwind';
import { Link, type LinkInsightsProps } from '../primitives';
export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
Partial<DetailedHTMLProps<HTMLAttributes<E>, E>>,
'ref'
>;
/**
* Button with a dropdown.
*/
export function Dropdown<E extends HTMLElement>(props: {
/** 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;
const dropdownId = useId();
return (
<div className={tcls('group/dropdown', 'relative flex min-w-0 shrink')}>
{button({
id: dropdownId,
tabIndex: 0,
'aria-expanded': true,
'aria-haspopup': true,
})}
<div
tabIndex={-1}
role="menu"
aria-orientation="vertical"
aria-labelledby={dropdownId}
className={tcls(
'w-52',
'max-h-80',
'flex',
'absolute',
'top-full',
'left-0',
'origin-top-left',
'invisible',
'transition-opacity',
'duration-1000',
'group-hover/dropdown:visible',
'group-focus-within/dropdown:visible',
className
)}
>
<div className="fixed z-50 w-52">
<div
className={tcls(
'mt-2',
'w-full',
'max-h-80',
'bg-tint-base',
'rounded-lg',
'straight-corners:rounded-sm',
'p-2',
'shadow-1xs',
'overflow-auto',
'ring-1',
'ring-tint-subtle',
'focus:outline-none'
)}
>
{children}
</div>
</div>
</div>
</div>
);
}
/**
* Animated chevron to display in the dropdown button.
*/
export function DropdownChevron() {
return (
<Icon
icon="chevron-down"
className={tcls(
'shrink-0',
'opacity-6',
'size-3',
'ms-1',
'transition-all',
'group-hover/dropdown:opacity-11',
'group-focus-within/dropdown:rotate-180'
)}
/>
);
}
/**
* Group of menu items in a dropdown.
*/
export function DropdownMenu(props: { children: React.ReactNode }) {
const { children } = props;
return <div className={tcls('flex', 'flex-col', 'gap-1')}>{children}</div>;
}
/**
* Menu item in a dropdown.
*/
export function DropdownMenuItem(
props: {
href: string | null;
active?: boolean;
className?: ClassValue;
children: React.ReactNode;
} & LinkInsightsProps
) {
const { children, active = false, href, className, insights } = props;
if (href) {
return (
<Link
href={href}
insights={insights}
className={tcls(
'rounded straight-corners:rounded-sm px-3 py-1 text-sm',
active ? 'bg-primary text-primary-strong' : null,
'hover:bg-tint-hover',
className
)}
>
{children}
</Link>
);
}
return (
<div className={tcls('px-3 py-1 font-medium text-tint text-xs', className)}>{children}</div>
);
}
@@ -0,0 +1,176 @@
'use client';
import { Icon } from '@gitbook/icons';
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
import { useState } from 'react';
import { type ClassValue, tcls } from '@/lib/tailwind';
import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu';
import { Link, type LinkInsightsProps } from '../primitives';
export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
Partial<DetailedHTMLProps<HTMLAttributes<E>, E>>,
'ref'
>;
/**
* Button with a dropdown.
*/
export function DropdownMenu(props: {
/** Content of the button */
button: React.ReactNode;
/** Content of the dropdown */
children: React.ReactNode;
/** Custom styles */
className?: ClassValue;
/** Open the dropdown on hover */
openOnHover?: boolean;
}) {
const { button, children, className, openOnHover = false } = props;
const [hovered, setHovered] = useState(false);
const [clicked, setClicked] = useState(false);
return (
<RadixDropdownMenu.Root
modal={false}
open={openOnHover ? clicked || hovered : clicked}
onOpenChange={setClicked}
>
<RadixDropdownMenu.Trigger
asChild
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onClick={() => (openOnHover ? setClicked(!clicked) : null)}
className="group/dropdown"
>
{button}
</RadixDropdownMenu.Trigger>
<RadixDropdownMenu.Portal>
<RadixDropdownMenu.Content
data-testid="dropdown-menu"
hideWhenDetached
collisionPadding={8}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
align="start"
className="z-40 animate-present pt-2"
>
<div
className={tcls(
'flex max-h-80 min-w-40 max-w-[40vw] flex-col gap-1 overflow-auto rounded-lg straight-corners:rounded-sm bg-tint-base p-2 shadow-lg ring-1 ring-tint-subtle sm:min-w-52 sm:max-w-80',
className
)}
>
{children}
</div>
</RadixDropdownMenu.Content>
</RadixDropdownMenu.Portal>
</RadixDropdownMenu.Root>
);
}
/**
* Animated chevron to display in the dropdown button.
*/
export function DropdownChevron() {
return (
<Icon
icon="chevron-down"
className={tcls(
'shrink-0',
'opacity-6',
'size-3',
'ms-1',
'transition-all',
'group-hover/dropdown:opacity-11',
'group-data-[state=open]/dropdown:opacity-11',
'group-data-[state=open]/dropdown:rotate-180'
)}
/>
);
}
/**
* Button with a chevron for use in dropdowns.
*/
export function DropdownButton(props: {
children: React.ReactNode;
className?: ClassValue;
}) {
const { children, className } = props;
return (
<div className={tcls('group/dropdown', 'flex', 'items-center', className)}>
{children}
<DropdownChevron />
</div>
);
}
/**
* Menu item in a dropdown.
*/
export function DropdownMenuItem(
props: {
href: string | null;
active?: boolean;
className?: ClassValue;
children: React.ReactNode;
} & LinkInsightsProps
) {
const { children, active = false, href, className, insights } = props;
const itemClassName = tcls(
'rounded straight-corners:rounded-sm px-3 py-1 text-sm',
active
? 'bg-primary text-primary-strong data-[highlighted]:bg-primary-hover'
: 'data-[highlighted]:bg-tint-hover',
'focus:outline-none',
className
);
if (href) {
return (
<RadixDropdownMenu.Item asChild>
<Link href={href} insights={insights} className={itemClassName}>
{children}
</Link>
</RadixDropdownMenu.Item>
);
}
return (
<RadixDropdownMenu.Item
className={tcls('px-3 py-1 font-medium text-tint text-xs', className)}
>
{children}
</RadixDropdownMenu.Item>
);
}
export function DropdownSubMenu(props: { children: React.ReactNode; label: React.ReactNode }) {
const { children, label } = props;
return (
<RadixDropdownMenu.Sub>
<RadixDropdownMenu.SubTrigger className="flex cursor-pointer items-center justify-between rounded straight-corners:rounded-sm px-3 py-1 text-sm focus:outline-none data-[highlighted]:bg-tint-hover">
{label}
<Icon icon="chevron-right" className="size-3 shrink-0 opacity-6" />
</RadixDropdownMenu.SubTrigger>
<RadixDropdownMenu.Portal>
<RadixDropdownMenu.SubContent
hideWhenDetached
collisionPadding={8}
className="z-40 animate-present"
>
<div className="flex max-h-80 min-w-40 max-w-[40vw] flex-col gap-1 overflow-auto rounded-lg straight-corners:rounded-sm bg-tint-base p-2 shadow-lg ring-1 ring-tint-subtle sm:min-w-52 sm:max-w-80">
{children}
</div>
</RadixDropdownMenu.SubContent>
</RadixDropdownMenu.Portal>
</RadixDropdownMenu.Sub>
);
}
@@ -13,12 +13,11 @@ import { tcls } from '@/lib/tailwind';
import { Button, Link } from '../primitives'; import { Button, Link } from '../primitives';
import { import {
Dropdown,
type DropdownButtonProps, type DropdownButtonProps,
DropdownChevron, DropdownChevron,
DropdownMenu, DropdownMenu,
DropdownMenuItem, DropdownMenuItem,
} from './Dropdown'; } from './DropdownMenu';
export async function HeaderLink(props: { export async function HeaderLink(props: {
context: GitBookSiteContext; context: GitBookSiteContext;
@@ -33,21 +32,13 @@ export async function HeaderLink(props: {
if (link.links && link.links.length > 0) { if (link.links && link.links.length > 0) {
return ( return (
<Dropdown <DropdownMenu
className={`shrink ${customization.styling.search === 'prominent' ? 'right-0 left-auto' : null}`} className={`shrink ${customization.styling.search === 'prominent' ? 'right-0 left-auto' : null}`}
button={(buttonProps) => { button={
if (!target || !link.to) { !target || !link.to ? (
return ( <HeaderItemDropdown headerPreset={headerPreset} title={link.title} />
<HeaderItemDropdown ) : (
{...buttonProps}
headerPreset={headerPreset}
title={link.title}
/>
);
}
return (
<HeaderLinkNavItem <HeaderLinkNavItem
{...buttonProps}
linkTarget={link.to} linkTarget={link.to}
linkStyle={linkStyle} linkStyle={linkStyle}
headerPreset={headerPreset} headerPreset={headerPreset}
@@ -55,15 +46,14 @@ export async function HeaderLink(props: {
isDropdown isDropdown
href={target?.href} href={target?.href}
/> />
); )
}} }
openOnHover={true}
> >
<DropdownMenu>
{link.links.map((subLink, index) => ( {link.links.map((subLink, index) => (
<SubHeaderLink key={index} {...props} link={subLink} /> <SubHeaderLink key={index} {...props} link={subLink} />
))} ))}
</DropdownMenu> </DropdownMenu>
</Dropdown>
); );
} }
@@ -157,10 +147,12 @@ function getHeaderLinkClassName(_props: { headerPreset: CustomizationHeaderPrese
'text-tint', 'text-tint',
'links-default:hover:text-primary', 'links-default:hover:text-primary',
'links-default:data-[state=open]:text-primary',
'links-default:tint:hover:text-tint-strong', 'links-default:tint:hover:text-tint-strong',
'links-default:tint:data-[state=open]:text-tint-strong',
'underline-offset-2', 'underline-offset-2',
'links-accent:hover:underline', 'links-accent:hover:underline',
'links-accent:data-[state=open]:underline',
'links-accent:underline-offset-4', 'links-accent:underline-offset-4',
'links-accent:decoration-primary-subtle', 'links-accent:decoration-primary-subtle',
'links-accent:decoration-[3px]', 'links-accent:decoration-[3px]',
@@ -10,7 +10,7 @@ import type React from 'react';
import { resolveContentRef } from '@/lib/references'; import { resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import { Dropdown, DropdownChevron, DropdownMenu, DropdownMenuItem } from './Dropdown'; import { DropdownChevron, DropdownMenu, DropdownMenuItem, DropdownSubMenu } from './DropdownMenu';
import styles from './headerLinks.module.css'; import styles from './headerLinks.module.css';
/** /**
@@ -23,7 +23,7 @@ export function HeaderLinkMore(props: {
}) { }) {
const { label, links, context } = props; const { label, links, context } = props;
const renderButton = () => ( const renderButton = (
<button <button
type="button" type="button"
className={tcls( className={tcls(
@@ -45,19 +45,18 @@ export function HeaderLinkMore(props: {
return ( return (
<div className={`${styles.linkEllipsis} z-20 items-center`}> <div className={`${styles.linkEllipsis} z-20 items-center`}>
<Dropdown <DropdownMenu
button={renderButton} button={renderButton}
openOnHover={true}
className={tcls( className={tcls(
'max-md:right-0 max-md:left-auto', 'max-md:right-0 max-md:left-auto',
context.customization.styling.search === 'prominent' && 'right-0 left-auto' context.customization.styling.search === 'prominent' && 'right-0 left-auto'
)} )}
> >
<DropdownMenu>
{links.map((link, index) => ( {links.map((link, index) => (
<MoreMenuLink key={index} link={link} context={context} /> <MoreMenuLink key={index} link={link} context={context} />
))} ))}
</DropdownMenu> </DropdownMenu>
</Dropdown>
</div> </div>
); );
} }
@@ -70,11 +69,13 @@ async function MoreMenuLink(props: {
const target = link.to ? await resolveContentRef(link.to, context) : null; const target = link.to ? await resolveContentRef(link.to, context) : null;
return ( return 'links' in link && link.links.length > 0 ? (
<> <DropdownSubMenu label={link.title}>
{'links' in link && link.links.length > 0 && ( {link.links.map((subLink, index) => {
<hr className="-mx-2 my-1 border-tint border-t first:hidden" /> return <MoreMenuLink key={index} {...props} link={subLink} />;
)} })}
</DropdownSubMenu>
) : (
<DropdownMenuItem <DropdownMenuItem
href={target?.href ?? null} href={target?.href ?? null}
insights={ insights={
@@ -91,11 +92,5 @@ async function MoreMenuLink(props: {
> >
{link.title} {link.title}
</DropdownMenuItem> </DropdownMenuItem>
{'links' in link
? link.links.map((subLink, index) => (
<MoreMenuLink key={index} {...props} link={subLink} />
))
: null}
</>
); );
} }
@@ -20,7 +20,7 @@ export async function HeaderLogo(props: HeaderLogoProps) {
return ( return (
<Link <Link
href={linker.toAbsoluteURL(linker.toPathInSpace(''))} href={linker.toAbsoluteURL(linker.toPathInSite(''))}
className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')} className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')}
> >
{customization.header.logo ? ( {customization.header.logo ? (
@@ -48,8 +48,6 @@ export async function HeaderLogo(props: HeaderLogoProps) {
]} ]}
priority="high" priority="high"
style={tcls( style={tcls(
'rounded',
'straight-corners:rounded-sm',
'overflow-hidden', 'overflow-hidden',
'shrink', 'shrink',
'min-w-0', 'min-w-0',
@@ -3,7 +3,7 @@ import type { SiteSpace } from '@gitbook/api';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import type { GitBookSiteContext } from '@v2/lib/context'; import type { GitBookSiteContext } from '@v2/lib/context';
import { Dropdown, DropdownChevron, DropdownMenu } from './Dropdown'; import { DropdownChevron, DropdownMenu } from './DropdownMenu';
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem'; import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
export function SpacesDropdown(props: { export function SpacesDropdown(props: {
@@ -16,14 +16,13 @@ export function SpacesDropdown(props: {
const { linker } = context; const { linker } = context;
return ( return (
<Dropdown <DropdownMenu
className={tcls( className={tcls(
'group-hover/dropdown:invisible', // Prevent hover from opening the dropdown, as it's annoying in this context 'group-hover/dropdown:invisible', // Prevent hover from opening the dropdown, as it's annoying in this context
'group-focus-within/dropdown:group-hover/dropdown:visible' // When the dropdown is already open, it should remain visible when hovered 'group-focus-within/dropdown:group-hover/dropdown:visible' // When the dropdown is already open, it should remain visible when hovered
)} )}
button={(buttonProps) => ( button={
<div <div
{...buttonProps}
data-testid="space-dropdown-button" data-testid="space-dropdown-button"
className={tcls( className={tcls(
'flex', 'flex',
@@ -40,25 +39,24 @@ export function SpacesDropdown(props: {
'straight-corners:rounded-none', 'straight-corners:rounded-none',
'bg-tint-base', 'bg-tint-base',
'group-hover/dropdown:bg-tint-base',
'group-focus-within/dropdown:bg-tint-base',
'text-sm', 'text-sm',
'text-tint', 'text-tint',
'group-hover/dropdown:text-tint-strong', 'hover:text-tint-strong',
'group-focus-within/dropdown:text-tint-strong', 'data-[state=open]:text-tint-strong',
'ring-1', 'ring-1',
'ring-tint-subtle', 'ring-tint-subtle',
'group-hover/dropdown:ring-tint-hover', 'hover:ring-tint-hover',
'group-focus-within/dropdown:ring-tint-hover', 'data-[state=open]:ring-tint-hover',
'contrast-more:bg-tint-base', 'contrast-more:bg-tint-base',
'contrast-more:ring-1', 'contrast-more:ring-1',
'contrast-more:group-hover/dropdown:ring-2', 'contrast-more:hover:ring-2',
'contrast-more:data-[state=open]:ring-2',
'contrast-more:ring-tint', 'contrast-more:ring-tint',
'contrast-more:group-hover/dropdown:ring-tint-hover', 'contrast-more:hover:ring-tint-hover',
'contrast-more:group-focus-within/dropdown:ring-tint-hover', 'contrast-more:data-[state=open]:ring-tint-hover',
className className
)} )}
@@ -66,9 +64,8 @@ export function SpacesDropdown(props: {
<span className={tcls('line-clamp-1', 'grow')}>{siteSpace.title}</span> <span className={tcls('line-clamp-1', 'grow')}>{siteSpace.title}</span>
<DropdownChevron /> <DropdownChevron />
</div> </div>
)} }
> >
<DropdownMenu>
{siteSpaces.map((otherSiteSpace, index) => ( {siteSpaces.map((otherSiteSpace, index) => (
<SpacesDropdownMenuItem <SpacesDropdownMenuItem
key={`${otherSiteSpace.id}-${index}`} key={`${otherSiteSpace.id}-${index}`}
@@ -83,6 +80,5 @@ export function SpacesDropdown(props: {
/> />
))} ))}
</DropdownMenu> </DropdownMenu>
</Dropdown>
); );
} }
@@ -4,7 +4,7 @@ import type { Space } from '@gitbook/api';
import { joinPath } from '@/lib/paths'; import { joinPath } from '@/lib/paths';
import { useCurrentPagePath } from '../hooks'; import { useCurrentPagePath } from '../hooks';
import { DropdownMenuItem } from './Dropdown'; import { DropdownMenuItem } from './DropdownMenu';
function useVariantSpaceHref(variantSpaceUrl: string) { function useVariantSpaceHref(variantSpaceUrl: string) {
const currentPathname = useCurrentPagePath(); const currentPathname = useCurrentPagePath();
@@ -87,7 +87,7 @@ export function PageAside(props: {
'page-api-block:xl:max-2xl:dark:hover:shadow-tint-1/1', 'page-api-block:xl:max-2xl:dark:hover:shadow-tint-1/1',
'page-api-block:xl:max-2xl:rounded-md', 'page-api-block:xl:max-2xl:rounded-md',
'page-api-block:xl:max-2xl:h-auto', 'page-api-block:xl:max-2xl:h-auto',
'page-api-block:xl:max-2xl:my-8', 'page-api-block:xl:max-2xl:my-4',
'page-api-block:p-2' 'page-api-block:p-2'
)} )}
> >
@@ -18,7 +18,7 @@ export function RocketLoaderDetector(props: { nonce?: string }) {
alert.className = 'p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 mt-8 mx-8'; alert.className = 'p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 mt-8 mx-8';
alert.innerHTML = \` alert.innerHTML = \`
<strong>Error in site configuration:</strong> <strong>Error in site configuration:</strong>
It looks like \${window.location.hostname} has been incorrectly configured in Cloudflare. This may lead to unexpected behavior or issues with the page loading. If you are the owner of this site, please refer to <a href="https://docs.gitbook.com/published-documentation/custom-domain/configure-dns#are-you-using-cloudflare" class="underline">GitBook's documentation</a> for steps to fix the problem. It looks like \${window.location.hostname} has been incorrectly configured in Cloudflare. This may lead to unexpected behavior or issues with the page loading. If you are the owner of this site, please refer to <a href="https://gitbook.com/docs/published-documentation/custom-domain/configure-dns#are-you-using-cloudflare" class="underline">GitBook's documentation</a> for steps to fix the problem.
\`; \`;
document.body.prepend(alert); document.body.prepend(alert);
@@ -69,7 +69,11 @@ async function resolvePage(context: GitBookSiteContext, params: PagePathParams |
// If a page still can't be found, we try with the API, in case we have a redirect at site level. // If a page still can't be found, we try with the API, in case we have a redirect at site level.
const redirectPathname = withLeadingSlash(rawPathname); const redirectPathname = withLeadingSlash(rawPathname);
if (/^\/[a-zA-Z0-9-_.\/]+[a-zA-Z0-9-_.]$/.test(redirectPathname)) { if (
/^\/(?:[A-Za-z0-9\-._~]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~]|%[0-9A-Fa-f]{2})+)*$/.test(
redirectPathname
)
) {
const redirectSources = new Set<string>([ const redirectSources = new Set<string>([
// Test the pathname relative to the root // Test the pathname relative to the root
// For example hello/world -> section/variant/hello/world // For example hello/world -> section/variant/hello/world
@@ -75,13 +75,13 @@ export function SiteSectionListItem(props: {
const isMounted = useIsMounted(); const isMounted = useIsMounted();
React.useEffect(() => {}, [isMounted]); // This updates the useScrollToActiveTOCItem hook once we're mounted, so we can actually scroll to the this item React.useEffect(() => {}, [isMounted]); // This updates the useScrollToActiveTOCItem hook once we're mounted, so we can actually scroll to the this item
const linkRef = React.createRef<HTMLAnchorElement>(); const anchorRef = React.createRef<HTMLAnchorElement>();
useScrollToActiveTOCItem({ linkRef, isActive }); useScrollToActiveTOCItem({ anchorRef, isActive });
return ( return (
<Link <Link
ref={anchorRef}
href={section.url} href={section.url}
ref={linkRef}
aria-current={isActive && 'page'} aria-current={isActive && 'page'}
className={tcls( 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', '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,18 +121,15 @@ export function SiteSectionGroupItem(props: {
const hasDescendants = group.sections.length > 0; const hasDescendants = group.sections.length > 0;
const isActiveGroup = group.sections.some((section) => section.id === currentSection.id); const isActiveGroup = group.sections.some((section) => section.id === currentSection.id);
const [isVisible, setIsVisible] = React.useState(isActiveGroup); const shouldOpen = hasDescendants && isActiveGroup;
const [isOpen, setIsOpen] = React.useState(shouldOpen);
// Update the visibility of the children, if we are navigating to a descendant. // Update the visibility of the children if the group becomes active.
React.useEffect(() => { React.useEffect(() => {
if (!hasDescendants) { if (shouldOpen) {
return; setIsOpen(shouldOpen);
} }
}, [shouldOpen]);
setIsVisible((prev) => prev || isActiveGroup);
}, [isActiveGroup, hasDescendants]);
const { show, hide, scope } = useToggleAnimation({ hasDescendants, isVisible });
return ( return (
<> <>
@@ -141,7 +138,7 @@ export function SiteSectionGroupItem(props: {
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setIsVisible((prev) => !prev); setIsOpen((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 ${ 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 isActiveGroup
@@ -184,7 +181,7 @@ export function SiteSectionGroupItem(props: {
'after:h-7', 'after:h-7',
'hover:bg-tint-active', 'hover:bg-tint-active',
'hover:text-current', 'hover:text-current',
isActiveGroup ? ['hover:bg-tint-hover'] : [] isActiveGroup && 'hover:bg-tint-hover'
)} )}
> >
<Icon <Icon
@@ -201,17 +198,13 @@ export function SiteSectionGroupItem(props: {
'group-hover:opacity-11', 'group-hover:opacity-11',
'contrast-more:opacity-11', 'contrast-more:opacity-11',
isVisible ? ['rotate-90'] : ['rotate-0'] isOpen ? 'rotate-90' : 'rotate-0'
)} )}
/> />
</span> </span>
</button> </button>
{hasDescendants ? ( {hasDescendants ? (
<motion.div <Descendants isVisible={isOpen}>
ref={scope}
className={tcls(isVisible ? null : '[&_ul>li]:opacity-1')}
initial={isVisible ? show : hide}
>
{group.sections.map((section) => ( {group.sections.map((section) => (
<SiteSectionListItem <SiteSectionListItem
section={section} section={section}
@@ -220,8 +213,25 @@ export function SiteSectionGroupItem(props: {
className="pl-5" className="pl-5"
/> />
))} ))}
</motion.div> </Descendants>
) : null} ) : 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>
);
}
@@ -83,6 +83,12 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
) )
} }
asChild asChild
onClick={(e) => {
if (value) {
e.preventDefault();
e.stopPropagation();
}
}}
> >
<SectionGroupTab <SectionGroupTab
isActive={isActive} isActive={isActive}
@@ -180,7 +186,7 @@ const SectionGroupTab = React.forwardRef(function SectionGroupTab(
ref={ref} ref={ref}
{...rest} {...rest}
className={tcls( className={tcls(
'group relative my-2 flex select-none items-center justify-between rounded straight-corners:rounded-none px-3 py-1 transition-colors', 'group relative my-2 flex select-none items-center justify-between rounded straight-corners:rounded-none px-3 py-1 transition-colors hover:cursor-default',
isActive isActive
? 'text-primary-subtle' ? 'text-primary-subtle'
: 'text-tint hover:bg-tint-hover hover:text-tint-strong' : 'text-tint hover:bg-tint-hover hover:text-tint-strong'
@@ -20,7 +20,7 @@ export async function PageDocumentItem(props: {
const href = context.linker.toPathForPage({ pages: rootPages, page }); const href = context.linker.toPathForPage({ pages: rootPages, page });
return ( return (
<li className={tcls('flex', 'flex-col')}> <li className="flex flex-col">
<ToggleableLinkItem <ToggleableLinkItem
href={href} href={href}
pathname={getPagePath(rootPages, page)} pathname={getPagePath(rootPages, page)}
@@ -52,7 +52,7 @@ export async function PageDocumentItem(props: {
} }
> >
{page.emoji || page.icon ? ( {page.emoji || page.icon ? (
<span className={tcls('flex', 'gap-3', 'items-center')}> <span className="flex items-center gap-3">
<TOCPageIcon page={page} /> <TOCPageIcon page={page} />
{page.title} {page.title}
</span> </span>
@@ -15,27 +15,13 @@ export function PageGroupItem(props: {
const { rootPages, page, context } = props; const { rootPages, page, context } = props;
return ( return (
<li className={tcls('flex', 'flex-col', 'group/page-group-item')}> <li className="group/page-group-item flex flex-col">
<div <div
className={tcls( className={tcls(
'flex', '-top-5 group-first/page-group-item:-mt-5 sticky z-[1] flex items-center gap-3 px-3 pt-6',
'items-center', 'font-semibold text-xs uppercase tracking-wide',
'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 '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. '-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. '[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', 'bg-tint-base',
'sidebar-filled:bg-tint-subtle', 'sidebar-filled:bg-tint-subtle',
@@ -55,9 +55,9 @@ export async function PageLinkItem(props: { page: RevisionPageLink; context: Git
'shrink-0', 'shrink-0',
'text-current', 'text-current',
'transition-colors', 'transition-colors',
'[&>path]:transition-[opacity]', '[&>path]:transition-opacity',
'[&>path]:[opacity:0.40]', '[&>path]:opacity-[0.4]',
'group-hover:[&>path]:[opacity:1]' 'group-hover:[&>path]:opacity-11'
)} )}
/> />
</Link> </Link>
@@ -1,8 +1,9 @@
import { type RevisionPage, RevisionPageType } from '@gitbook/api'; import type { RevisionPage } from '@gitbook/api';
import type { GitBookSiteContext } from '@v2/lib/context'; import type { GitBookSiteContext } from '@v2/lib/context';
import { type ClassValue, tcls } from '@/lib/tailwind'; import { type ClassValue, tcls } from '@/lib/tailwind';
import assertNever from 'assert-never';
import { PageDocumentItem } from './PageDocumentItem'; import { PageDocumentItem } from './PageDocumentItem';
import { PageGroupItem } from './PageGroupItem'; import { PageGroupItem } from './PageGroupItem';
import { PageLinkItem } from './PageLinkItem'; import { PageLinkItem } from './PageLinkItem';
@@ -16,33 +17,20 @@ export function PagesList(props: {
const { rootPages, pages, context, style } = props; const { rootPages, pages, context, style } = props;
return ( 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) => { {pages.map((page) => {
if (page.type === RevisionPageType.Computed) { if (page.type === 'computed') {
throw new Error( throw new Error(
'Unexpected computed page, it should have been computed in the API' '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) { if (page.hidden) {
return null; return null;
} }
if (page.type === RevisionPageType.Group) { switch (page.type) {
return ( case 'document':
<PageGroupItem
key={page.id}
rootPages={rootPages}
page={page}
context={context}
/>
);
}
return ( return (
<PageDocumentItem <PageDocumentItem
key={page.id} key={page.id}
@@ -51,6 +39,23 @@ export function PagesList(props: {
context={context} 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);
}
})} })}
</ul> </ul>
); );
@@ -1,40 +1,64 @@
'use client'; 'use client';
import React from 'react'; import React, {
useCallback,
useEffect,
useMemo,
useRef,
type ComponentPropsWithoutRef,
} from 'react';
import { assert } from 'ts-essentials';
import { type ClassValue, tcls } from '@/lib/tailwind'; interface TOCScrollContainerContextType {
onContainerMount: (listener: (element: HTMLDivElement) => void) => () => void;
const TOCScrollContainerRefContext = React.createContext<React.RefObject<HTMLDivElement> | null>(
null
);
function useTOCScrollContainerRefContext() {
const ctx = React.useContext(TOCScrollContainerRefContext);
if (!ctx) {
throw new Error('Context `TOCScrollContainerRefContext` must be used within Provider');
} }
const TOCScrollContainerContext = React.createContext<TOCScrollContainerContextType | null>(null);
function useTOCScrollContainerContext() {
const ctx = React.useContext(TOCScrollContainerContext);
assert(ctx);
return ctx; return ctx;
} }
export function TOCScrollContainer(props: { /**
children: React.ReactNode; * Table of contents scroll container.
className?: ClassValue; */
style?: React.CSSProperties; export function TOCScrollContainer(props: ComponentPropsWithoutRef<'div'>) {
}) { const ref = useRef<HTMLDivElement>(null);
const { children, className, style } = props; const listeners = useRef<((element: HTMLDivElement) => void)[]>([]);
const scrollContainerRef = React.createRef<HTMLDivElement>(); 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 = [];
};
}, []);
return ( return (
<TOCScrollContainerRefContext.Provider value={scrollContainerRef}> <TOCScrollContainerContext.Provider value={value}>
<div <div ref={ref} data-testid="toc-scroll-container" {...props} />
ref={scrollContainerRef} </TOCScrollContainerContext.Provider>
data-testid="toc-scroll-container"
className={tcls(className)}
style={style}
>
{children}
</div>
</TOCScrollContainerRefContext.Provider>
); );
} }
@@ -42,39 +66,31 @@ export function TOCScrollContainer(props: {
const TOC_ITEM_OFFSET = 100; const TOC_ITEM_OFFSET = 100;
/** /**
* Scrolls the table of contents container to the page item when it becomes active * Scrolls the table of contents container to the page item when it's initially active.
*/ */
export function useScrollToActiveTOCItem(props: { export function useScrollToActiveTOCItem(props: {
anchorRef: React.RefObject<HTMLAnchorElement>;
isActive: boolean; isActive: boolean;
linkRef: React.RefObject<HTMLAnchorElement>;
}) { }) {
const { isActive, linkRef } = props; const { isActive, anchorRef } = props;
const scrollContainerRef = useTOCScrollContainerRefContext(); const isInitialActiveRef = useRef(isActive);
const isScrolled = React.useRef(false); const { onContainerMount } = useTOCScrollContainerContext();
React.useLayoutEffect(() => { useEffect(() => {
if (!isActive) { const anchor = anchorRef.current;
isScrolled.current = false; if (isInitialActiveRef.current && anchor) {
return; return onContainerMount((container) => {
if (isOutOfView(anchor, container)) {
container.scrollTo({ top: anchor.offsetTop - TOC_ITEM_OFFSET });
} }
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]); }, [onContainerMount, anchorRef]);
} }
function isOutOfView(tocItem: HTMLElement, tocContainer: HTMLElement) { function isOutOfView(element: HTMLElement, container: HTMLElement) {
const tocItemTop = tocItem.offsetTop; const tocItemTop = element.offsetTop;
const containerTop = tocContainer.scrollTop; const containerTop = container.scrollTop;
const containerBottom = containerTop + tocContainer.clientHeight; const containerBottom = containerTop + container.clientHeight;
return ( return (
tocItemTop < containerTop + TOC_ITEM_OFFSET || tocItemTop < containerTop + TOC_ITEM_OFFSET ||
tocItemTop > containerBottom - TOC_ITEM_OFFSET tocItemTop > containerBottom - TOC_ITEM_OFFSET
@@ -66,36 +66,26 @@ export function TableOfContents(props: {
<div // The actual sidebar, either shown with a filled bg or transparent. <div // The actual sidebar, either shown with a filled bg or transparent.
className={tcls( className={tcls(
'lg:-ms-5', 'lg:-ms-5',
'overflow-hidden', 'relative flex flex-grow flex-col overflow-hidden border-tint-subtle',
'relative',
'flex',
'flex-col',
'flex-grow',
'sidebar-filled:bg-tint-subtle', 'sidebar-filled:bg-tint-subtle',
'theme-muted:bg-tint-subtle', 'theme-muted:bg-tint-subtle',
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle', '[html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'[html.sidebar-filled.theme-muted_&]:bg-tint-base', '[html.sidebar-filled.theme-muted_&]:bg-tint-base',
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-base', '[html.sidebar-filled.theme-bold.tint_&]:bg-tint-base',
'[html.sidebar-filled.theme-gradient_&]:border',
'page-no-toc:!bg-transparent', 'page-no-toc:!bg-transparent',
'sidebar-filled:rounded-xl', 'sidebar-filled:rounded-xl',
'straight-corners:rounded-none' 'straight-corners:rounded-none'
)} )}
> >
{innerHeader && <div className={tcls('px-5 *:my-4')}>{innerHeader}</div>} {innerHeader && <div className="px-5 *:my-4">{innerHeader}</div>}
<TOCScrollContainer // The scrollview inside the sidebar <TOCScrollContainer // The scrollview inside the sidebar
className={tcls( className={tcls(
'flex', 'flex flex-grow flex-col p-2',
'flex-grow',
'flex-col',
'p-2',
customization.trademark.enabled && 'lg:pb-20', customization.trademark.enabled && 'lg:pb-20',
'lg:gutter-stable overflow-y-auto',
'overflow-y-auto',
'lg:gutter-stable',
'[&::-webkit-scrollbar]:bg-transparent', '[&::-webkit-scrollbar]:bg-transparent',
'[&::-webkit-scrollbar-thumb]:bg-transparent', '[&::-webkit-scrollbar-thumb]:bg-transparent',
'group-hover:[&::-webkit-scrollbar]:bg-tint-subtle', 'group-hover:[&::-webkit-scrollbar]:bg-tint-subtle',
@@ -107,11 +97,7 @@ export function TableOfContents(props: {
rootPages={pages} rootPages={pages}
pages={pages} pages={pages}
context={context} context={context}
style={tcls( style="page-no-toc:hidden border-tint-subtle sidebar-list-line:border-l"
'page-no-toc:hidden',
'sidebar-list-line:border-l',
'border-tint-subtle'
)}
/> />
{customization.trademark.enabled ? ( {customization.trademark.enabled ? (
<Trademark <Trademark
@@ -2,12 +2,12 @@
import { Icon } from '@gitbook/icons'; import { Icon } from '@gitbook/icons';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React, { useRef } from 'react';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import { useCurrentPagePath, useToggleAnimation } from '../hooks'; import { useCurrentPagePath } from '../hooks';
import { Link, type LinkInsightsProps } from '../primitives'; import { Link, type LinkInsightsProps, type LinkProps } from '../primitives';
import { useScrollToActiveTOCItem } from './TOCScroller'; import { useScrollToActiveTOCItem } from './TOCScroller';
/** /**
@@ -24,35 +24,48 @@ export function ToggleableLinkItem(
const { href, children, descendants, pathname, insights } = props; const { href, children, descendants, pathname, insights } = props;
const currentPagePath = useCurrentPagePath(); const currentPagePath = useCurrentPagePath();
const isActive = currentPagePath === pathname; const isActive = currentPagePath === pathname;
const hasDescendants = !!descendants;
const hasActiveDescendant =
hasDescendants && (isActive || currentPagePath.startsWith(`${pathname}/`));
const [isVisible, setIsVisible] = React.useState(hasActiveDescendant); if (!descendants) {
return (
// Update the visibility of the children, if we are navigating to a descendant. <LinkItem href={href} insights={insights} isActive={isActive}>
React.useEffect(() => { {children}
if (!hasDescendants) { </LinkItem>
return; );
} }
setIsVisible((prev) => prev || hasActiveDescendant);
}, [hasActiveDescendant, hasDescendants]);
const { show, hide, scope } = useToggleAnimation({ hasDescendants, isVisible });
const linkRef = React.createRef<HTMLAnchorElement>();
useScrollToActiveTOCItem({ linkRef, isActive });
return ( 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 <Link
ref={linkRef} ref={anchorRef}
href={href} href={href}
insights={insights} insights={insights}
{...(isActive ? { 'aria-current': 'page' } : {})} aria-current={isActive ? 'page' : undefined}
className={tcls( className={tcls(
'group/toclink relative transition-colors', 'group/toclink relative transition-colors',
'flex flex-row justify-between', 'flex flex-row justify-between',
@@ -91,7 +104,49 @@ export function ToggleableLinkItem(
)} )}
> >
{children} {children}
{hasDescendants ? ( </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.
React.useEffect(() => {
if (defaultIsOpen) {
setIsOpen(defaultIsOpen);
}
}, [defaultIsOpen]);
return children({
toggler: (
<Toggler
isLinkActive={isOpen}
isOpen={isOpen}
onToggle={() => {
setIsOpen((prev) => !prev);
}}
/>
),
descendants: <Descendants isVisible={isOpen}>{descendants}</Descendants>,
});
}
function Toggler(props: {
isLinkActive: boolean;
isOpen: boolean;
onToggle: () => void;
}) {
const { isLinkActive, isOpen, onToggle } = props;
return (
<span <span
className={tcls( className={tcls(
'group', 'group',
@@ -109,43 +164,50 @@ export function ToggleableLinkItem(
'after:h-7', 'after:h-7',
'hover:bg-tint-active', 'hover:bg-tint-active',
'hover:text-current', 'hover:text-current',
isActive ? ['hover:bg-tint-hover'] : [] isLinkActive && 'hover:bg-tint-hover'
)} )}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setIsVisible((prev) => !prev); onToggle();
}} }}
> >
<Icon <Icon
icon="chevron-right" icon="chevron-right"
className={tcls( className={tcls(
'grid', 'm-1 grid size-3 flex-shrink-0 text-current opacity-6 transition',
'flex-shrink-0', 'group-hover:opacity-11 contrast-more:opacity-11',
'size-3', isOpen ? 'rotate-90' : 'rotate-0'
'm-1',
'transition-[opacity]',
'text-current',
'transition-transform',
'opacity-6',
'group-hover:opacity-11',
'contrast-more:opacity-11',
isVisible ? ['rotate-90'] : ['rotate-0']
)} )}
/> />
</span> </span>
) : null} );
</Link> }
{hasDescendants ? (
<motion.div const show = {
ref={scope} opacity: 1,
className={tcls(isVisible ? null : '[&_ul>li]:opacity-1')} height: 'auto',
initial={isVisible ? show : hide} };
> const hide = {
{descendants} opacity: 0,
</motion.div> height: 0,
) : null} 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>
); );
} }
@@ -3,6 +3,7 @@
import { useParams, useSelectedLayoutSegment } from 'next/navigation'; import { useParams, useSelectedLayoutSegment } from 'next/navigation';
import { removeLeadingSlash } from '@/lib/paths'; import { removeLeadingSlash } from '@/lib/paths';
import { useMemo } from 'react';
/** /**
* Return the page of the current page being rendered. * Return the page of the current page being rendered.
@@ -14,9 +15,11 @@ export function useCurrentPagePath() {
// For V1, we use the selected layout segment. // For V1, we use the selected layout segment.
const rawActiveSegment = useSelectedLayoutSegment() ?? ''; const rawActiveSegment = useSelectedLayoutSegment() ?? '';
return useMemo(() => {
if (typeof params.pagePath === 'string') { if (typeof params.pagePath === 'string') {
return removeLeadingSlash(decodeURIComponent(params.pagePath)); return removeLeadingSlash(decodeURIComponent(params.pagePath));
} }
return decodeURIComponent(rawActiveSegment); return decodeURIComponent(rawActiveSegment);
}, [params.pagePath, rawActiveSegment]);
} }
@@ -1,7 +1,7 @@
import { stagger, useAnimate } from 'framer-motion'; 'use client';
import React from 'react';
import { useIsMounted } from '.'; import { stagger, useAnimate } from 'framer-motion';
import React, { useLayoutEffect } from 'react';
const show = { const show = {
opacity: 1, opacity: 1,
@@ -19,44 +19,35 @@ const hide = {
const staggerMenuItems = stagger(0.02, { ease: (p) => p ** 2 }); const staggerMenuItems = stagger(0.02, { ease: (p) => p ** 2 });
export function useToggleAnimation({ export function useToggleAnimation(isVisible: boolean) {
hasDescendants,
isVisible,
}: {
hasDescendants: boolean;
isVisible: boolean;
}) {
const isMounted = useIsMounted();
const [scope, animate] = useAnimate(); 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 // Animate the visibility of the children
// only after the initial state. // only after the initial state.
React.useEffect(() => { React.useEffect(() => {
if (!isMounted || !hasDescendants) { if (previousIsVisible === undefined || previousIsVisible === isVisible) {
return; return;
} }
try { try {
animate(scope.current, isVisible ? show : hide, { animate(scope.current, isVisible ? show : hide, { duration: 0.1 });
duration: 0.1,
});
const selector = '& > ul > li'; const selector = '& > ul > li';
if (isVisible) if (isVisible) {
animate( animate(selector, { opacity: 1 }, { delay: staggerMenuItems });
selector, } else {
{ opacity: 1 },
{
delay: staggerMenuItems,
}
);
else {
animate(selector, { opacity: 0 }); animate(selector, { opacity: 0 });
} }
} catch (error) { } catch (error) {
// The selector can crash in some browsers, we ignore it as the animation is not critical. // The selector can crash in some browsers, we ignore it as the animation is not critical.
console.error(error); console.error(error);
} }
}, [isVisible, isMounted, hasDescendants, animate, scope]); }, [previousIsVisible, isVisible, animate, scope]);
return { show, hide, scope }; return { show, hide, scope };
} }
@@ -90,7 +90,7 @@ export function Button({
'contrast-more:hover:ring-2', 'contrast-more:hover:ring-2',
'contrast-more:hover:ring-tint-12', 'contrast-more:hover:ring-tint-12',
'hover:scale-105', 'hover:scale-104',
'active:scale-100', 'active:scale-100',
'transition-all', 'transition-all',
+24
View File
@@ -28,6 +28,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
multiWeight: { multiWeight: {
@@ -81,6 +84,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
multiSource: { multiSource: {
@@ -99,6 +105,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
missingFormat: { missingFormat: {
@@ -117,6 +126,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
empty: { empty: {
@@ -124,6 +136,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
custom: true, custom: true,
fontFamily: 'Empty Font', fontFamily: 'Empty Font',
fontFaces: [], fontFaces: [],
permissions: {
edit: false,
},
}, },
specialChars: { specialChars: {
@@ -136,6 +151,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }], sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }],
}, },
], ],
permissions: {
edit: false,
},
}, },
complex: { complex: {
@@ -158,6 +176,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
variousURLs: { variousURLs: {
@@ -174,6 +195,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
], ],
}, },
], ],
permissions: {
edit: false,
},
}, },
}; };
+3 -3
View File
@@ -1,9 +1,9 @@
import type { CustomizationFontDefinition } from '@gitbook/api'; import type { CustomizationFontDefinitionInput } from '@gitbook/api';
/** /**
* Define the custom font faces and set the --font-custom to the custom font name * Define the custom font faces and set the --font-custom to the custom font name
*/ */
export function generateFontFacesCSS(customFont: CustomizationFontDefinition): string { export function generateFontFacesCSS(customFont: CustomizationFontDefinitionInput): string {
const { fontFaces } = customFont; const { fontFaces } = customFont;
// Generate font face declarations for all weights // Generate font face declarations for all weights
@@ -45,7 +45,7 @@ export function generateFontFacesCSS(customFont: CustomizationFontDefinition): s
/** /**
* Get a list of font sources to preload (only 400 and 700 weights) * Get a list of font sources to preload (only 400 and 700 weights)
*/ */
export function getFontSourcesToPreload(customFont: CustomizationFontDefinition) { export function getFontSourcesToPreload(customFont: CustomizationFontDefinitionInput) {
return customFont.fontFaces.filter( return customFont.fontFaces.filter(
(face): face is typeof face & { weight: 400 | 700 } => (face): face is typeof face & { weight: 400 | 700 } =>
face.weight === 400 || face.weight === 700 face.weight === 400 || face.weight === 700
+7 -1
View File
@@ -267,6 +267,7 @@ export const getPublishedContentByUrl = cache({
const parsed = parseCacheResponse(response); const parsed = parseCacheResponse(response);
// biome-ignore lint/suspicious/noConsole: log the ttl of the token
console.log( console.log(
`Parsed ttl: ${parsed.ttl} at ${Date.now()}, for ${'apiToken' in response.data ? response.data.apiToken : '<no-token>'}` `Parsed ttl: ${parsed.ttl} at ${Date.now()}, for ${'apiToken' in response.data ? response.data.apiToken : '<no-token>'}`
); );
@@ -764,7 +765,12 @@ export const getComputedDocument = cache({
* Mimic the validation done on source server-side to reduce API usage. * Mimic the validation done on source server-side to reduce API usage.
*/ */
function validateSiteRedirectSource(source: string) { function validateSiteRedirectSource(source: string) {
return source.length <= 512 && /^\/[a-zA-Z0-9-_.\\/]+[a-zA-Z0-9-_.]$/.test(source); return (
source.length <= 512 &&
/^\/(?:[A-Za-z0-9\-._~]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~]|%[0-9A-Fa-f]{2})+)*$/.test(
source
)
);
} }
/** /**
+1 -13
View File
@@ -1,5 +1,4 @@
import { parseOpenAPI } from '@gitbook/openapi-parser'; import { parseOpenAPI } from '@gitbook/openapi-parser';
import { unstable_cache } from 'next/cache';
import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cache'; import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cache';
import type { import type {
@@ -10,7 +9,6 @@ import type {
} from '@/lib/openapi/types'; } from '@/lib/openapi/types';
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare'; import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize'; import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize';
import { GITBOOK_RUNTIME } from '@v2/lib/env';
import { assert } from 'ts-essentials'; import { assert } from 'ts-essentials';
import { resolveContentRef } from '../references'; import { resolveContentRef } from '../references';
import { isV2 } from '../v2'; import { isV2 } from '../v2';
@@ -71,18 +69,8 @@ const fetchFilesystemV1 = cache({
}); });
const fetchFilesystemV2 = withCacheKey( const fetchFilesystemV2 = withCacheKey(
withoutConcurrentExecution(getCloudflareRequestGlobal, async (cacheKey, url: string) => { withoutConcurrentExecution(getCloudflareRequestGlobal, async (_cacheKey, url: string) => {
if (GITBOOK_RUNTIME !== 'cloudflare') {
return fetchFilesystemUseCache(url); return fetchFilesystemUseCache(url);
}
// FIXME: OpenNext doesn't support 'use cache' yet
const uncached = unstable_cache(async () => fetchFilesystemUncached(url), [cacheKey], {
revalidate: 60 * 60 * 24,
});
const response = await uncached();
return response;
}) })
); );
+16
View File
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'bun:test';
import { getExtension } from './paths';
describe('getExtension', () => {
it('should return the extension of a path', () => {
expect(getExtension('test.txt')).toBe('.txt');
});
it('should return an empty string if there is no extension', () => {
expect(getExtension('test/path/to/file')).toBe('');
});
it('should return the extension of a path with multiple dots', () => {
expect(getExtension('test.with.multiple.dots.txt')).toBe('.txt');
});
});
+12
View File
@@ -57,3 +57,15 @@ export function withTrailingSlash(pathname: string): string {
return pathname; return pathname;
} }
/**
* Get the extension of a path.
*/
export function getExtension(path: string): string {
const re = /\.[0-9a-z]+$/i;
const match = path.match(re);
if (match) {
return match[0];
}
return '';
}
+41 -6
View File
@@ -41,8 +41,12 @@ export interface ResolvedContentRef {
file?: RevisionFile; file?: RevisionFile;
/** Page document resolved from the content ref */ /** Page document resolved from the content ref */
page?: RevisionPageDocument; page?: RevisionPageDocument;
/** Resolved reusable content, if the ref points to reusable content on a revision. */ /** Resolved reusable content, if the ref points to reusable content on a revision. Also contains the space and revision used for resolution. */
reusableContent?: RevisionReusableContent; reusableContent?: {
revisionReusableContent: RevisionReusableContent;
space: Space;
revision: string;
};
/** Resolve OpenAPI spec filesystem. */ /** Resolve OpenAPI spec filesystem. */
openAPIFilesystem?: Filesystem; openAPIFilesystem?: Filesystem;
} }
@@ -231,21 +235,52 @@ export async function resolveContentRef(
} }
case 'reusable-content': { case 'reusable-content': {
// Figure out which space and revision the reusable content is in.
const container: { space: Space; revision: string } | null = await (async () => {
// without a space on the content ref, or if the space is the same as the current one, we can use the current revision.
if (!contentRef.space || contentRef.space === context.space.id) {
return { space: context.space, revision: revisionId };
}
const space = await getDataOrNull(
dataFetcher.getSpace({
spaceId: contentRef.space,
shareKey: undefined,
})
);
if (!space) {
return null;
}
return { space, revision: space.revision };
})();
if (!container) {
return null;
}
const reusableContent = await getDataOrNull( const reusableContent = await getDataOrNull(
dataFetcher.getReusableContent({ dataFetcher.getReusableContent({
spaceId: space.id, spaceId: container.space.id,
revisionId, revisionId: container.revision,
reusableContentId: contentRef.reusableContent, reusableContentId: contentRef.reusableContent,
}) })
); );
if (!reusableContent) { if (!reusableContent) {
return null; return null;
} }
return { return {
href: getGitBookAppHref(`/s/${space.id}`), href: getGitBookAppHref(`/s/${container.space}/~/reusable/${reusableContent.id}`),
text: reusableContent.title, text: reusableContent.title,
active: false, active: false,
reusableContent, reusableContent: {
revisionReusableContent: reusableContent,
space: container.space,
revision: container.revision,
},
}; };
} }
+3 -3
View File
@@ -28,19 +28,19 @@ export async function trace<T>(
}; };
const start = now(); const start = now();
let failed = false; let traceError: null | Error = null;
try { try {
return await fn(span); return await fn(span);
} catch (error) { } catch (error) {
span.setAttribute('error', true); span.setAttribute('error', true);
failed = true; traceError = error as Error;
throw error; throw error;
} finally { } finally {
if (process.env.SILENT !== 'true' && process.env.NODE_ENV !== 'development') { if (process.env.SILENT !== 'true' && process.env.NODE_ENV !== 'development') {
const end = now(); const end = now();
// biome-ignore lint/suspicious/noConsole: we want to log performance data // biome-ignore lint/suspicious/noConsole: we want to log performance data
console.log( console.log(
`trace ${completeName} ${failed ? 'failed' : 'succeeded'} in ${end - start}ms`, `trace ${completeName} ${traceError ? `failed with ${traceError.message}` : 'succeeded'} in ${end - start}ms`,
attributes attributes
); );
} }
+108 -39
View File
@@ -8,6 +8,7 @@ import type { GitBookDataFetcher } from '@v2/lib/data/types';
import { createImageResizer } from '@v2/lib/images'; import { createImageResizer } from '@v2/lib/images';
import { createLinker } from '@v2/lib/links'; import { createLinker } from '@v2/lib/links';
import { GitBookAPI } from '@gitbook/api';
import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data'; import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { import {
@@ -30,6 +31,7 @@ import {
getUserById, getUserById,
renderIntegrationUi, renderIntegrationUi,
searchSiteContent, searchSiteContent,
withAPI as withAPIV1,
} from './api'; } from './api';
import { getDynamicCustomizationSettings } from './customization'; import { getDynamicCustomizationSettings } from './customization';
import { withLeadingSlash, withTrailingSlash } from './paths'; import { withLeadingSlash, withTrailingSlash } from './paths';
@@ -58,7 +60,7 @@ export async function getV1BaseContext(): Promise<GitBookBaseContext> {
return url; return url;
}; };
const dataFetcher = await getDataFetcherV1(); const dataFetcher = getDataFetcherV1();
const imageResizer = createImageResizer({ const imageResizer = createImageResizer({
imagesContextId: host, imagesContextId: host,
@@ -82,44 +84,83 @@ export async function getV1BaseContext(): Promise<GitBookBaseContext> {
* Try not to use this as much as possible, and instead take the data fetcher from the props. * Try not to use this as much as possible, and instead take the data fetcher from the props.
* This data fetcher should only be used at the top of the tree. * This data fetcher should only be used at the top of the tree.
*/ */
async function getDataFetcherV1(): Promise<GitBookDataFetcher> { function getDataFetcherV1(apiTokenOverride?: string): GitBookDataFetcher {
let apiClient: GitBookAPI | undefined;
/**
* Run a function with the correct API client. If an API token is provided, we
* create a new API client with the token. Otherwise, we use the default API client.
*/
async function withAPI<T>(fn: () => Promise<T>): Promise<T> {
// No token override - we can use the default API client.
if (!apiTokenOverride) {
return fn();
}
const client = await api();
if (!apiClient) {
// New client uses same endpoint and user agent as the default client.
apiClient = new GitBookAPI({
endpoint: client.client.endpoint,
authToken: apiTokenOverride,
userAgent: client.client.userAgent,
});
}
return withAPIV1(
{
client: apiClient,
contextId: client.contextId,
},
fn
);
}
const dataFetcher: GitBookDataFetcher = { const dataFetcher: GitBookDataFetcher = {
async api() { async api() {
return withAPI(async () => {
const result = await api(); const result = await api();
return result.client; return result.client;
});
}, },
withToken() { withToken({ apiToken }) {
// In v1, the token is global and controlled by the middleware. return getDataFetcherV1(apiToken);
// We don't need to do anything special here.
return dataFetcher;
}, },
getUserById(userId) { getUserById(userId) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const user = await getUserById(userId); const user = await getUserById(userId);
if (!user) { if (!user) {
throw new DataFetcherError('User not found', 404); throw new DataFetcherError('User not found', 404);
} }
return user; return user;
}); })
);
}, },
getPublishedContentSite(params) { getPublishedContentSite(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
return getPublishedContentSite(params); return getPublishedContentSite(params);
}); })
);
}, },
getSpace(params) { getSpace(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
return getSpace(params.spaceId, params.shareKey); return getSpace(params.spaceId, params.shareKey);
}); })
);
}, },
getChangeRequest(params) { getChangeRequest(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const changeRequest = await getChangeRequest( const changeRequest = await getChangeRequest(
params.spaceId, params.spaceId,
params.changeRequestId params.changeRequestId
@@ -129,19 +170,23 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
} }
return changeRequest; return changeRequest;
}); })
);
}, },
getRevision(params) { getRevision(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
return getRevision(params.spaceId, params.revisionId, { return getRevision(params.spaceId, params.revisionId, {
metadata: params.metadata, metadata: params.metadata,
}); });
}); })
);
}, },
getRevisionFile(params) { getRevisionFile(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const revisionFile = await getRevisionFile( const revisionFile = await getRevisionFile(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
@@ -152,7 +197,8 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
} }
return revisionFile; return revisionFile;
}); })
);
}, },
getRevisionPageMarkdown() { getRevisionPageMarkdown() {
@@ -160,37 +206,44 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
}, },
getDocument(params) { getDocument(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const document = await getDocument(params.spaceId, params.documentId); const document = await getDocument(params.spaceId, params.documentId);
if (!document) { if (!document) {
throw new DataFetcherError('Document not found', 404); throw new DataFetcherError('Document not found', 404);
} }
return document; return document;
}); })
);
}, },
getComputedDocument(params) { getComputedDocument(params) {
return wrapDataFetcherError(() => { return withAPI(() =>
wrapDataFetcherError(() => {
return getComputedDocument( return getComputedDocument(
params.organizationId, params.organizationId,
params.spaceId, params.spaceId,
params.source, params.source,
params.seed params.seed
); );
}); })
);
}, },
getRevisionPages(params) { getRevisionPages(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
return getRevisionPages(params.spaceId, params.revisionId, { return getRevisionPages(params.spaceId, params.revisionId, {
metadata: params.metadata, metadata: params.metadata,
}); });
}); })
);
}, },
getRevisionPageByPath(params) { getRevisionPageByPath(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const revisionPage = await getRevisionPageByPath( const revisionPage = await getRevisionPageByPath(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
@@ -202,11 +255,13 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
} }
return revisionPage; return revisionPage;
}); })
);
}, },
getReusableContent(params) { getReusableContent(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const reusableContent = await getReusableContent( const reusableContent = await getReusableContent(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
@@ -218,11 +273,13 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
} }
return reusableContent; return reusableContent;
}); })
);
}, },
getLatestOpenAPISpecVersionContent(params) { getLatestOpenAPISpecVersionContent(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent( const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent(
params.organizationId, params.organizationId,
params.slug params.slug
@@ -233,27 +290,33 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
} }
return openAPISpecVersionContent; return openAPISpecVersionContent;
}); })
);
}, },
getSiteRedirectBySource(params) { getSiteRedirectBySource(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const siteRedirect = await getSiteRedirectBySource(params); const siteRedirect = await getSiteRedirectBySource(params);
if (!siteRedirect) { if (!siteRedirect) {
throw new DataFetcherError('Site redirect not found', 404); throw new DataFetcherError('Site redirect not found', 404);
} }
return siteRedirect; return siteRedirect;
}); })
);
}, },
getEmbedByUrl(params) { getEmbedByUrl(params) {
return wrapDataFetcherError(() => { return withAPI(() =>
wrapDataFetcherError(() => {
return getEmbedByUrlInSpace(params.spaceId, params.url); return getEmbedByUrlInSpace(params.spaceId, params.url);
}); })
);
}, },
searchSiteContent(params) { searchSiteContent(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
wrapDataFetcherError(async () => {
const { organizationId, siteId, query, cacheBust, scope } = params; const { organizationId, siteId, query, cacheBust, scope } = params;
const result = await searchSiteContent( const result = await searchSiteContent(
organizationId, organizationId,
@@ -263,14 +326,20 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
cacheBust cacheBust
); );
return result.items; return result.items;
}); })
);
}, },
renderIntegrationUi(params) { renderIntegrationUi(params) {
return wrapDataFetcherError(async () => { return withAPI(() =>
const result = await renderIntegrationUi(params.integrationName, params.request); wrapDataFetcherError(async () => {
const result = await renderIntegrationUi(
params.integrationName,
params.request
);
return result; return result;
}); })
);
}, },
streamAIResponse() { streamAIResponse() {
@@ -6,7 +6,8 @@ import {
getVisitorAuthCookieName, getVisitorAuthCookieName,
getVisitorAuthCookieValue, getVisitorAuthCookieValue,
getVisitorToken, getVisitorToken,
} from './visitor-token'; getVisitorUnsignedClaims,
} from './visitors';
describe('getVisitorAuthToken', () => { describe('getVisitorAuthToken', () => {
it('should return the token from the query parameters', () => { it('should return the token from the query parameters', () => {
@@ -158,3 +159,139 @@ function assertVisitorAuthCookieValue(
throw new Error('Expected a VisitorAuthCookieValue'); throw new Error('Expected a VisitorAuthCookieValue');
} }
describe('getVisitorUnsignedClaims', () => {
it('should merge claims from multiple public cookies', () => {
const cookies = [
{
name: 'gitbook-visitor-public-bucket',
value: JSON.stringify({ bucket: { flags: { SITE_AI: true, SITE_PREVIEW: true } } }),
},
{
name: 'gitbook-visitor-public-launchdarkly',
value: JSON.stringify({
launchdarkly: { flags: { ALPHA: true, API: true } },
}),
},
];
const url = new URL('https://example.com/');
const claims = getVisitorUnsignedClaims({ cookies, url });
expect(claims.all).toStrictEqual({
bucket: { flags: { SITE_AI: true, SITE_PREVIEW: true } },
launchdarkly: { flags: { ALPHA: true, API: true } },
});
});
it('should parse visitor.* query params with simple types', () => {
const url = new URL(
'https://example.com/?visitor.isEnterprise=true&visitor.language=fr&visitor.country=fr'
);
const claims = getVisitorUnsignedClaims({ cookies: [], url });
expect(claims.all).toStrictEqual({
isEnterprise: true,
language: 'fr',
country: 'fr',
});
expect(claims.fromVisitorParams).toStrictEqual({
isEnterprise: true,
language: 'fr',
country: 'fr',
});
});
it('should ignore params that do not match visitor.* convention', () => {
const url = new URL('https://example.com/?visitor.isEnterprise=true&otherParam=true');
const claims = getVisitorUnsignedClaims({ cookies: [], url });
expect(claims.all).toStrictEqual({
isEnterprise: true,
// otherParam is not present
});
expect(claims.fromVisitorParams).toStrictEqual({
isEnterprise: true,
});
});
it('should support nested query param keys via dot notation', () => {
const url = new URL(
'https://example.com/?visitor.isEnterprise=true&visitor.flags.ALPHA=true&visitor.flags.API=false'
);
const claims = getVisitorUnsignedClaims({ cookies: [], url });
expect(claims.all).toStrictEqual({
isEnterprise: true,
flags: {
ALPHA: true,
API: false,
},
});
expect(claims.fromVisitorParams).toStrictEqual({
isEnterprise: true,
flags: {
ALPHA: true,
API: false,
},
});
});
it('should ignore invalid JSON in cookie values', () => {
const cookies = [
{
name: 'gitbook-visitor-public',
value: '{not: "json"}',
},
];
const url = new URL('https://example.com/');
const claims = getVisitorUnsignedClaims({ cookies, url });
expect(claims.all).toStrictEqual({});
});
it('should merge claims from cookies and visitor.* query params', () => {
const cookies = [
{
name: 'gitbook-visitor-public',
value: JSON.stringify({ role: 'admin', language: 'fr' }),
},
{
name: 'gitbook-visitor-public-bucket',
value: JSON.stringify({ bucket: { flags: { SITE_AI: true, SITE_PREVIEW: true } } }),
},
];
const url = new URL(
'https://example.com/?visitor.isEnterprise=true&visitor.flags.ALPHA=true&visitor.flags.API=false&visitor.bucket.flags.HELLO=false'
);
const claims = getVisitorUnsignedClaims({ cookies, url });
expect(claims.all).toStrictEqual({
role: 'admin',
language: 'fr',
bucket: {
flags: { HELLO: false, SITE_AI: true, SITE_PREVIEW: true },
},
isEnterprise: true,
flags: {
ALPHA: true,
API: false,
},
});
expect(claims.fromVisitorParams).toStrictEqual({
bucket: {
flags: { HELLO: false },
},
isEnterprise: true,
flags: {
ALPHA: true,
API: false,
},
});
});
});
@@ -4,6 +4,7 @@ import hash from 'object-hash';
const VISITOR_AUTH_PARAM = 'jwt_token'; const VISITOR_AUTH_PARAM = 'jwt_token';
export const VISITOR_TOKEN_COOKIE = 'gitbook-visitor-token'; export const VISITOR_TOKEN_COOKIE = 'gitbook-visitor-token';
const VISITOR_UNSIGNED_CLAIMS_PREFIX = 'gitbook-visitor-public';
/** /**
* Typing for a cookie, matching the internal type of Next.js. * Typing for a cookie, matching the internal type of Next.js.
@@ -30,6 +31,27 @@ type VisitorAuthCookieValue = {
token: string; token: string;
}; };
type ClaimPrimitive =
| string
| number
| boolean
| null
| undefined
| { [key: string]: ClaimPrimitive }
| ClaimPrimitive[];
/**
* The result of a visitor data lookup that can include:
* - a visitor token (JWT)
* - a record of visitor public/unsigned claims (JSON object)
* - a session cookie response to persist any visitor query params across navigations.
*/
export type VisitorDataLookup = {
visitorToken: VisitorTokenLookup;
unsignedClaims: Record<string, ClaimPrimitive>;
visitorParamsCookie: ResponseCookie | undefined;
};
/** /**
* The result of a visitor token lookup. * The result of a visitor token lookup.
*/ */
@@ -53,6 +75,30 @@ export type VisitorTokenLookup =
/** Not visitor token was found */ /** Not visitor token was found */
| undefined; | undefined;
/**
* Get the visitor data for the request potentially including:
* - a JWT token that may contain signed claims or can be used for VA authentication.
* - a record of the unsigned claims passed via a cookie or visitor.* params.
* - a session cookie response that is used to persist any visitor.* params that were passed via the site URL.
*/
export function getVisitorData({
cookies,
url,
}: {
cookies: RequestCookies;
url: URL | NextRequest['nextUrl'];
}): VisitorDataLookup {
const visitorToken = getVisitorToken({ cookies, url });
const unsignedClaims = getVisitorUnsignedClaims({ cookies, url });
const visitorParamsCookie = getResponseCookieForVisitorParams(unsignedClaims.fromVisitorParams);
return {
visitorToken,
unsignedClaims: unsignedClaims.all,
visitorParamsCookie,
};
}
/** /**
* Get the visitor token for the request. This token can either be in the * Get the visitor token for the request. This token can either be in the
* query parameters or stored as a cookie. * query parameters or stored as a cookie.
@@ -82,6 +128,138 @@ export function getVisitorToken({
} }
} }
/**
* Get the visitor unsigned/public claims for the request. They can either be in `visitor.` query
* parameters or stored in special `gitbook-visitor-public-*` cookies.
*/
export function getVisitorUnsignedClaims(args: {
cookies: RequestCookies;
url: URL | NextRequest['nextUrl'];
}): {
/**
* The unsigned claims coming from both `gitbook-visitor-public` cookies and `visitor.*` query params.
*/
all: Record<string, ClaimPrimitive>;
/**
* The unsigned claims from the `visitor.*` query params.
*/
fromVisitorParams: Record<string, ClaimPrimitive>;
} {
const { cookies, url } = args;
const claims: Record<string, ClaimPrimitive> = {};
const searchParamsClaims: Record<string, ClaimPrimitive> = {};
for (const cookie of cookies) {
if (cookie.name.startsWith(VISITOR_UNSIGNED_CLAIMS_PREFIX)) {
try {
const parsed = JSON.parse(cookie.value);
if (typeof parsed === 'object' && parsed !== null) {
Object.assign(claims, parsed);
}
} catch (_err) {
console.warn(`Invalid JSON in unsigned claim cookie "${cookie.name}"`);
}
}
}
for (const [key, value] of url.searchParams.entries()) {
if (key.startsWith('visitor.')) {
const claimPath = key.substring('visitor.'.length);
const claimValue = parseVisitorQueryParamValue(value);
setVisitorClaimByPath(claims, claimPath, claimValue);
setVisitorClaimByPath(searchParamsClaims, claimPath, claimValue);
}
}
return { all: claims, fromVisitorParams: searchParamsClaims };
}
/**
* Set the value of claims in a claims object at a specific path.
*/
function setVisitorClaimByPath(
claims: Record<string, ClaimPrimitive>,
keyPath: string,
value: ClaimPrimitive
): void {
const keys = keyPath.split('.');
let current = claims;
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (index === keys.length - 1) {
current[key] = value;
} else {
if (!(key in current) || !isClaimPrimitiveObject(current[key])) {
current[key] = {};
}
current = current[key];
}
}
}
function isClaimPrimitiveObject(value: unknown): value is Record<string, ClaimPrimitive> {
return typeof value === 'object' && value !== null;
}
/**
* Parse the value expected in a `visitor.` URL query parameter.
*/
function parseVisitorQueryParamValue(value: string): ClaimPrimitive {
if (value === 'true') {
return true;
}
if (value === 'false') {
return false;
}
if (value === 'null') {
return null;
}
if (value === 'undefined') {
return undefined;
}
const num = Number(value);
if (!Number.isNaN(num) && value.trim() !== '') {
return num;
}
try {
const parsed = JSON.parse(value);
if (typeof parsed === 'object' && parsed !== null) {
return parsed;
}
} catch {}
return value;
}
/**
* Returns to cookie response to use in order to persist visitor params that were passed to the URL.
*/
function getResponseCookieForVisitorParams(
visitorParamsClaims: Record<string, ClaimPrimitive>
): ResponseCookie | undefined {
if (Object.keys(visitorParamsClaims).length === 0) {
return undefined;
}
return {
name: VISITOR_UNSIGNED_CLAIMS_PREFIX,
value: JSON.stringify(visitorParamsClaims),
options: {
sameSite: process.env.NODE_ENV === 'production' ? 'none' : undefined,
secure: process.env.NODE_ENV === 'production',
},
};
}
/** /**
* Return the lookup result for content served with visitor auth. * Return the lookup result for content served with visitor auth.
*/ */
+12 -12
View File
@@ -24,9 +24,9 @@ import {
type ResponseCookies, type ResponseCookies,
type VisitorTokenLookup, type VisitorTokenLookup,
getResponseCookiesForVisitorAuth, getResponseCookiesForVisitorAuth,
getVisitorToken, getVisitorData,
normalizeVisitorAuthURL, normalizeVisitorAuthURL,
} from '@/lib/visitor-token'; } from '@/lib/visitors';
import { joinPath, withLeadingSlash } from '@/lib/paths'; import { joinPath, withLeadingSlash } from '@/lib/paths';
import { getProxyModeBasePath } from '@/lib/proxy'; import { getProxyModeBasePath } from '@/lib/proxy';
@@ -392,17 +392,17 @@ async function lookupSiteInProxy(request: NextRequest, url: URL): Promise<Lookup
* When serving multi spaces based on the current URL. * When serving multi spaces based on the current URL.
*/ */
async function lookupSiteInMultiMode(request: NextRequest, url: URL): Promise<LookupResult> { async function lookupSiteInMultiMode(request: NextRequest, url: URL): Promise<LookupResult> {
const visitorAuthToken = getVisitorToken({ const { visitorToken } = getVisitorData({
cookies: request.cookies.getAll(), cookies: request.cookies.getAll(),
url, url,
}); });
const lookup = await lookupSiteByAPI(url, visitorAuthToken); const lookup = await lookupSiteByAPI(url, visitorToken);
return { return {
...lookup, ...lookup,
...('basePath' in lookup && visitorAuthToken ...('basePath' in lookup && visitorToken
? getLookupResultForVisitorAuth(lookup.basePath, visitorAuthToken) ? getLookupResultForVisitorAuth(lookup.basePath, visitorToken)
: {}), : {}),
visitorToken: visitorAuthToken?.token, visitorToken: visitorToken?.token,
}; };
} }
@@ -609,12 +609,12 @@ async function lookupSiteInMultiPathMode(request: NextRequest, url: URL): Promis
const target = new URL(targetStr); const target = new URL(targetStr);
target.search = url.search; target.search = url.search;
const visitorAuthToken = getVisitorToken({ const { visitorToken } = getVisitorData({
cookies: request.cookies.getAll(), cookies: request.cookies.getAll(),
url: target, url: target,
}); });
const lookup = await lookupSiteByAPI(target, visitorAuthToken); const lookup = await lookupSiteByAPI(target, visitorToken);
if ('error' in lookup) { if ('error' in lookup) {
return lookup; return lookup;
} }
@@ -641,10 +641,10 @@ async function lookupSiteInMultiPathMode(request: NextRequest, url: URL): Promis
...lookup, ...lookup,
siteBasePath: joinPath(target.host, lookup.siteBasePath), siteBasePath: joinPath(target.host, lookup.siteBasePath),
basePath: joinPath(target.host, lookup.basePath), basePath: joinPath(target.host, lookup.basePath),
...('basePath' in lookup && visitorAuthToken ...('basePath' in lookup && visitorToken
? getLookupResultForVisitorAuth(lookup.basePath, visitorAuthToken) ? getLookupResultForVisitorAuth(lookup.basePath, visitorToken)
: {}), : {}),
visitorToken: visitorAuthToken?.token, visitorToken: visitorToken?.token,
}; };
} }
+2 -1
View File
@@ -2,6 +2,7 @@ import {
CURRENT_SIGNATURE_VERSION, CURRENT_SIGNATURE_VERSION,
type CloudflareImageOptions, type CloudflareImageOptions,
type SignatureVersion, type SignatureVersion,
SizableImageAction,
checkIsSizableImageURL, checkIsSizableImageURL,
isSignatureVersion, isSignatureVersion,
parseImageAPIURL, parseImageAPIURL,
@@ -40,7 +41,7 @@ export async function serveResizedImage(
// Check again if the image can be sized, even though we checked when rendering the Image component // Check again if the image can be sized, even though we checked when rendering the Image component
// Otherwise, it's possible to pass just any link to this endpoint and trigger HTML injection on the domain // Otherwise, it's possible to pass just any link to this endpoint and trigger HTML injection on the domain
// Also prevent infinite redirects. // Also prevent infinite redirects.
if (!checkIsSizableImageURL(url)) { if (checkIsSizableImageURL(url) === SizableImageAction.Skip) {
return new Response('Invalid url parameter', { status: 400 }); return new Response('Invalid url parameter', { status: 400 });
} }
+12 -9
View File
@@ -8,20 +8,23 @@ export async function serveRobotsTxt(context: GitBookSiteContext) {
const { linker } = context; const { linker } = context;
const isRoot = checkIsRootSiteContext(context); const isRoot = checkIsRootSiteContext(context);
const lines = [ const isIndexable = await isSiteIndexable(context);
'User-agent: *',
// Disallow dynamic routes / search queries const lines = isIndexable
'Disallow: /*?',
...((await isSiteIndexable(context))
? [ ? [
'User-agent: *',
// Allow image resizing and icon generation routes for favicons and search results
'Allow: /~gitbook/image?*',
'Allow: /~gitbook/icon?*',
// Disallow other dynamic routes / search queries
'Disallow: /*?',
'Allow: /', 'Allow: /',
`Sitemap: ${linker.toAbsoluteURL(linker.toPathInSpace(isRoot ? '/sitemap.xml' : '/sitemap-pages.xml'))}`, `Sitemap: ${linker.toAbsoluteURL(linker.toPathInSpace(isRoot ? '/sitemap.xml' : '/sitemap-pages.xml'))}`,
] ]
: ['Disallow: /']), : ['User-agent: *', 'Disallow: /'];
];
const content = lines.join('\n');
return new Response(content, { const robotsTxt = lines.join('\n');
return new Response(robotsTxt, {
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'text/plain',
}, },
+1 -1
View File
@@ -141,7 +141,7 @@ function getUrlsFromSiteSpaces(context: GitBookSiteContext, siteSpaces: SiteSpac
} }
const url = new URL(siteSpace.urls.published); const url = new URL(siteSpace.urls.published);
url.pathname = joinPath(url.pathname, 'sitemap-pages.xml'); url.pathname = joinPath(url.pathname, 'sitemap-pages.xml');
return context.linker.toLinkForContent(url.toString()); return context.linker.toAbsoluteURL(context.linker.toLinkForContent(url.toString()));
}, []); }, []);
return urls.filter(filterOutNullable); return urls.filter(filterOutNullable);
} }
+1
View File
@@ -443,6 +443,7 @@ const config: Config = {
scale: { scale: {
'98': '0.98', '98': '0.98',
'102': '1.02', '102': '1.02',
'104': '1.04',
}, },
}, },
opacity: opacity(), opacity: opacity(),
+2 -2
View File
@@ -12,13 +12,13 @@ interface Test {
// and to be able to see the results, and only catch major regressions. // and to be able to see the results, and only catch major regressions.
const tests: Array<Test> = [ const tests: Array<Test> = [
{ {
url: 'https://docs.gitbook.com', url: 'https://gitbook.com/docs',
strategy: 'desktop', strategy: 'desktop',
threshold: 60, threshold: 60,
}, },
{ {
url: 'https://docs.gitbook.com', url: 'https://gitbook.com/docs',
strategy: 'mobile', strategy: 'mobile',
threshold: 30, threshold: 30,
}, },
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/openapi-parser # @gitbook/openapi-parser
## 2.1.4
### Patch Changes
- d00dc8c: Pass scalar's errors through OpenAPIParseError
## 2.1.3 ## 2.1.3
### Patch Changes ### Patch Changes
+1 -1
View File
@@ -9,7 +9,7 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"version": "2.1.3", "version": "2.1.4",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@scalar/openapi-parser": "^0.10.10", "@scalar/openapi-parser": "^0.10.10",
+5 -1
View File
@@ -1,3 +1,5 @@
import type { ErrorObject } from '@scalar/openapi-parser';
type OpenAPIParseErrorCode = type OpenAPIParseErrorCode =
| 'invalid' | 'invalid'
| 'parse-v2-in-v3' | 'parse-v2-in-v3'
@@ -12,17 +14,19 @@ export class OpenAPIParseError extends Error {
public override name = 'OpenAPIParseError'; public override name = 'OpenAPIParseError';
public code: OpenAPIParseErrorCode; public code: OpenAPIParseErrorCode;
public rootURL: string | null; public rootURL: string | null;
public errors: ErrorObject[] | undefined;
constructor( constructor(
message: string, message: string,
options: { options: {
code: OpenAPIParseErrorCode; code: OpenAPIParseErrorCode;
rootURL?: string | null; rootURL?: string | null;
cause?: Error; cause?: Error;
errors?: ErrorObject[] | undefined;
} }
) { ) {
super(message, { cause: options.cause }); super(message, { cause: options.cause });
this.code = options.code; this.code = options.code;
this.rootURL = options.rootURL ?? null; this.rootURL = options.rootURL ?? null;
this.errors = options.errors;
} }
} }
+1
View File
@@ -40,6 +40,7 @@ async function untrustedValidate(input: ValidateOpenAPIV3Input) {
throw new OpenAPIParseError('Invalid OpenAPI document', { throw new OpenAPIParseError('Invalid OpenAPI document', {
code: 'invalid', code: 'invalid',
rootURL, rootURL,
errors: result.errors,
}); });
} }
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"classnames": "^2.5.1", "classnames": "^2.5.1",
"@gitbook/api": "*", "@gitbook/api": "^0.115.0",
"@gitbook/icons": "workspace:*" "@gitbook/icons": "workspace:*"
}, },
"peerDependencies": { "peerDependencies": {
+26
View File
@@ -1,5 +1,31 @@
# @gitbook/react-openapi # @gitbook/react-openapi
## 1.3.0
### Minor Changes
- 326e28e: Design tweaks to code blocks and OpenAPI pages
### Patch Changes
- 42ca7e1: Fix openapi CR preview
- 5e975ab: Fix code highlighting for HTTP
- 580101d: Fix schemas disclosure label causing client error
- 20ebecb: Missing top-level required OpenAPI alternatives
- 80cb52a: Handle OpenAPI alternatives from schema.items
- cb5598d: Handle invalid OpenAPI Responses
- c6637b0: Use default value if string number or boolean in generateSchemaExample
- a3ec264: Fix Python code sample "null vs None"
- Updated dependencies [d00dc8c]
- @gitbook/openapi-parser@2.1.4
## 1.2.1
### Patch Changes
- ebc39e9: Missing select icon
- b6b09d4: Fix OpenAPI responses select placement and icon
## 1.2.0 ## 1.2.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -8,7 +8,7 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"version": "1.2.0", "version": "1.3.0",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@gitbook/openapi-parser": "workspace:*", "@gitbook/openapi-parser": "workspace:*",
@@ -9,11 +9,12 @@ import { Button, Disclosure, DisclosurePanel } from 'react-aria-components';
*/ */
export function OpenAPIDisclosure(props: { export function OpenAPIDisclosure(props: {
icon: React.ReactNode; icon: React.ReactNode;
header: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
label: string | ((isExpanded: boolean) => string); label: string | ((isExpanded: boolean) => string);
className?: string; className?: string;
}): React.JSX.Element { }): React.JSX.Element {
const { icon, children, label, className } = props; const { icon, header, label, children, className } = props;
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
return ( return (
@@ -31,8 +32,11 @@ export function OpenAPIDisclosure(props: {
: 'none', : 'none',
})} })}
> >
{icon} {header}
<div className="openapi-disclosure-trigger-label">
<span>{typeof label === 'function' ? label(isExpanded) : label}</span> <span>{typeof label === 'function' ? label(isExpanded) : label}</span>
{icon}
</div>
</Button> </Button>
<DisclosurePanel className="openapi-disclosure-panel"> <DisclosurePanel className="openapi-disclosure-panel">
{isExpanded ? children : null} {isExpanded ? children : null}
@@ -76,7 +76,7 @@ function DisclosureItem(props: {
}); });
const panelRef = useRef<HTMLDivElement | null>(null); const panelRef = useRef<HTMLDivElement | null>(null);
const triggerRef = useRef<HTMLButtonElement | null>(null); const triggerRef = useRef<HTMLDivElement | null>(null);
const isDisabled = groupState?.isDisabled || !group.tabs?.length || false; const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
const { buttonProps: triggerProps, panelProps } = useDisclosure( const { buttonProps: triggerProps, panelProps } = useDisclosure(
{ {
@@ -96,12 +96,11 @@ function DisclosureItem(props: {
return ( return (
<div className="openapi-disclosure-group" aria-expanded={state.isExpanded}> <div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
<div className="openapi-disclosure-group-header"> <div
<button
slot="trigger" slot="trigger"
ref={triggerRef} ref={triggerRef}
{...mergeProps(buttonProps, focusProps)} {...mergeProps(buttonProps, focusProps)}
disabled={isDisabled} aria-disabled={isDisabled}
style={{ style={{
outline: isFocusVisible outline: isFocusVisible
? '2px solid rgb(var(--primary-color-500)/0.4)' ? '2px solid rgb(var(--primary-color-500)/0.4)'
@@ -117,8 +116,9 @@ function DisclosureItem(props: {
)} )}
</div> </div>
<div className="openapi-disclosure-group-label">
{group.label} {group.label}
</button>
{group.tabs ? ( {group.tabs ? (
<div <div
className="openapi-disclosure-group-mediatype" className="openapi-disclosure-group-mediatype"
@@ -132,6 +132,7 @@ function DisclosureItem(props: {
state.expand(); state.expand();
}} }}
items={group.tabs} items={group.tabs}
placement="bottom end"
> >
{group.tabs.map((tab) => ( {group.tabs.map((tab) => (
<OpenAPISelectItem key={tab.key} id={tab.key} value={tab}> <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
@@ -145,6 +146,7 @@ function DisclosureItem(props: {
</div> </div>
) : null} ) : null}
</div> </div>
</div>
{state.isExpanded && selectedTab && ( {state.isExpanded && selectedTab && (
<div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}> <div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
+34 -2
View File
@@ -1,7 +1,9 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { OpenAPIDisclosure } from './OpenAPIDisclosure'; import { OpenAPIDisclosure } from './OpenAPIDisclosure';
import { OpenAPISchemaPresentation } from './OpenAPISchema';
import { OpenAPISchemaProperties } from './OpenAPISchemaServer'; import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
import type { OpenAPIClientContext } from './context'; import type { OpenAPIClientContext } from './context';
import { tString } from './translate';
import { parameterToProperty, resolveDescription } from './utils'; import { parameterToProperty, resolveDescription } from './utils';
/** /**
@@ -27,7 +29,31 @@ export function OpenAPIResponse(props: {
return ( return (
<div className="openapi-response-body"> <div className="openapi-response-body">
{headers.length > 0 ? ( {headers.length > 0 ? (
<OpenAPIDisclosure icon={context.icons.plus} label="Headers"> <OpenAPIDisclosure
header={
<OpenAPISchemaPresentation
context={context}
property={{
propertyName: tString(context.translation, 'headers'),
schema: {
type: 'object',
},
required: null,
}}
/>
}
icon={context.icons.plus}
label={(isExpanded) =>
tString(
context.translation,
isExpanded ? 'hide' : 'show',
tString(
context.translation,
headers.length === 1 ? 'header' : 'headers'
)
)
}
>
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={headers.map(([name, header]) => properties={headers.map(([name, header]) =>
parameterToProperty({ name, ...header }) parameterToProperty({ name, ...header })
@@ -40,7 +66,13 @@ export function OpenAPIResponse(props: {
<div className="openapi-responsebody"> <div className="openapi-responsebody">
<OpenAPISchemaProperties <OpenAPISchemaProperties
id={`response-${context.blockKey}`} id={`response-${context.blockKey}`}
properties={[{ schema: mediaType.schema }]} properties={[
{
schema: mediaType.schema,
propertyName: tString(context.translation, 'response'),
required: null,
},
]}
context={context} context={context}
/> />
</div> </div>
@@ -6,8 +6,8 @@ import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
import { type OpenAPIContext, getOpenAPIClientContext } from './context'; import { type OpenAPIContext, getOpenAPIClientContext } from './context';
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types'; import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { getExampleFromReference, getExamples } from './util/example'; import { getExampleFromReference, getExamples } from './util/example';
import { createStateKey, getStatusCodeDefaultLabel } from './utils'; import { createStateKey, getStatusCodeDefaultLabel, resolveDescription } from './utils';
import { checkIsReference, resolveDescription } from './utils'; import { checkIsReference } from './utils';
/** /**
* Display an example of the response content. * Display an example of the response content.
@@ -41,7 +41,9 @@ export function OpenAPIResponseExample(props: {
return Number(a) - Number(b); return Number(a) - Number(b);
}); });
const tabs = responses.map(([key, responseObject]) => { const tabs = responses
.filter(([_, responseObject]) => responseObject && typeof responseObject === 'object')
.map(([key, responseObject]) => {
const description = resolveDescription(responseObject); const description = resolveDescription(responseObject);
const label = description ? ( const label = description ? (
<Markdown source={description} /> <Markdown source={description} />
@@ -20,8 +20,9 @@ export function OpenAPIResponses(props: {
}) { }) {
const { responses, context } = props; const { responses, context } = props;
const groups = Object.entries(responses).map( const groups = Object.entries(responses)
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => { .filter(([_, response]) => response && typeof response === 'object')
.map(([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
const tabs = (() => { const tabs = (() => {
// If there is no content, but there are headers, we need to show the headers // If there is no content, but there are headers, we need to show the headers
if ( if (
@@ -83,8 +84,7 @@ export function OpenAPIResponses(props: {
), ),
tabs, tabs,
}; };
} });
);
const state = useResponseExamplesState(context.blockKey, groups[0]?.key); const state = useResponseExamplesState(context.blockKey, groups[0]?.key);
@@ -98,6 +98,7 @@ export function OpenAPIResponses(props: {
state.setKey(key); state.setKey(key);
}} }}
groups={groups} groups={groups}
selectIcon={context.icons.chevronDown}
selectStateKey={createStateKey('response-media-types', context.blockKey)} selectStateKey={createStateKey('response-media-types', context.blockKey)}
/> />
</StaticSection> </StaticSection>
+134 -70
View File
@@ -4,6 +4,7 @@
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
import { useId } from 'react'; import { useId } from 'react';
import type { ComponentPropsWithoutRef } from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
@@ -12,6 +13,7 @@ import { OpenAPIDisclosure } from './OpenAPIDisclosure';
import { OpenAPISchemaName } from './OpenAPISchemaName'; import { OpenAPISchemaName } from './OpenAPISchemaName';
import type { OpenAPIClientContext } from './context'; import type { OpenAPIClientContext } from './context';
import { retrocycle } from './decycle'; import { retrocycle } from './decycle';
import { getDisclosureLabel } from './getDisclosureLabel';
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import { tString } from './translate'; import { tString } from './translate';
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils'; import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
@@ -19,30 +21,28 @@ import { checkIsReference, resolveDescription, resolveFirstExample } from './uti
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>; type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
export interface OpenAPISchemaPropertyEntry { export interface OpenAPISchemaPropertyEntry {
propertyName?: string | undefined; propertyName?: string;
required?: boolean | undefined; required?: boolean | null;
schema: OpenAPIV3.SchemaObject; schema: OpenAPIV3.SchemaObject;
} }
/** /**
* Render a property of an OpenAPI schema. * Render a property of an OpenAPI schema.
*/ */
function OpenAPISchemaProperty(props: { function OpenAPISchemaProperty(
props: {
property: OpenAPISchemaPropertyEntry; property: OpenAPISchemaPropertyEntry;
context: OpenAPIClientContext; context: OpenAPIClientContext;
circularRefs: CircularRefsIds; circularRefs: CircularRefsIds;
className?: string; className?: string;
}) { } & Omit<ComponentPropsWithoutRef<'div'>, 'property' | 'context' | 'circularRefs' | 'className'>
const { circularRefs: parentCircularRefs, context, className, property } = props; ) {
const { circularRefs: parentCircularRefs, context, className, property, ...rest } = props;
const { schema } = property; const { schema } = property;
const id = useId(); const id = useId();
return (
<div id={id} className={clsx('openapi-schema', className)}>
<OpenAPISchemaPresentation context={context} property={property} />
{(() => {
const circularRefId = parentCircularRefs.get(schema); const circularRefId = parentCircularRefs.get(schema);
// Avoid recursing infinitely, and instead render a link to the parent schema // Avoid recursing infinitely, and instead render a link to the parent schema
if (circularRefId) { if (circularRefId) {
@@ -53,39 +53,65 @@ function OpenAPISchemaProperty(props: {
circularRefs.set(schema, id); circularRefs.set(schema, id);
const properties = getSchemaProperties(schema); const properties = getSchemaProperties(schema);
const ancestors = new Set(circularRefs.keys());
const alternatives = getSchemaAlternatives(schema, ancestors);
const header = <OpenAPISchemaPresentation context={context} property={property} />;
const content = (() => {
if (properties?.length) { if (properties?.length) {
return ( return (
<OpenAPIDisclosure
icon={context.icons.plus}
label={(isExpanded) =>
getDisclosureLabel({ schema, isExpanded, context })
}
>
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
circularRefs={circularRefs} circularRefs={circularRefs}
context={context} context={context}
/> />
);
}
if (alternatives) {
return (
<div className="openapi-schema-alternatives">
{alternatives.map((alternativeSchema, index) => (
<div key={index} className="openapi-schema-alternative">
<OpenAPISchemaAlternative
schema={alternativeSchema}
circularRefs={circularRefs}
context={context}
/>
{index < alternatives.length - 1 ? (
<OpenAPISchemaAlternativeSeparator
schema={schema}
context={context}
/>
) : null}
</div>
))}
</div>
);
}
return null;
})();
if (properties?.length) {
return (
<OpenAPIDisclosure
icon={context.icons.plus}
className={clsx('openapi-schema', className)}
header={header}
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
{...rest}
>
{content}
</OpenAPIDisclosure> </OpenAPIDisclosure>
); );
} }
const ancestors = new Set(circularRefs.keys()); return (
const alternatives = getSchemaAlternatives(schema, ancestors); <div id={id} {...rest} className={clsx('openapi-schema', className)}>
{header}
if (alternatives) { {content}
return alternatives.map((schema, index) => (
<OpenAPISchemaAlternative
key={index}
schema={schema}
circularRefs={circularRefs}
context={context}
/>
));
}
return null;
})()}
</div> </div>
); );
} }
@@ -115,6 +141,7 @@ function OpenAPISchemaProperties(props: {
circularRefs={circularRefs} circularRefs={circularRefs}
property={property} property={property}
context={context} context={context}
style={{ animationDelay: `${index * 0.02}s` }}
/> />
); );
})} })}
@@ -205,34 +232,48 @@ function OpenAPISchemaAlternative(props: {
context: OpenAPIClientContext; context: OpenAPIClientContext;
}) { }) {
const { schema, circularRefs, context } = props; const { schema, circularRefs, context } = props;
const description = resolveDescription(schema);
const properties = getSchemaProperties(schema); const properties = getSchemaProperties(schema);
return ( return properties?.length ? (
<>
{description ? (
<Markdown source={description} className="openapi-schema-description" />
) : null}
<OpenAPIDisclosure <OpenAPIDisclosure
icon={context.icons.plus} icon={context.icons.plus}
header={<OpenAPISchemaPresentation property={{ schema }} context={context} />}
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })} label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
> >
{properties?.length ? (
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
circularRefs={circularRefs} circularRefs={circularRefs}
context={context} context={context}
/> />
</OpenAPIDisclosure>
) : ( ) : (
<OpenAPISchemaProperty <OpenAPISchemaProperty
property={{ schema }} property={{ schema }}
circularRefs={circularRefs} circularRefs={circularRefs}
context={context} context={context}
/> />
)} );
</OpenAPIDisclosure> }
</>
function OpenAPISchemaAlternativeSeparator(props: {
schema: OpenAPIV3.SchemaObject;
context: OpenAPIClientContext;
}) {
const { schema, context } = props;
const anyOf = schema.anyOf || schema.items?.anyOf;
const oneOf = schema.oneOf || schema.items?.oneOf;
const allOf = schema.allOf || schema.items?.allOf;
if (!anyOf && !oneOf && !allOf) {
return null;
}
return (
<span className="openapi-schema-alternative-separator">
{(anyOf || oneOf) && tString(context.translation, 'or')}
{allOf && tString(context.translation, 'and')}
</span>
); );
} }
@@ -293,7 +334,7 @@ function OpenAPISchemaEnum(props: {
return ( return (
<span className="openapi-schema-enum"> <span className="openapi-schema-enum">
Available options:{' '} {tString(context.translation, 'possible_values')}:{' '}
{enumValues.map((item, index) => ( {enumValues.map((item, index) => (
<span key={index} className="openapi-schema-enum-value"> <span key={index} className="openapi-schema-enum-value">
<OpenAPICopyButton <OpenAPICopyButton
@@ -313,7 +354,7 @@ function OpenAPISchemaEnum(props: {
/** /**
* Render the top row of a schema. e.g: name, type, and required status. * Render the top row of a schema. e.g: name, type, and required status.
*/ */
function OpenAPISchemaPresentation(props: { export function OpenAPISchemaPresentation(props: {
property: OpenAPISchemaPropertyEntry; property: OpenAPISchemaPropertyEntry;
context: OpenAPIClientContext; context: OpenAPIClientContext;
}) { }) {
@@ -437,6 +478,14 @@ export function getSchemaAlternatives(
schema: OpenAPIV3.SchemaObject, schema: OpenAPIV3.SchemaObject,
ancestors: Set<OpenAPIV3.SchemaObject> = new Set() ancestors: Set<OpenAPIV3.SchemaObject> = new Set()
): OpenAPIV3.SchemaObject[] | null { ): OpenAPIV3.SchemaObject[] | null {
// Search for alternatives in the items property if it exists
if (
schema.items &&
('oneOf' in schema.items || 'allOf' in schema.items || 'anyOf' in schema.items)
) {
return getSchemaAlternatives(schema.items, ancestors);
}
const alternatives: const alternatives:
| [AlternativeType, (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[]] | [AlternativeType, (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[]]
| null = (() => { | null = (() => {
@@ -552,6 +601,9 @@ function flattenAlternatives(
schemasOrRefs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[], schemasOrRefs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[],
ancestors: Set<OpenAPIV3.SchemaObject> ancestors: Set<OpenAPIV3.SchemaObject>
): OpenAPIV3.SchemaObject[] { ): OpenAPIV3.SchemaObject[] {
// Get the parent schema's required fields from the most recent ancestor
const latestAncestor = Array.from(ancestors).pop();
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => { return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
if (checkIsReference(schemaOrRef)) { if (checkIsReference(schemaOrRef)) {
return acc; return acc;
@@ -560,16 +612,47 @@ function flattenAlternatives(
if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) { if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) {
const schemas = getSchemaAlternatives(schemaOrRef, ancestors); const schemas = getSchemaAlternatives(schemaOrRef, ancestors);
if (schemas) { if (schemas) {
acc.push(...schemas); acc.push(
...schemas.map((schema) => ({
...schema,
required: mergeRequiredFields(schema, latestAncestor),
}))
);
} }
return acc; return acc;
} }
acc.push(schemaOrRef); // For direct schemas, handle required fields
const schema = {
...schemaOrRef,
required: mergeRequiredFields(schemaOrRef, latestAncestor),
};
acc.push(schema);
return acc; return acc;
}, []); }, []);
} }
/**
* Merge the required fields of a schema with the required fields of its latest ancestor.
*/
function mergeRequiredFields(
schemaOrRef: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
latestAncestor: OpenAPIV3.SchemaObject | undefined
) {
if (!schemaOrRef.required && !latestAncestor?.required) {
return undefined;
}
if (checkIsReference(schemaOrRef)) {
return latestAncestor?.required;
}
return Array.from(
new Set([...(latestAncestor?.required || []), ...(schemaOrRef.required || [])])
);
}
function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string { function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
// Otherwise try to infer a nice title // Otherwise try to infer a nice title
let type = 'any'; let type = 'any';
@@ -587,6 +670,11 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
if (schema.format) { if (schema.format) {
type += ` · ${schema.format}`; type += ` · ${schema.format}`;
} }
// Only add the title if it's an object (no need for the title of a string, number, etc.)
if (type === 'object' && schema.title) {
type += ` · ${schema.title.replaceAll(' ', '')}`;
}
} }
if ('anyOf' in schema) { if ('anyOf' in schema) {
@@ -601,27 +689,3 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
return type; return type;
} }
function getDisclosureLabel(props: {
schema: OpenAPIV3.SchemaObject;
isExpanded: boolean;
context: OpenAPIClientContext;
}) {
const { schema, isExpanded, context } = props;
let label: string;
if (schema.type === 'array' && !!schema.items) {
if (schema.items.oneOf) {
label = tString(context.translation, 'available_items').toLowerCase();
}
// Fallback to "child attributes" for enums and objects
else if (schema.items.enum || schema.items.type === 'object') {
label = tString(context.translation, 'child_attributes').toLowerCase();
} else {
label = schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
}
} else {
label = schema.title || tString(context.translation, 'child_attributes').toLowerCase();
}
return `${isExpanded ? tString(context.translation, 'hide') : tString(context.translation, 'show')} ${label}`;
}
@@ -6,7 +6,7 @@ import { t, tString } from './translate';
interface OpenAPISchemaNameProps { interface OpenAPISchemaNameProps {
schema?: OpenAPIV3.SchemaObject; schema?: OpenAPIV3.SchemaObject;
propertyName?: string | React.JSX.Element; propertyName?: string | React.JSX.Element;
required?: boolean; required?: boolean | null;
type?: string; type?: string;
context: OpenAPIClientContext; context: OpenAPIClientContext;
} }
@@ -43,7 +43,7 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
{t(context.translation, 'write_only')} {t(context.translation, 'write_only')}
</span> </span>
) : null} ) : null}
{required ? ( {required === null ? null : required ? (
<span className="openapi-schema-required"> <span className="openapi-schema-required">
{t(context.translation, 'required')} {t(context.translation, 'required')}
</span> </span>
+1 -1
View File
@@ -11,7 +11,7 @@ export function SectionHeader(props: ComponentPropsWithoutRef<'div'>) {
{...props} {...props}
className={clsx( className={clsx(
'openapi-section-header', 'openapi-section-header',
props.className && `${props.className}-header` props.className ? `${props.className}-header` : undefined
)} )}
/> />
); );

Some files were not shown because too many files have changed in this diff Show More