mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-15 23:25:16 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc49a3d899 | |||
| db880d647f | |||
| 6f888387b4 | |||
| 0dd9a65cae | |||
| f39ad015b4 | |||
| a1438cb678 | |||
| 97a5dd3138 | |||
| 6f2cab2f07 | |||
| be6329a64a | |||
| 967b65300d | |||
| 581ceb2b3f | |||
| 9ad5951afc | |||
| 58c4cb2f9e | |||
| ac3643001c | |||
| dd8c53da9a | |||
| e149e618fb | |||
| 39d2828af9 | |||
| 321e07d9fd | |||
| a36a939aef | |||
| c75151e2dc | |||
| aa3357a57d | |||
| de53946896 | |||
| d06bedbbe1 | |||
| d51204148e | |||
| 319761a39e | |||
| 7fbf01a348 | |||
| c22490bd80 | |||
| 860fd6d02e | |||
| c43c524804 | |||
| 66bb938d85 | |||
| 0e6d928496 | |||
| f07982d9f8 | |||
| b92ecfad58 | |||
| 416bde70bc | |||
| 580f7ade15 | |||
| cdffd7c47b | |||
| 5a959cf041 | |||
| 31466546a2 | |||
| 90ead98eaa | |||
| 65e9cb9d5c | |||
| e59076a074 | |||
| 653920dec9 | |||
| 23cedd2d8b | |||
| 70c4182fd8 | |||
| b62b101065 | |||
| b9c929beeb | |||
| 2b6c593289 | |||
| c0b339e406 | |||
| da485f5144 | |||
| 139a8050a5 | |||
| da7b369ffd |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix security issue with injection of "javacript:` url in the back button of PDFs
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Implement retry logic for the DO cache to prevent when revalidating content.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Fix OpenAPISchemas description padding
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/colors": patch
|
||||
---
|
||||
|
||||
Desaturate text colors by decreasing chroma for the last steps of the color scale
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve OpenAPI schema style
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@gitbook/openapi-parser': patch
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Remove stable from x-stability
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Improve the error message returned by the revalidate endpoint.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Do not set cookie to identify visitor for insights when disabled.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gitbook/react-openapi": patch
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Improve OpenAPI schemas block ungrouped style. Classnames have changed, please refer to this PR to update GBX.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/react-openapi": patch
|
||||
---
|
||||
|
||||
Hide deprecated properties in examples
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Better error handling in cache revalidation.
|
||||
@@ -19,6 +19,12 @@ inputs:
|
||||
deploy:
|
||||
description: 'Deploy as main version for all traffic instead of uploading versions'
|
||||
required: true
|
||||
commitTag:
|
||||
description: 'Commit branch to associate with the deployment'
|
||||
required: true
|
||||
commitMessage:
|
||||
description: 'Commit message to associate with the deployment'
|
||||
required: true
|
||||
outputs:
|
||||
deployment-url:
|
||||
description: "Deployment URL"
|
||||
@@ -53,6 +59,8 @@ runs:
|
||||
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
|
||||
- name: Build worker
|
||||
run: bun run turbo build:v2:cloudflare
|
||||
env:
|
||||
GITBOOK_RUNTIME: cloudflare
|
||||
shell: bash
|
||||
- id: deploy
|
||||
name: Deploy to Cloudflare
|
||||
@@ -63,7 +71,7 @@ runs:
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '3.112.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: ${{ fromJSON(inputs.deploy) == true && 'deploy' || 'versions upload' }} --config ./packages/gitbook-v2/wrangler.toml
|
||||
command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc
|
||||
- name: Outputs
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -62,6 +62,7 @@ runs:
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ inputs.vercelOrg }}
|
||||
VERCEL_PROJECT_ID: ${{ inputs.vercelProject }}
|
||||
GITBOOK_RUNTIME: vercel
|
||||
- name: Deploy Project Artifacts to Vercel
|
||||
id: deploy
|
||||
shell: bash
|
||||
|
||||
@@ -40,6 +40,7 @@ jobs:
|
||||
run: bun run turbo gitbook#build:cloudflare
|
||||
env:
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
||||
GITBOOK_RUNTIME: cloudflare
|
||||
- id: deploy
|
||||
name: Deploy to Cloudflare
|
||||
uses: cloudflare/wrangler-action@v3.14.0
|
||||
@@ -95,6 +96,8 @@ jobs:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
opItem: op://gitbook-open/2c-preview
|
||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
commitTag: ${{ github.ref == 'refs/heads/main' && 'main' || format('pr{0}', github.event.pull_request.number) }}
|
||||
commitMessage: ${{ github.sha }}
|
||||
- name: Outputs
|
||||
run: |
|
||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Deploy staging
|
||||
- name: Deploy
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-vercel
|
||||
with:
|
||||
@@ -48,6 +48,8 @@ jobs:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
opItem: op://gitbook-open/2c-production
|
||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
commitTag: main
|
||||
commitMessage: ${{ github.sha }}
|
||||
- name: Outputs
|
||||
run: |
|
||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Deploy staging
|
||||
- name: Deploy
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-vercel
|
||||
with:
|
||||
@@ -48,6 +48,8 @@ jobs:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
opItem: op://gitbook-open/2c-staging
|
||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
commitTag: main
|
||||
commitMessage: ${{ github.sha }}
|
||||
- name: Outputs
|
||||
run: |
|
||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"packages/colors": {
|
||||
"name": "@gitbook/colors",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
@@ -49,7 +49,7 @@
|
||||
},
|
||||
"packages/gitbook": {
|
||||
"name": "gitbook",
|
||||
"version": "0.8.2",
|
||||
"version": "0.9.2",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "*",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
@@ -64,15 +64,15 @@
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@upstash/redis": "^1.27.1",
|
||||
"ai": "^4.1.46",
|
||||
"ajv": "^8.12.0",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "^1.2.1",
|
||||
"bun-types": "^1.1.20",
|
||||
"classnames": "^2.5.1",
|
||||
"event-iterator": "^2.0.0",
|
||||
"framer-motion": "^10.16.14",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsontoxml": "^1.0.1",
|
||||
@@ -81,13 +81,14 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.17",
|
||||
"next": "14.2.25",
|
||||
"next": "14.2.26",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
"object-hash": "^3.0.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"p-map": "^7.0.0",
|
||||
"parse-cache-control": "^1.0.1",
|
||||
"partial-json": "^0.1.7",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
@@ -104,6 +105,9 @@
|
||||
"unified": "^11.0.5",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zod": "^3.24.2",
|
||||
"zod-to-json-schema": "^3.24.5",
|
||||
"zustand": "^5.0.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@argos-ci/playwright": "^4.3.0",
|
||||
@@ -136,14 +140,13 @@
|
||||
},
|
||||
"packages/gitbook-v2": {
|
||||
"name": "gitbook-v2",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "*",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"next": "^15.2.3",
|
||||
"p-memoize": "^7.1.1",
|
||||
"next": "canary",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"rison": "^0.1.1",
|
||||
@@ -151,7 +154,7 @@
|
||||
"warn-once": "^0.1.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opennextjs/cloudflare": "^0.5.10",
|
||||
"@opennextjs/cloudflare": "^1.0.0-beta.0",
|
||||
"@types/rison": "^0.0.9",
|
||||
"gitbook": "*",
|
||||
"postcss": "^8",
|
||||
@@ -180,7 +183,7 @@
|
||||
},
|
||||
"packages/openapi-parser": {
|
||||
"name": "@gitbook/openapi-parser",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.10",
|
||||
"@scalar/openapi-types": "^0.1.9",
|
||||
@@ -234,7 +237,7 @@
|
||||
},
|
||||
"packages/react-openapi": {
|
||||
"name": "@gitbook/react-openapi",
|
||||
"version": "1.1.6",
|
||||
"version": "1.1.9",
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "^1.2.5",
|
||||
@@ -262,18 +265,18 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@gitbook/api": "0.106.0",
|
||||
"@gitbook/api": "0.108.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.0.9", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA=="],
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.1.10", "", { "dependencies": { "@ai-sdk/provider": "1.0.9", "eventsource-parser": "^3.0.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q=="],
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.4", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA=="],
|
||||
|
||||
"@ai-sdk/react": ["@ai-sdk/react@1.1.19", "", { "dependencies": { "@ai-sdk/provider-utils": "2.1.10", "@ai-sdk/ui-utils": "1.1.16", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.0.0" }, "optionalPeers": ["react", "zod"] }, "sha512-zqSOWmJxpB45ZrwZ04+Q7Uo3xeGM0tva2eUYz2T4gv9Yk6MQAfOBA6+scsKg8CyIuUy4M4/C4pCY3eWQf7sfQg=="],
|
||||
"@ai-sdk/react": ["@ai-sdk/react@1.2.6", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.4", "@ai-sdk/ui-utils": "1.2.5", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-5BFChNbcYtcY9MBStcDev7WZRHf0NpTrk8yfSoedWctB3jfWkFd1HECBvdc8w3mUQshF2MumLHtAhRO7IFtGGQ=="],
|
||||
|
||||
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.1.16", "", { "dependencies": { "@ai-sdk/provider": "1.0.9", "@ai-sdk/provider-utils": "2.1.10", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-jfblR2yZVISmNK2zyNzJZFtkgX57WDAUQXcmn3XUBJyo8LFsADu+/vYMn5AOyBi9qJT0RBk11PEtIxIqvByw3Q=="],
|
||||
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.5", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "@ai-sdk/provider-utils": "2.2.4", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-XDgqnJcaCkDez7qolvk+PDbs/ceJvgkNkxkOlc9uDWqxfDJxtvCZ+14MP/1qr4IBwGIgKVHzMDYDXvqVhSWLzg=="],
|
||||
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
@@ -287,25 +290,25 @@
|
||||
|
||||
"@argos-ci/util": ["@argos-ci/util@2.3.0", "", {}, "sha512-tkxnCpaj7yN9nCFzo9MX0FJ5YjUepEOGYfdvF8COQqp+EdY1qubOPpc4Z0l1B60BlC8YtjQv/oRxHSh1XzxWFg=="],
|
||||
|
||||
"@ast-grep/napi": ["@ast-grep/napi@0.34.3", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.34.3", "@ast-grep/napi-darwin-x64": "0.34.3", "@ast-grep/napi-linux-arm64-gnu": "0.34.3", "@ast-grep/napi-linux-arm64-musl": "0.34.3", "@ast-grep/napi-linux-x64-gnu": "0.34.3", "@ast-grep/napi-linux-x64-musl": "0.34.3", "@ast-grep/napi-win32-arm64-msvc": "0.34.3", "@ast-grep/napi-win32-ia32-msvc": "0.34.3", "@ast-grep/napi-win32-x64-msvc": "0.34.3" } }, "sha512-2yrnMrUw3NVm9hf+YKO+BOY3Aci/qau2vDo0lGtA7qGMma18XPUIOTdzm601k5gPHo4MfxPPZLoe9QdTUviANg=="],
|
||||
"@ast-grep/napi": ["@ast-grep/napi@0.35.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.35.0", "@ast-grep/napi-darwin-x64": "0.35.0", "@ast-grep/napi-linux-arm64-gnu": "0.35.0", "@ast-grep/napi-linux-arm64-musl": "0.35.0", "@ast-grep/napi-linux-x64-gnu": "0.35.0", "@ast-grep/napi-linux-x64-musl": "0.35.0", "@ast-grep/napi-win32-arm64-msvc": "0.35.0", "@ast-grep/napi-win32-ia32-msvc": "0.35.0", "@ast-grep/napi-win32-x64-msvc": "0.35.0" } }, "sha512-3ucaaSxV6fxXoqHrE/rxAvP1THnDdY5jNzGlnvx+JvnY9C/dSRKc0jlRMRz59N3El572+/yNRUUpAV1T9aBJug=="],
|
||||
|
||||
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.34.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0a8dS+mOP5TYRX3YDiejL1WXWoWga3wpMYZGSs6Ni+SlH1WEO8zyUHe/1z6jNWH8VMHfH9FSCy6+YaPTpiurCA=="],
|
||||
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.35.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-T+MN4Oinc+sXjXCIHzfxDDWY7r2pKgPxM6zVeVlkMTrJV2mJtyKYBIS+CABhRM6kflps2T2I6l4DGaKV/8Ym9w=="],
|
||||
|
||||
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.34.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-n70ha95Fk187B8tgnvR+ZW429EAs/rXktD0839Mdm2+fWjD+JSdB3SADzOGo2cKhuLpLOKnsvfF/bmu+C/p0YQ=="],
|
||||
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.35.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pEYiN6JI1HY2uWhMYJ9+3yIMyVYKuYdFzeD+dL7odA3qzK0o9N9AM3/NOt4ynU2EhufaWCJr0P5NoQ636qN6MQ=="],
|
||||
|
||||
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.34.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-2LBUvMVkBcO/CJ4ItgZ1MOSqDq2fRmhiLwFxIYjhjG7Rz+4ZNdRY+d7Sl596g0BChB4ffNv+M5HS8uUBuUax1w=="],
|
||||
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NBuzQngABGKz7lhG08IQb+7nPqUx81Ol37xmS3ZhVSdSgM0mtp93rCbgFTkJcAFE8IMfCHQSg7G4g0Iotz4ABQ=="],
|
||||
|
||||
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.34.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-vlX4mOyVO1Oy2CdOIFi7HBPwMKzOyLdBpRCcu7pArBOQJkpJ2eS5GR5qSW15f7KPLTkUMpJq7juLz/rP6Rc79Q=="],
|
||||
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EcvHPwyWpCL/96LuItBYGfeI5FaMTRvL+dHbO/hL5q1npqbb5qn+ppJwtNOjTPz8tayvgggxVk9T4C2O7taYA=="],
|
||||
|
||||
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.34.3", "", { "os": "linux", "cpu": "x64" }, "sha512-F9TwAfZT/vjjxoPH9Fk8/PTNB95Hm2V/rtva+xMCxkTnhaSh0swM+ku3vavkZ4rwk+LfKPAY37pifEVWg4JPNQ=="],
|
||||
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FDzNdlqmQnsiWXhnLxusw5AOfEcEM+5xtmrnAf3SBRFr86JyWD9qsynnFYC2pnP9hlMfifNH2TTmMpyGJW49Xw=="],
|
||||
|
||||
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.34.3", "", { "os": "linux", "cpu": "x64" }, "sha512-2W0ZYsRxdVnwJ/BfnCOSKgfcZ2UFf5I+vF5aMmeAOplOg7vlFHb8XHSa4GqO0MoBfFTGKTH76bKwxLz8d38y1Q=="],
|
||||
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wlmndjfBafT8u5p4DBnoRQyoCSGNuVSz7rT3TqhvlHcPzUouRWMn95epU9B1LNLyjXvr9xHeRjSktyCN28w57Q=="],
|
||||
|
||||
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.34.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-XRWHlZnO77dEjC7IM5aQRTBC/hc/08Hdl18baJL7smG2dYGJKonUA7BQns6Vt2i63sOEghclkDw6Pq0PD60dbw=="],
|
||||
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.35.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-gkhJeYc4rrZLX2icLxalPikTLMR57DuIYLwLr9g+StHYXIsGHrbfrE6Nnbdd8Izfs34ArFCrcwdaMrGlvOPSeg=="],
|
||||
|
||||
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.34.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-0jR3QIkuasSrEvsaGtsgMEvgEY8FVe4pemuW77hUOH/mhO4vxFOWny7w4kUaBxQkzJ5z3lbXVoqO4Uv9rpJsRA=="],
|
||||
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.35.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OdUuRa3chHCZ65y+qALfkUjz0W0Eg21YZ9TyPquV5why07M6HAK38mmYGzLxFH6294SvRQhs+FA/rAfbKeH0jA=="],
|
||||
|
||||
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.34.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OY1Cswkz+0bRZgt9sLjzgyA+y0X164UFjwf5WTYpbCTPUlwdpJP6L7FqJNRMemzQp0qQwwRR7ejpBUF4o/V0Aw=="],
|
||||
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.35.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pcQRUHqbroTN1oQ56V982a7IZTUUySQYWa2KEyksiifHGuBuitlzcyzFGjT96ThcqD9XW0UVJMvpoF2Qjh006Q=="],
|
||||
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
@@ -623,7 +626,7 @@
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
|
||||
|
||||
"@gitbook/api": ["@gitbook/api@0.106.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-2qA/w18JwHe2fwR2A45q1pEdCZArxqUloegfNibu0xngDhea+iKTXSBrN94wD6Lwkh7cqBp9MvtxC+YMz3hx1g=="],
|
||||
"@gitbook/api": ["@gitbook/api@0.108.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-OytauuT3IMf0zl6rBvIacQ7YphzNhpuNAvEVQbysCGo3BhGYD1xv+vXVmtkAGUnFEAclw2fg2VjWPt3sOlQsAg=="],
|
||||
|
||||
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
|
||||
|
||||
@@ -751,25 +754,25 @@
|
||||
|
||||
"@msgpack/msgpack": ["@msgpack/msgpack@3.0.0-beta2", "", {}, "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw=="],
|
||||
|
||||
"@next/env": ["@next/env@14.2.25", "", {}, "sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w=="],
|
||||
"@next/env": ["@next/env@14.2.26", "", {}, "sha512-vO//GJ/YBco+H7xdQhzJxF7ub3SUwft76jwaeOyVVQFHCi5DCnkP16WHB+JBylo4vOKPoZBlR94Z8xBxNBdNJA=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.25", "", { "os": "darwin", "cpu": "arm64" }, "sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.26", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zDJY8gsKEseGAxG+C2hTMT0w9Nk9N1Sk1qV7vXYz9MEiyRoF5ogQX2+vplyUMIfygnjn9/A04I6yrUTRTuRiyQ=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.25", "", { "os": "darwin", "cpu": "x64" }, "sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.26", "", { "os": "darwin", "cpu": "x64" }, "sha512-U0adH5ryLfmTDkahLwG9sUQG2L0a9rYux8crQeC92rPhi3jGQEY47nByQHrVrt3prZigadwj/2HZ1LUUimuSbg=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.25", "", { "os": "linux", "cpu": "arm64" }, "sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.26", "", { "os": "linux", "cpu": "arm64" }, "sha512-SINMl1I7UhfHGM7SoRiw0AbwnLEMUnJ/3XXVmhyptzriHbWvPPbbm0OEVG24uUKhuS1t0nvN/DBvm5kz6ZIqpg=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.25", "", { "os": "linux", "cpu": "arm64" }, "sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.26", "", { "os": "linux", "cpu": "arm64" }, "sha512-s6JaezoyJK2DxrwHWxLWtJKlqKqTdi/zaYigDXUJ/gmx/72CrzdVZfMvUc6VqnZ7YEvRijvYo+0o4Z9DencduA=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.25", "", { "os": "linux", "cpu": "x64" }, "sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.26", "", { "os": "linux", "cpu": "x64" }, "sha512-FEXeUQi8/pLr/XI0hKbe0tgbLmHFRhgXOUiPScz2hk0hSmbGiU8aUqVslj/6C6KA38RzXnWoJXo4FMo6aBxjzg=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.25", "", { "os": "linux", "cpu": "x64" }, "sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.26", "", { "os": "linux", "cpu": "x64" }, "sha512-BUsomaO4d2DuXhXhgQCVt2jjX4B4/Thts8nDoIruEJkhE5ifeQFtvW5c9JkdOtYvE5p2G0hcwQ0UbRaQmQwaVg=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.25", "", { "os": "win32", "cpu": "arm64" }, "sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.26", "", { "os": "win32", "cpu": "arm64" }, "sha512-5auwsMVzT7wbB2CZXQxDctpWbdEnEW/e66DyXO1DcgHxIyhP06awu+rHKshZE+lPLIGiwtjo7bsyeuubewwxMw=="],
|
||||
|
||||
"@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.25", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw=="],
|
||||
"@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.26", "", { "os": "win32", "cpu": "ia32" }, "sha512-GQWg/Vbz9zUGi9X80lOeGsz1rMH/MtFO/XqigDznhhhTfDlDoynCM6982mPCbSlxJ/aveZcKtTlwfAjwhyxDpg=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.25", "", { "os": "win32", "cpu": "x64" }, "sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.26", "", { "os": "win32", "cpu": "x64" }, "sha512-2rdB3T1/Gp7bv1eQTTm9d1Y1sv9UuJ2LAwOE0Pe2prHKe32UNscj7YS13fRB37d0GAiGNR+Y7ZcW8YjDI8Ns0w=="],
|
||||
|
||||
"@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="],
|
||||
|
||||
@@ -789,9 +792,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=="],
|
||||
|
||||
"@opennextjs/aws": ["@opennextjs/aws@https://pkg.pr.new/@opennextjs/aws@756", { "dependencies": { "@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" }, "bin": { "open-next": "./dist/index.js" } }],
|
||||
"@opennextjs/aws": ["@opennextjs/aws@3.5.4", "", { "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-QPCTcJ95p0pHUVDjjiB9MGRbOKotaq1Dl9WrmS6/dJt6vb6zdavKFBYdT/WQsTn5wj2RqInim7t6Xc5x+wAO6Q=="],
|
||||
|
||||
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@0.5.10", "", { "dependencies": { "@ast-grep/napi": "^0.34.1", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@756", "enquirer": "^2.4.1", "glob": "^11.0.0", "yaml": "^2.7.0" }, "peerDependencies": { "wrangler": "^3.111.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-L/D472YT5OW1LwpFtD/aVXHJYcVPbFVX7XdphlUjCR4+2osSQIDnsuNgfDRydHMDJZMKxeZDc251ZBzUVKpCqw=="],
|
||||
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.0.0-beta.0", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.5.4", "enquirer": "^2.4.1", "glob": "^11.0.0" }, "peerDependencies": { "wrangler": "^3.114.3 || ^4.7.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-hBYUs/v5MHkXqZnKn9rrXnjlf/dgUOhMoaqPHcrc8eS24GC7AFuQvDH1a4v7y54xzF2/rOJVYaq6RcbDQAve+A=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
@@ -837,6 +840,8 @@
|
||||
|
||||
"@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-use-callback-ref": ["@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-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -1315,8 +1320,6 @@
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.14", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA=="],
|
||||
|
||||
"@upstash/redis": ["@upstash/redis@1.34.3", "", { "dependencies": { "crypto-js": "^4.2.0" } }, "sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ=="],
|
||||
|
||||
"@vercel/build-utils": ["@vercel/build-utils@9.0.1", "", {}, "sha512-pG/izEqA0AGyqQj6QBfGoTOKU9FPG18sYw9qpncEq00uA+J4Ly4e8ssNbENsXtnXqkMjeoS3c5ncR3jT0bOyiA=="],
|
||||
|
||||
"@vercel/error-utils": ["@vercel/error-utils@2.0.3", "", {}, "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ=="],
|
||||
@@ -1393,7 +1396,7 @@
|
||||
|
||||
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
|
||||
|
||||
"ai": ["ai@4.1.47", "", { "dependencies": { "@ai-sdk/provider": "1.0.9", "@ai-sdk/provider-utils": "2.1.10", "@ai-sdk/react": "1.1.19", "@ai-sdk/ui-utils": "1.1.16", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.0.0" }, "optionalPeers": ["react", "zod"] }, "sha512-9UZ8Mkv1HlprCJfQ0Kq+rgKbfkrkDtJjslr1WOHBRzvC70jDJJYp8r0Qq4vlF7zs9VBkGyy8Rhm5zQJJIUjvgw=="],
|
||||
"ai": ["ai@4.3.0", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "@ai-sdk/provider-utils": "2.2.4", "@ai-sdk/react": "1.2.6", "@ai-sdk/ui-utils": "1.2.5", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-PxyQYKhWaU3LiZEpeKRaekVonZIbWdKAwgnqm0CSAxy1MFufmYEC5SM5Mc9uiK2DoHcbAL3d1jyaQ2fSDAJL8w=="],
|
||||
|
||||
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||
|
||||
@@ -1585,8 +1588,6 @@
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
|
||||
|
||||
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
|
||||
|
||||
"crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="],
|
||||
|
||||
"css-functions-list": ["css-functions-list@3.2.3", "", {}, "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA=="],
|
||||
@@ -2281,7 +2282,7 @@
|
||||
|
||||
"mime-types": ["mime-types@3.0.0", "", { "dependencies": { "mime-db": "^1.53.0" } }, "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
|
||||
|
||||
@@ -2319,7 +2320,7 @@
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"next": ["next@14.2.25", "", { "dependencies": { "@next/env": "14.2.25", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.25", "@next/swc-darwin-x64": "14.2.25", "@next/swc-linux-arm64-gnu": "14.2.25", "@next/swc-linux-arm64-musl": "14.2.25", "@next/swc-linux-x64-gnu": "14.2.25", "@next/swc-linux-x64-musl": "14.2.25", "@next/swc-win32-arm64-msvc": "14.2.25", "@next/swc-win32-ia32-msvc": "14.2.25", "@next/swc-win32-x64-msvc": "14.2.25" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q=="],
|
||||
"next": ["next@14.2.26", "", { "dependencies": { "@next/env": "14.2.26", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.26", "@next/swc-darwin-x64": "14.2.26", "@next/swc-linux-arm64-gnu": "14.2.26", "@next/swc-linux-arm64-musl": "14.2.26", "@next/swc-linux-x64-gnu": "14.2.26", "@next/swc-linux-x64-musl": "14.2.26", "@next/swc-win32-arm64-msvc": "14.2.26", "@next/swc-win32-ia32-msvc": "14.2.26", "@next/swc-win32-x64-msvc": "14.2.26" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-b81XSLihMwCfwiUVRRja3LphLo4uBBMZEzBBWMaISbKTwOmq3wPknIETy/8000tr7Gq4WmbuFYPS7jOYIf+ZJw=="],
|
||||
|
||||
"next-themes": ["next-themes@0.2.1", "", { "peerDependencies": { "next": "*", "react": "*", "react-dom": "*" } }, "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A=="],
|
||||
|
||||
@@ -2393,8 +2394,6 @@
|
||||
|
||||
"p-map": ["p-map@7.0.2", "", {}, "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q=="],
|
||||
|
||||
"p-memoize": ["p-memoize@7.1.1", "", { "dependencies": { "mimic-fn": "^4.0.0", "type-fest": "^3.0.0" } }, "sha512-DZ/bONJILHkQ721hSr/E9wMz5Am/OTJ9P6LhLFo2Tu+jL8044tgc9LwHO8g4PiaYePnlVVRAJcKmgy8J9MVFrA=="],
|
||||
|
||||
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||
|
||||
"package-json": ["package-json@6.5.0", "", { "dependencies": { "got": "^9.6.0", "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" } }, "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ=="],
|
||||
@@ -2417,6 +2416,8 @@
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
@@ -2805,7 +2806,7 @@
|
||||
|
||||
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
||||
|
||||
"type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
|
||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"type-is": ["type-is@2.0.0", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw=="],
|
||||
|
||||
@@ -2949,9 +2950,9 @@
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
|
||||
"zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
|
||||
"zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="],
|
||||
|
||||
@@ -3679,6 +3680,24 @@
|
||||
|
||||
"@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-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-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-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-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-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "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-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
|
||||
|
||||
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "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-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
|
||||
|
||||
"@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-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=="],
|
||||
@@ -3689,12 +3708,20 @@
|
||||
|
||||
"@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="],
|
||||
|
||||
"@scalar/api-client/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/code-highlight/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="],
|
||||
|
||||
"@scalar/oas-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
|
||||
|
||||
"@scalar/oas-utils/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
|
||||
|
||||
"@scalar/types/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@scalar/use-hooks/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
"@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -3995,7 +4022,7 @@
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"gitbook-v2/next": ["next@15.2.3", "", { "dependencies": { "@next/env": "15.2.3", "@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.2.3", "@next/swc-darwin-x64": "15.2.3", "@next/swc-linux-arm64-gnu": "15.2.3", "@next/swc-linux-arm64-musl": "15.2.3", "@next/swc-linux-x64-gnu": "15.2.3", "@next/swc-linux-x64-musl": "15.2.3", "@next/swc-win32-arm64-msvc": "15.2.3", "@next/swc-win32-x64-msvc": "15.2.3", "sharp": "^0.33.5" }, "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-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w=="],
|
||||
"gitbook-v2/next": ["next@15.3.0-canary.37", "", { "dependencies": { "@next/env": "15.3.0-canary.37", "@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.0-canary.37", "@next/swc-darwin-x64": "15.3.0-canary.37", "@next/swc-linux-arm64-gnu": "15.3.0-canary.37", "@next/swc-linux-arm64-musl": "15.3.0-canary.37", "@next/swc-linux-x64-gnu": "15.3.0-canary.37", "@next/swc-linux-x64-musl": "15.3.0-canary.37", "@next/swc-win32-arm64-msvc": "15.3.0-canary.37", "@next/swc-win32-x64-msvc": "15.3.0-canary.37", "sharp": "^0.33.5" }, "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-K9BJZoFYnp+WqR7jYSCzrBJHOwTXJOED964z0En7qszrjF2HXfa24W2bFAMBwK56JD+h4lUyhpbgBoDiDN+vnA=="],
|
||||
|
||||
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
|
||||
|
||||
@@ -4045,8 +4072,6 @@
|
||||
|
||||
"mdast-util-gfm-task-list-item/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
|
||||
"meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
|
||||
|
||||
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
|
||||
@@ -4075,8 +4100,6 @@
|
||||
|
||||
"normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
|
||||
|
||||
"onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"p-filter/p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="],
|
||||
|
||||
"package-json/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
@@ -4623,6 +4646,8 @@
|
||||
|
||||
"@cloudflare/next-on-pages/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
|
||||
|
||||
"@cloudflare/next-on-pages/miniflare/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||
|
||||
"@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/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
|
||||
@@ -4701,6 +4726,8 @@
|
||||
|
||||
"@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-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=="],
|
||||
|
||||
"@scalar/api-client/pretty-ms/parse-ms": ["parse-ms@3.0.0", "", {}, "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw=="],
|
||||
@@ -4863,23 +4890,23 @@
|
||||
|
||||
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.2.3", "", {}, "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw=="],
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.3.0-canary.37", "", {}, "sha512-NoG8pEk34zpIphk7vSTZ6zs3rj33jtxWO5E2o+bCE8gjFQunChNnRWP1OPo2cATmnc64aJKcvdX5uOuEtAvoUw=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.0-canary.37", "", { "os": "darwin", "cpu": "arm64" }, "sha512-H+gorYP1jSJCmPDpTlQIyLS6GXkA6WZ74H7lxHeXWdAWyP7zv/MmxuhPmsD/RFzYEwZNDSk9jVFpYcOvfWK1Hw=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.0-canary.37", "", { "os": "darwin", "cpu": "x64" }, "sha512-zul0GsE7SGga8DdBWHRYzdQC5WST1RMmVJRxutvqCqcm7R/GosIzibrEFE5AkW7XMh6bZCO71r4zKPehTuWFcg=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.0-canary.37", "", { "os": "linux", "cpu": "arm64" }, "sha512-ucAOG0sMG2MkNbGTFAI0qncbkci0VE34xjwcpxUT7LUv/rWG83oVOcxcGY5xVbz9V6MpPCxr62FV3I5h6t+w0w=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.0-canary.37", "", { "os": "linux", "cpu": "arm64" }, "sha512-hVnST5YroTumu1y+UB4HXN2mbmTs5VWqsr8KmQonAc7+Uu9hvjSb79cpCD5B97h0TBb34Z2vr90hN9w0Q06PFA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.0-canary.37", "", { "os": "linux", "cpu": "x64" }, "sha512-p7ArUaiTRm5pSqHg60UtJbZUzsOU90idibVRPgnXd+0Kec7n6TClE3pWcIFow9by0bpSi6kmbNmkHf61i0Kerw=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.0-canary.37", "", { "os": "linux", "cpu": "x64" }, "sha512-Egi3swoN3pbseJ3oRGFYs94ICEjmzmY+exWfNlpycjBMWp1m/QzIcIxHTdz2/Lqe5SNGVMrJ8uEDNAD2knqgvw=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q=="],
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.0-canary.37", "", { "os": "win32", "cpu": "arm64" }, "sha512-gOE0lBeS/ztrSHnXimEp9D5OOTN86moSfMgC05ei/UJSFyT7YEZa4FRYRDzctNEilRJC2DBnxjEpeD+JhDuf6A=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w=="],
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.0-canary.37", "", { "os": "win32", "cpu": "x64" }, "sha512-J/SjJYN1Nj/olRC0ykfDNKKoxJss4a3Zf340UeyHbmbqeKHvGbT/H0QFMx+3DT+Sqar3PqbyZBLbxpE/QYct1w=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
|
||||
+2
-2
@@ -7,12 +7,12 @@
|
||||
"turbo": "^2.4.4",
|
||||
"vercel": "^39.3.0"
|
||||
},
|
||||
"packageManager": "bun@1.2.5",
|
||||
"packageManager": "bun@1.2.8",
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"@gitbook/api": "0.106.0"
|
||||
"@gitbook/api": "0.108.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -220,7 +220,7 @@ export function colorScale(
|
||||
continue;
|
||||
}
|
||||
|
||||
const chromaRatio = index < 8 ? index * 0.05 : 1;
|
||||
const chromaRatio = index === 8 || index === 9 ? 1 : index * 0.05;
|
||||
|
||||
const shade = {
|
||||
L: targetL, // Blend lightness
|
||||
|
||||
@@ -8,8 +8,9 @@ const nextConfig = {
|
||||
// This is needed to throw "forbidden" when the api token expired during revalidation
|
||||
authInterrupts: true,
|
||||
|
||||
// This is needed to use 'use cache'
|
||||
useCache: true,
|
||||
// We don't use 'use cache' as it's not supported in OpenNext yet
|
||||
// and instead we scope `unstable_cache` to the parameters of the function
|
||||
useCache: false,
|
||||
|
||||
// Content is fully static, we can cache it in the session memory cache for a long time
|
||||
staleTimes: {
|
||||
@@ -34,6 +35,7 @@ const nextConfig = {
|
||||
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
|
||||
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,
|
||||
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
|
||||
|
||||
// Next.js envs
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
|
||||
import d1TagCache from '@opennextjs/cloudflare/d1-tag-cache';
|
||||
import kvIncrementalCache from '@opennextjs/cloudflare/kv-cache';
|
||||
import memoryQueue from '@opennextjs/cloudflare/memory-queue';
|
||||
import kvIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache';
|
||||
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
|
||||
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
|
||||
|
||||
export default defineCloudflareConfig({
|
||||
incrementalCache: kvIncrementalCache,
|
||||
queue: memoryQueue,
|
||||
tagCache: d1TagCache,
|
||||
tagCache: doShardedTagCache({
|
||||
baseShardSize: 12,
|
||||
regionalCache: true,
|
||||
shardReplication: {
|
||||
numberOfSoftReplicas: 2,
|
||||
numberOfHardReplicas: 1,
|
||||
},
|
||||
}),
|
||||
queue: doQueue,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"next": "^15.2.3",
|
||||
"next": "canary",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"@gitbook/api": "*",
|
||||
@@ -12,12 +12,11 @@
|
||||
"server-only": "^0.0.1",
|
||||
"warn-once": "^0.1.1",
|
||||
"rison": "^0.1.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"p-memoize": "^7.1.1"
|
||||
"jwt-decode": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gitbook": "*",
|
||||
"@opennextjs/cloudflare": "^0.5.10",
|
||||
"@opennextjs/cloudflare": "^1.0.0-beta.0",
|
||||
"@types/rison": "^0.0.9",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"postcss": "^8"
|
||||
@@ -28,7 +27,7 @@
|
||||
"build": "next build",
|
||||
"build:v2": "next build",
|
||||
"start": "next start",
|
||||
"build:v2:cloudflare": "opennextjs-cloudflare",
|
||||
"build:v2:cloudflare": "opennextjs-cloudflare build",
|
||||
"dev:v2:cloudflare": "wrangler dev --port 8771",
|
||||
"unit": "bun test",
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
+1
-11
@@ -3,10 +3,9 @@ import {
|
||||
generateSitePageMetadata,
|
||||
generateSitePageViewport,
|
||||
} from '@/components/SitePage';
|
||||
import { getCacheTag } from '@gitbook/cache-tags';
|
||||
import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@v2/app/utils';
|
||||
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import { unstable_cacheTag as cacheTag } from 'next/cache';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
@@ -15,19 +14,10 @@ type PageProps = {
|
||||
};
|
||||
|
||||
export default async function Page(props: PageProps) {
|
||||
'use cache';
|
||||
|
||||
const params = await props.params;
|
||||
const { context } = await getStaticSiteContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
cacheTag(
|
||||
getCacheTag({
|
||||
tag: 'site',
|
||||
site: context.site.id,
|
||||
})
|
||||
);
|
||||
|
||||
return <SitePage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@ import {
|
||||
generateSiteLayoutMetadata,
|
||||
generateSiteLayoutViewport,
|
||||
} from '@/components/SiteLayout';
|
||||
import { getCacheTag } from '@gitbook/cache-tags';
|
||||
import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils';
|
||||
import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env';
|
||||
import { unstable_cacheTag as cacheTag } from 'next/cache';
|
||||
|
||||
interface SiteStaticLayoutProps {
|
||||
params: Promise<RouteLayoutParams>;
|
||||
@@ -17,17 +15,8 @@ export default async function SiteStaticLayout({
|
||||
params,
|
||||
children,
|
||||
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
|
||||
'use cache';
|
||||
|
||||
const { context, visitorAuthClaims } = await getStaticSiteContext(await params);
|
||||
|
||||
cacheTag(
|
||||
getCacheTag({
|
||||
tag: 'site',
|
||||
site: context.site.id,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomizationRootLayout customization={context.customization}>
|
||||
<SiteLayout
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import { getCloudflareContext as getCloudflareContextOpenNext } from '@opennextjs/cloudflare';
|
||||
import { GITBOOK_RUNTIME } from '../env';
|
||||
|
||||
/**
|
||||
* Return the Cloudflare context or null when not running in Cloudflare.
|
||||
*/
|
||||
export function getCloudflareContext() {
|
||||
if (GITBOOK_RUNTIME !== 'cloudflare') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getCloudflareContextOpenNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object representing the current request.
|
||||
*/
|
||||
export function getCloudflareRequestGlobal() {
|
||||
const context = getCloudflareContext();
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return context.cf;
|
||||
}
|
||||
@@ -1,39 +1,52 @@
|
||||
import { describe, expect, it, mock } from 'bun:test';
|
||||
import { memoize } from './memoize';
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { withCacheKey, withoutConcurrentExecution } from './memoize';
|
||||
|
||||
describe('memoize', () => {
|
||||
it('should memoize the function', async () => {
|
||||
const fn = mock(async () => Math.random());
|
||||
const memoized = memoize(fn);
|
||||
expect(await memoized()).toBe(await memoized());
|
||||
});
|
||||
|
||||
it('should memoize the function with different arguments', async () => {
|
||||
describe('withoutConcurrentExecution', () => {
|
||||
it('should memoize the function based on the cache key', async () => {
|
||||
const fn = mock(async (a: number, b: number) => a + b);
|
||||
const memoized = memoize(fn);
|
||||
expect(await memoized(1, 2)).toBe(await memoized(1, 2));
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
expect(await memoized(1, 2)).not.toBe(await memoized(2, 3));
|
||||
const memoized = withoutConcurrentExecution(() => null, fn);
|
||||
|
||||
const p1 = memoized('c1', 1, 2);
|
||||
const p2 = memoized('c1', 1, 2);
|
||||
const p3 = memoized('c3', 2, 3);
|
||||
|
||||
expect(await p1).toBe(await p2);
|
||||
expect(await p1).not.toBe(await p3);
|
||||
expect(fn.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should memoize a function complex object', async () => {
|
||||
const fn = mock(async (a: { foo: string; bar: number }) => a.foo + a.bar);
|
||||
const memoized = memoize(fn);
|
||||
expect(await memoized({ foo: 'foo', bar: 1 })).toBe(await memoized({ foo: 'foo', bar: 1 }));
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
expect(await memoized({ foo: 'foo', bar: 1 })).not.toBe(
|
||||
await memoized({ foo: 'foo', bar: 2 })
|
||||
);
|
||||
expect(fn.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should wrap concurrent async calls', async () => {
|
||||
it('should support caching per request', async () => {
|
||||
const fn = mock(async () => Math.random());
|
||||
const memoized = memoize(fn);
|
||||
const promise1 = memoized();
|
||||
const promise2 = memoized();
|
||||
|
||||
const request1 = { id: 'request1' };
|
||||
const request2 = { id: 'request2' };
|
||||
|
||||
const requestContext = new AsyncLocalStorage<{ id: string }>();
|
||||
|
||||
const memoized = withoutConcurrentExecution(() => requestContext.getStore(), fn);
|
||||
|
||||
// Both in the same request
|
||||
const promise1 = requestContext.run(request1, () => memoized('c1'));
|
||||
const promise2 = requestContext.run(request1, () => memoized('c1'));
|
||||
|
||||
// In a different request
|
||||
const promise3 = requestContext.run(request2, () => memoized('c1'));
|
||||
|
||||
expect(await promise1).toBe(await promise2);
|
||||
expect(await promise1).not.toBe(await promise3);
|
||||
expect(fn.mock.calls.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withCacheKey', () => {
|
||||
it('should wrap the function by passing the cache key', async () => {
|
||||
const fn = mock(
|
||||
async (cacheKey: string, arg: { a: number; b: number }, c: number) =>
|
||||
`${cacheKey}, result=${arg.a + arg.b + c}`
|
||||
);
|
||||
const memoized = withCacheKey(fn);
|
||||
expect(await memoized({ a: 1, b: 2 }, 4)).toBe('[[["a",1],["b",2]],4], result=7');
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,61 @@
|
||||
import pMemoize from 'p-memoize';
|
||||
/**
|
||||
* Wrap a function by preventing concurrent executions of the same function.
|
||||
* With a logic to work per-request in Cloudflare Workers.
|
||||
*/
|
||||
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
|
||||
getGlobalContext: () => object | null | undefined,
|
||||
wrapped: (...args: ArgsType) => Promise<ReturnType>
|
||||
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
|
||||
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
|
||||
|
||||
return (key: string, ...args: ArgsType) => {
|
||||
const globalContext = getGlobalContext() ?? globalThis;
|
||||
|
||||
/**
|
||||
* Cache storage that is scoped to the current request when executed in Cloudflare Workers,
|
||||
* to avoid "Cannot perform I/O on behalf of a different request" errors.
|
||||
*/
|
||||
const promiseCache =
|
||||
globalPromiseCache.get(globalContext) ?? new Map<string, Promise<ReturnType>>();
|
||||
globalPromiseCache.set(globalContext, promiseCache);
|
||||
|
||||
const concurrent = promiseCache.get(key);
|
||||
if (concurrent) {
|
||||
return concurrent;
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await wrapped(...args);
|
||||
return result;
|
||||
} finally {
|
||||
promiseCache.delete(key);
|
||||
}
|
||||
})();
|
||||
|
||||
promiseCache.set(key, promise);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We wrap 'use cache' calls in a p-memoize function to avoid
|
||||
* executing the function multiple times when doing concurrent calls.
|
||||
*
|
||||
* Hopefully one day this can be done directly by 'use cache'.
|
||||
* Wrap a function by passing it a cache key that is computed from the function arguments.
|
||||
*/
|
||||
export function memoize<F extends (...args: any[]) => any>(f: F): F {
|
||||
return pMemoize(f, {
|
||||
cacheKey: (args) => {
|
||||
return JSON.stringify(deepSortValue(args));
|
||||
},
|
||||
});
|
||||
export function withCacheKey<ArgsType extends any[], ReturnType>(
|
||||
wrapped: (cacheKey: string, ...args: ArgsType) => Promise<ReturnType>
|
||||
): (...args: ArgsType) => Promise<ReturnType> {
|
||||
return (...args: ArgsType) => {
|
||||
const cacheKey = getCacheKey(args);
|
||||
return wrapped(cacheKey, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a cache key from the function arguments.
|
||||
*/
|
||||
function getCacheKey(args: any[]) {
|
||||
return JSON.stringify(deepSortValue(args));
|
||||
}
|
||||
|
||||
function deepSortValue(value: unknown): unknown {
|
||||
|
||||
@@ -179,4 +179,15 @@ export interface GitBookDataFetcher {
|
||||
integrationName: string;
|
||||
request: api.RenderIntegrationUI;
|
||||
}): Promise<DataFetcherResponse<api.ContentKitRenderOutput>>;
|
||||
|
||||
/**
|
||||
* Stream an AI response.
|
||||
*/
|
||||
streamAIResponse(params: {
|
||||
organizationId: string;
|
||||
siteId: string;
|
||||
input: api.AIMessageInput[];
|
||||
output: api.AIOutputFormat;
|
||||
model: api.AIModel;
|
||||
}): AsyncGenerator<api.AIStreamResponse, void, unknown>;
|
||||
}
|
||||
|
||||
+8
@@ -6,6 +6,14 @@ import 'server-only';
|
||||
* and not from the `process.env` object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runtime environment.
|
||||
*/
|
||||
export const GITBOOK_RUNTIME = (process.env.GITBOOK_RUNTIME ?? 'unknown') as
|
||||
| 'vercel'
|
||||
| 'cloudflare'
|
||||
| 'unknown';
|
||||
|
||||
/**
|
||||
* Main host on which GitBook is running.
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,11 @@ export async function verifyImageSignature(
|
||||
): Promise<boolean> {
|
||||
const generator = IMAGE_SIGNATURE_FUNCTIONS[version];
|
||||
const generated = await generator(input);
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: we want to log the signature comparison
|
||||
console.log(
|
||||
`comparing image signature for "${input.url}" on identifier "${input.imagesContextId}": "${generated}" (expected) === "${signature}" (actual)`
|
||||
);
|
||||
return generated === signature;
|
||||
}
|
||||
|
||||
@@ -65,7 +70,9 @@ const generateSignatureV2: SignFn = async (input) => {
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(':');
|
||||
return fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
||||
|
||||
const signature = fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
||||
return signature;
|
||||
};
|
||||
|
||||
// Reused buffer for FNV-1a hashing in the v1 algorithm
|
||||
|
||||
@@ -167,6 +167,23 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
// (customization override, theme, etc)
|
||||
let routeType: 'dynamic' | 'static' = 'static';
|
||||
|
||||
// We pick only stable data from the siteURL data to prevent re-rendering of
|
||||
// the root layout when changing pages..
|
||||
const stableSiteURLData: SiteURLData = {
|
||||
site: siteURLData.site,
|
||||
siteSection: siteURLData.siteSection,
|
||||
siteSpace: siteURLData.siteSpace,
|
||||
siteBasePath: siteURLData.siteBasePath,
|
||||
basePath: siteURLData.basePath,
|
||||
space: siteURLData.space,
|
||||
organization: siteURLData.organization,
|
||||
changeRequest: siteURLData.changeRequest,
|
||||
revision: siteURLData.revision,
|
||||
shareKey: siteURLData.shareKey,
|
||||
apiToken: siteURLData.apiToken,
|
||||
imagesContextId: imagesContextId,
|
||||
};
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set(MiddlewareHeaders.RouteType, routeType);
|
||||
requestHeaders.set(MiddlewareHeaders.URLMode, mode);
|
||||
@@ -174,7 +191,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
MiddlewareHeaders.SiteURL,
|
||||
`${siteCanonicalURL.origin}${siteURLData.basePath}`
|
||||
);
|
||||
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(siteURLData));
|
||||
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(stableSiteURLData));
|
||||
|
||||
// Preview of customization/theme
|
||||
const customization = siteRequestURL.searchParams.get('customization');
|
||||
@@ -204,23 +221,6 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
);
|
||||
routeType = routeTypeFromPathname ?? routeType;
|
||||
|
||||
// We pick only stable data from the siteURL data to prevent re-rendering of
|
||||
// the root layout when changing pages..
|
||||
const stableSiteURLData: SiteURLData = {
|
||||
site: siteURLData.site,
|
||||
siteSection: siteURLData.siteSection,
|
||||
siteSpace: siteURLData.siteSpace,
|
||||
siteBasePath: siteURLData.siteBasePath,
|
||||
basePath: siteURLData.basePath,
|
||||
space: siteURLData.space,
|
||||
organization: siteURLData.organization,
|
||||
changeRequest: siteURLData.changeRequest,
|
||||
revision: siteURLData.revision,
|
||||
shareKey: siteURLData.shareKey,
|
||||
apiToken: siteURLData.apiToken,
|
||||
imagesContextId: imagesContextId,
|
||||
};
|
||||
|
||||
const route = [
|
||||
'sites',
|
||||
routeType,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"main": ".open-next/worker.js",
|
||||
"name": "gitbook-open-v2",
|
||||
"compatibility_date": "2025-03-11",
|
||||
"compatibility_flags": ["nodejs_compat", "allow_importable_env"],
|
||||
"assets": {
|
||||
"directory": ".open-next/assets",
|
||||
"binding": "ASSETS"
|
||||
},
|
||||
"observability": {
|
||||
"enabled": true
|
||||
},
|
||||
"durable_objects": {
|
||||
"bindings": [
|
||||
{
|
||||
"name": "NEXT_CACHE_DO_QUEUE",
|
||||
"class_name": "DOQueueHandler"
|
||||
},
|
||||
{
|
||||
"name": "NEXT_TAG_CACHE_DO_SHARDED",
|
||||
"class_name": "DOShardedTagCache"
|
||||
}
|
||||
]
|
||||
},
|
||||
"migrations": [
|
||||
{
|
||||
"tag": "v1",
|
||||
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"preview": {
|
||||
"kv_namespaces": [
|
||||
{
|
||||
// gitbook-open-v2-cache-preview
|
||||
"binding": "NEXT_INC_CACHE_KV",
|
||||
"id": "b7dd9cf58bf2458f84812a2d83b3760c"
|
||||
}
|
||||
],
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "NEXT_CACHE_D1",
|
||||
"database_id": "f59ddb40-ad72-4312-9395-0ac6a129af8e",
|
||||
"database_name": "gitbook-open-v2-tags-preview"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"binding": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-preview"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-prod-api-cache"
|
||||
}
|
||||
]
|
||||
},
|
||||
"staging": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "open-2c.gitbook-staging.com/*",
|
||||
"zone_name": "gitbook-staging.com"
|
||||
},
|
||||
{
|
||||
"pattern": "static-2c.gitbook-staging.com/*",
|
||||
"zone_name": "gitbook-staging.com"
|
||||
}
|
||||
],
|
||||
"kv_namespaces": [
|
||||
{
|
||||
// gitbook-open-v2-cache-staging
|
||||
"binding": "NEXT_INC_CACHE_KV",
|
||||
"id": "a446e25f12b741afb185f1e5b4474f0a"
|
||||
}
|
||||
],
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "NEXT_CACHE_D1",
|
||||
"database_id": "9df62e39-1f35-4066-83aa-e9b8ed3ac8d5",
|
||||
"database_name": "gitbook-open-v2-tags-staging"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"binding": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-staging"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-staging-api-cache"
|
||||
}
|
||||
],
|
||||
"tail_consumers": [
|
||||
{
|
||||
"service": "gitbook-x-staging-tail"
|
||||
}
|
||||
]
|
||||
},
|
||||
"production": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "open-2c.gitbook.com/*",
|
||||
"zone_name": "gitbook.com"
|
||||
},
|
||||
{
|
||||
"pattern": "static-2c.gitbook.com/*",
|
||||
"zone_name": "gitbook.com"
|
||||
}
|
||||
],
|
||||
"kv_namespaces": [
|
||||
{
|
||||
// gitbook-open-v2-cache-production
|
||||
"binding": "NEXT_INC_CACHE_KV",
|
||||
"id": "72379746280d4e79acf24440eea950dc"
|
||||
}
|
||||
],
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "NEXT_CACHE_D1",
|
||||
"database_id": "a6f16fce-5f45-43a9-89a4-7b83ddf25b77",
|
||||
"database_name": "gitbook-open-v2-tags-production"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"binding": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-production"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-prod-api-cache"
|
||||
}
|
||||
],
|
||||
"tail_consumers": [
|
||||
{
|
||||
"service": "gitbook-x-prod-tail"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
main = ".open-next/worker.js"
|
||||
name = "gitbook-open-v2"
|
||||
compatibility_date = "2025-03-11"
|
||||
compatibility_flags = ["nodejs_compat", "allow_importable_env"]
|
||||
assets = { directory = ".open-next/assets", binding = "ASSETS" }
|
||||
observability = { enabled = true }
|
||||
|
||||
[env.preview]
|
||||
kv_namespaces = [
|
||||
{ binding = "NEXT_CACHE_WORKERS_KV", id = "b7dd9cf58bf2458f84812a2d83b3760c" } # gitbook-open-v2-cache-preview
|
||||
]
|
||||
d1_databases = [
|
||||
{ binding = "NEXT_CACHE_D1", database_id = "f59ddb40-ad72-4312-9395-0ac6a129af8e", database_name = "gitbook-open-v2-tags-preview" }
|
||||
]
|
||||
services = [
|
||||
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-preview" },
|
||||
{ binding = "GITBOOK_API", service = "gitbook-x-prod-api-cache" }
|
||||
]
|
||||
|
||||
[env.staging]
|
||||
routes = [
|
||||
{ pattern = "open-2c.gitbook-staging.com/*", zone_name = "gitbook-staging.com" },
|
||||
{ pattern = "static-2c.gitbook-staging.com/*", zone_name = "gitbook-staging.com" }
|
||||
]
|
||||
kv_namespaces = [
|
||||
{ binding = "NEXT_CACHE_WORKERS_KV", id = "a446e25f12b741afb185f1e5b4474f0a" } # gitbook-open-v2-cache-staging
|
||||
]
|
||||
d1_databases = [
|
||||
{ binding = "NEXT_CACHE_D1", database_id = "9df62e39-1f35-4066-83aa-e9b8ed3ac8d5", database_name = "gitbook-open-v2-tags-staging" }
|
||||
]
|
||||
services = [
|
||||
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-staging" },
|
||||
{ binding = "GITBOOK_API", service = "gitbook-x-staging-api-cache" }
|
||||
]
|
||||
|
||||
[env.production]
|
||||
routes = [
|
||||
{ pattern = "open-2c.gitbook.com/*", zone_name = "gitbook.com" },
|
||||
{ pattern = "static-2c.gitbook.com/*", zone_name = "gitbook.com" }
|
||||
]
|
||||
kv_namespaces = [
|
||||
{ binding = "NEXT_CACHE_WORKERS_KV", id = "72379746280d4e79acf24440eea950dc" } # gitbook-open-v2-cache-production
|
||||
]
|
||||
d1_databases = [
|
||||
{ binding = "NEXT_CACHE_D1", database_id = "a6f16fce-5f45-43a9-89a4-7b83ddf25b77", database_name = "gitbook-open-v2-tags-production" }
|
||||
]
|
||||
services = [
|
||||
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-production" },
|
||||
{ binding = "GITBOOK_API", service = "gitbook-x-prod-api-cache" }
|
||||
]
|
||||
@@ -1,5 +1,16 @@
|
||||
# gitbook
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- da7b369: Fix missing headers in OpenAPIResponses
|
||||
- 139a805: Fix OpenAPI enum display
|
||||
- Updated dependencies [da7b369]
|
||||
- Updated dependencies [da485f5]
|
||||
- Updated dependencies [139a805]
|
||||
- @gitbook/react-openapi@1.1.9
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -92,11 +92,6 @@ const testCases: TestsCase[] = [
|
||||
contentBaseURL: 'https://book.character.ai',
|
||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
},
|
||||
{
|
||||
name: 'docs.tradeonnova.io',
|
||||
contentBaseURL: 'https://docs.tradeonnova.io',
|
||||
tests: [{ name: 'Home', url: '/' }],
|
||||
},
|
||||
{
|
||||
name: 'azcoiner.gitbook.io',
|
||||
contentBaseURL: 'https://azcoiner.gitbook.io',
|
||||
|
||||
@@ -276,6 +276,9 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
||||
internationalization: {
|
||||
locale: CustomizationLocale.En,
|
||||
},
|
||||
insights: {
|
||||
trackingCookie: true,
|
||||
},
|
||||
favicon: {},
|
||||
header: {
|
||||
preset: CustomizationHeaderPreset.Default,
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
|
||||
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
|
||||
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
|
||||
},
|
||||
|
||||
webpack(config) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent -f ../../.env.local next dev",
|
||||
@@ -29,12 +29,11 @@
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@upstash/redis": "^1.27.1",
|
||||
"ai": "^4.1.46",
|
||||
"ajv": "^8.12.0",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "^1.2.1",
|
||||
"bun-types": "^1.1.20",
|
||||
"classnames": "^2.5.1",
|
||||
@@ -46,7 +45,7 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.17",
|
||||
"next": "14.2.25",
|
||||
"next": "14.2.26",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -68,7 +67,12 @@
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.5",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zod": "^3.24.2",
|
||||
"zod-to-json-schema": "^3.24.5",
|
||||
"event-iterator": "^2.0.0",
|
||||
"partial-json": "^0.1.7",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@argos-ci/playwright": "^4.3.0",
|
||||
|
||||
@@ -14,7 +14,15 @@ interface JsonBody {
|
||||
* The body should be a JSON with { tags: string[] }
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const json = (await req.json()) as JsonBody;
|
||||
let json: JsonBody;
|
||||
|
||||
try {
|
||||
json = await req.json();
|
||||
} catch (err) {
|
||||
return NextResponse.json({
|
||||
error: `invalid json body: ${err}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!json.tags || !Array.isArray(json.tags)) {
|
||||
return NextResponse.json(
|
||||
@@ -25,10 +33,18 @@ export async function POST(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const result = await revalidateTags(json.tags);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
stats: result.stats,
|
||||
});
|
||||
try {
|
||||
const result = await revalidateTags(json.tags);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
stats: result.stats,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: `${err}`,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { useScrollPage } from '@/components/hooks';
|
||||
export function PageClientLayout(props: { withSections?: boolean }) {
|
||||
// We use this hook in the page layout to ensure the elements for the blocks
|
||||
// are rendered before we scroll to a hash or to the top of the page
|
||||
useScrollPage({ scrollMarginTop: props.withSections ? 50 : undefined });
|
||||
useScrollPage({ scrollMarginTop: props.withSections ? 48 : undefined });
|
||||
|
||||
useStripFallbackQueryParam();
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/primitives/Button';
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { t, tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export default function ErrorPage(props: {
|
||||
@@ -35,9 +35,8 @@ export default function ErrorPage(props: {
|
||||
}}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
>
|
||||
{t(language, 'unexpected_error_retry')}
|
||||
</Button>
|
||||
label={tString(language, 'unexpected_error_retry')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
'use client';
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t } from '@/intl/translate';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useVisitedPages } from '../Insights';
|
||||
import { usePageContext } from '../PageContext';
|
||||
import { Loading } from '../primitives';
|
||||
import { streamLinkPageSummary } from './server-actions/streamLinkPageSummary';
|
||||
|
||||
/**
|
||||
* Summarise a page's content for use in a link preview
|
||||
*/
|
||||
export function AIPageLinkSummary(props: {
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
linkPreview?: string;
|
||||
linkTitle?: string;
|
||||
showTrademark: boolean;
|
||||
}) {
|
||||
const { targetSpaceId, targetPageId, linkPreview, linkTitle, showTrademark = true } = props;
|
||||
|
||||
const currentPage = usePageContext();
|
||||
|
||||
const language = useLanguage();
|
||||
const visitedPages = useVisitedPages((state) => state.pages);
|
||||
const [highlight, setHighlight] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
setHighlight('');
|
||||
|
||||
(async () => {
|
||||
const stream = await streamLinkPageSummary({
|
||||
currentSpaceId: currentPage.spaceId,
|
||||
currentPageId: currentPage.pageId,
|
||||
currentPageTitle: currentPage.title,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
});
|
||||
|
||||
for await (const highlight of stream) {
|
||||
if (canceled) return;
|
||||
setHighlight(highlight ?? '');
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [
|
||||
currentPage.pageId,
|
||||
currentPage.spaceId,
|
||||
currentPage.title,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
]);
|
||||
|
||||
const shimmerBlocks = [
|
||||
'w-[20%] [animation-delay:-1s]',
|
||||
'w-[35%] [animation-delay:-0.8s]',
|
||||
'w-[25%] [animation-delay:-0.6s]',
|
||||
'w-[10%] [animation-delay:-0.4s]',
|
||||
'w-[40%] [animation-delay:-0.2s]',
|
||||
'w-[30%] [animation-delay:0s]',
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex w-screen items-center gap-1 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
{showTrademark ? (
|
||||
<Loading className="size-4" busy={!highlight || highlight.length === 0} />
|
||||
) : (
|
||||
<Icon icon="sparkle" className="size-3" />
|
||||
)}
|
||||
<h6 className="text-tint">{t(language, 'link_tooltip_ai_summary')}</h6>
|
||||
</div>
|
||||
{highlight.length > 0 ? (
|
||||
<p className="animate-fadeIn">{highlight}</p>
|
||||
) : (
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{shimmerBlocks.map((block, index) => (
|
||||
<div
|
||||
key={`${index}-${block}`}
|
||||
className={`${block} h-4 animate-pulse rounded straight-corners:rounded-none bg-tint-active`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{highlight.length > 0 ? (
|
||||
<div className="animate-fadeIn text-tint-subtle text-xs">
|
||||
{t(language, 'link_tooltip_ai_summary_description')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './AIPageLinkSummary';
|
||||
@@ -0,0 +1,124 @@
|
||||
'use server';
|
||||
import { type AIMessageInput, AIModel, type AIStreamResponse } from '@gitbook/api';
|
||||
import type { GitBookBaseContext } from '@v2/lib/context';
|
||||
import { EventIterator } from 'event-iterator';
|
||||
import type { MaybePromise } from 'p-map';
|
||||
import * as partialJson from 'partial-json';
|
||||
import type { DeepPartial } from 'ts-essentials';
|
||||
import type { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* Get the latest value from a stream and the response id.
|
||||
*/
|
||||
export async function generate<T>(
|
||||
promise: MaybePromise<{
|
||||
stream: EventIterator<T>;
|
||||
response: Promise<{ responseId: string }>;
|
||||
}>
|
||||
) {
|
||||
const input = await promise;
|
||||
let value: T | undefined;
|
||||
|
||||
for await (const event of input.stream) {
|
||||
value = event;
|
||||
}
|
||||
|
||||
const { responseId } = await input.response;
|
||||
return {
|
||||
responseId,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the generation of an object using the AI.
|
||||
*/
|
||||
export async function streamGenerateObject<T>(
|
||||
context: GitBookBaseContext,
|
||||
{
|
||||
organizationId,
|
||||
siteId,
|
||||
}: {
|
||||
organizationId: string;
|
||||
siteId: string;
|
||||
},
|
||||
{
|
||||
schema,
|
||||
messages,
|
||||
model = AIModel.Fast,
|
||||
}: {
|
||||
schema: z.ZodSchema<T>;
|
||||
messages: AIMessageInput[];
|
||||
model?: AIModel;
|
||||
previousResponseId?: string;
|
||||
}
|
||||
) {
|
||||
const rawStream = context.dataFetcher.streamAIResponse({
|
||||
organizationId,
|
||||
siteId,
|
||||
input: messages,
|
||||
output: {
|
||||
type: 'object',
|
||||
schema: zodToJsonSchema(schema),
|
||||
},
|
||||
model,
|
||||
});
|
||||
|
||||
let json = '';
|
||||
return parseResponse<DeepPartial<T>>(rawStream, (event) => {
|
||||
if (event.type === 'response_object') {
|
||||
json += event.jsonChunk;
|
||||
|
||||
const parsed = partialJson.parse(json, partialJson.ALL);
|
||||
return parsed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a stream from the API to extract the responseId.
|
||||
*/
|
||||
function parseResponse<T>(
|
||||
responseStream: EventIterator<AIStreamResponse>,
|
||||
parse: (response: AIStreamResponse) => T | undefined
|
||||
): {
|
||||
stream: EventIterator<T>;
|
||||
response: Promise<{ responseId: string }>;
|
||||
} {
|
||||
let resolveResponse: (value: { responseId: string }) => void;
|
||||
const response = new Promise<{ responseId: string }>((resolve) => {
|
||||
resolveResponse = resolve;
|
||||
});
|
||||
|
||||
const stream = new EventIterator<T>((queue) => {
|
||||
(async () => {
|
||||
let foundResponse = false;
|
||||
|
||||
for await (const event of responseStream) {
|
||||
if (event.type === 'response_finish') {
|
||||
foundResponse = true;
|
||||
resolveResponse({ responseId: event.responseId });
|
||||
} else {
|
||||
const parsed = parse(event);
|
||||
if (parsed !== undefined) {
|
||||
queue.push(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundResponse) {
|
||||
throw new Error('No response found');
|
||||
}
|
||||
})().then(
|
||||
() => {
|
||||
queue.stop();
|
||||
},
|
||||
(error) => {
|
||||
queue.fail(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return { stream, response };
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './streamLinkPageSummary';
|
||||
@@ -0,0 +1,166 @@
|
||||
'use server';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
import { getV1BaseContext } from '@/lib/v1';
|
||||
import { isV2 } from '@/lib/v2';
|
||||
import { AIMessageRole } from '@gitbook/api';
|
||||
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
|
||||
import { getServerActionBaseContext } from '@v2/lib/server-actions';
|
||||
import { z } from 'zod';
|
||||
import { streamGenerateObject } from './api';
|
||||
|
||||
/**
|
||||
* Get a summary of a page, in the context of another page
|
||||
*/
|
||||
export async function* streamLinkPageSummary({
|
||||
currentSpaceId,
|
||||
currentPageId,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
}: {
|
||||
currentSpaceId: string;
|
||||
currentPageId: string;
|
||||
currentPageTitle: string;
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
linkPreview?: string;
|
||||
linkTitle?: string;
|
||||
visitedPages?: Array<{ spaceId: string; pageId: string }>;
|
||||
}) {
|
||||
const baseContext = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
|
||||
const siteURLData = await getSiteURLDataFromMiddleware();
|
||||
|
||||
const { stream } = await streamGenerateObject(
|
||||
baseContext,
|
||||
{
|
||||
organizationId: siteURLData.organization,
|
||||
siteId: siteURLData.site,
|
||||
},
|
||||
{
|
||||
schema: z.object({
|
||||
highlight: z
|
||||
.string()
|
||||
.describe('The reason why the user should read the target page.'),
|
||||
// questions: z.array(z.string().describe('The questions to sea')).max(3),
|
||||
}),
|
||||
messages: [
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# 1. Role
|
||||
You are a contextual fact extractor. Your job is to find the exact fact from the linked page that directly answers the implied question in the current paragraph.
|
||||
|
||||
# 2. Task
|
||||
Extract a contextually-relevant fact that:
|
||||
- Directly answers the specific need or question implied by the link's placement
|
||||
- States a capability, limitation, or specification from the target page
|
||||
- Connects precisely to the user's current paragraph or sentence
|
||||
- Completes the user's understanding based on what they're currently reading
|
||||
|
||||
# 3. Instructions
|
||||
1. First, identify the exact need, question, or gap in the current paragraph where the link appears
|
||||
2. Find the specific fact in the target page that addresses this exact contextual need
|
||||
3. Ensure the fact relates directly to the context of the paragraph containing the link
|
||||
4. Avoid ALL instructional language including words like "use", "click", "select", "create"
|
||||
5. Keep it under 30 words, factual and declarative about what EXISTS or IS TRUE`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# 4. Current page
|
||||
The content of the current page is:`,
|
||||
attachments: [
|
||||
{
|
||||
type: 'page' as const,
|
||||
spaceId: currentSpaceId,
|
||||
pageId: currentPageId,
|
||||
},
|
||||
],
|
||||
},
|
||||
...(visitedPages
|
||||
? [
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: '# 5. Previous pages',
|
||||
},
|
||||
...visitedPages.map(({ spaceId, pageId }) => ({
|
||||
role: AIMessageRole.Developer,
|
||||
content: `## Page ${pageId}`,
|
||||
attachments: [
|
||||
{
|
||||
type: 'page' as const,
|
||||
spaceId,
|
||||
pageId,
|
||||
},
|
||||
],
|
||||
})),
|
||||
]
|
||||
: []),
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# 6. Target page
|
||||
The content of the target page is:`,
|
||||
attachments: [
|
||||
{
|
||||
type: 'page' as const,
|
||||
spaceId: targetSpaceId,
|
||||
pageId: targetPageId,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# 7. Link preview
|
||||
The content of the link preview is:
|
||||
> ${linkPreview}
|
||||
> Page ID: ${targetPageId}`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# 8. Guidelines & Examples
|
||||
ALWAYS:
|
||||
- ALWAYS choose facts that directly fulfill the contextual need where the link appears
|
||||
- ALWAYS connect target page information specifically to the current paragraph context
|
||||
- ALWAYS focus on the gap in knowledge that the link is meant to fill
|
||||
- ALWAYS consider user's navigation history to ensure contextual continuity
|
||||
- ALWAYS use action verbs like "click", "select", "use", "create", "enable"
|
||||
|
||||
NEVER:
|
||||
- NEVER include ANY unspecifc language like "learn", "how to", "discover", etc. State the fact directly.
|
||||
- NEVER select general facts unrelated to the specific link context
|
||||
- NEVER ignore the specific context where the link appears
|
||||
- NEVER repeat the same fact in different words
|
||||
|
||||
## Examples
|
||||
Current paragraph: "When organizing content, headings are limited to 3 levels. For more advanced editing, you can use (multiple select)[/multiple-select] to move multiple blocks at once."
|
||||
Preview: "Multiple Select: Select multiple content blocks at once."
|
||||
✓ "Shift selects content between two points, useful for reorganizing your current heading structure."
|
||||
✗ "Shift and Ctrl/Cmd keys are the modifiers for selecting multiple blocks."
|
||||
|
||||
Current paragraph: "Most changes can be published directly, but for major revisions, if you want others to review changes before publishing, create a (change request)[/change-requests]."
|
||||
Preview: "Change Requests: Collaborative content editing workflow."
|
||||
✓ "Each reviewer's approval is tracked separately, with specific change highlighting for your major revisions."
|
||||
✗ "Each reviewer receives an email notification and can approve or request changes."
|
||||
|
||||
Current paragraph: "Your team mentioned issues with conflicting edits. Need to collaborate in real-time? You can use (live edit mode)[/live-edit]."
|
||||
Preview: "Live Edit: Real-time collaborative editing."
|
||||
✓ "Teams with GitHub repositories (like yours) cannot use this feature due to sync limitations."
|
||||
✗ "Incompatible with GitHub/GitLab sync and requires specific visibility settings."`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.User,
|
||||
content: `I'm considering reading the link titled "${linkTitle}" pointing to page ${targetPageId}. Why should I read it? Relate it to the paragraph I'm currently reading.`,
|
||||
},
|
||||
].filter(filterOutNullable),
|
||||
}
|
||||
);
|
||||
|
||||
for await (const value of stream) {
|
||||
const highlight = value.highlight;
|
||||
if (!highlight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield highlight;
|
||||
}
|
||||
}
|
||||
@@ -97,9 +97,8 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
onClick={() => {
|
||||
onUpdateState(true);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_accept')}
|
||||
</Button>
|
||||
label={tString(language, 'cookies_accept')}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
@@ -107,9 +106,8 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
onClick={() => {
|
||||
onUpdateState(false);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_reject')}
|
||||
</Button>
|
||||
label={tString(language, 'cookies_reject')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,15 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
||||
return (
|
||||
<Tag
|
||||
id={id}
|
||||
className={tcls(textStyle.textSize, 'heading', 'group', 'relative', 'grid', style)}
|
||||
className={tcls(
|
||||
textStyle.textSize,
|
||||
'heading',
|
||||
'group',
|
||||
'relative',
|
||||
'grid',
|
||||
'scroll-m-12',
|
||||
style
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
|
||||
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { StyledLink } from '../primitives';
|
||||
import type { InlineProps } from './Inline';
|
||||
import { InlineLinkTooltip } from './InlineLinkTooltip';
|
||||
import { Inlines } from './Inlines';
|
||||
|
||||
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
const { inline, document, context, ancestorInlines } = props;
|
||||
|
||||
const resolved = context.contentContext
|
||||
? await resolveContentRef(inline.data.ref, context.contentContext)
|
||||
? await resolveContentRef(inline.data.ref, context.contentContext, {
|
||||
resolveAnchorText: true,
|
||||
})
|
||||
: null;
|
||||
|
||||
if (!resolved) {
|
||||
if (!context.contentContext || !resolved) {
|
||||
return (
|
||||
<span title="Broken link" className="underline">
|
||||
<Inlines
|
||||
@@ -25,24 +28,33 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const isExternal = inline.data.ref.kind === 'url';
|
||||
|
||||
return (
|
||||
<StyledLink
|
||||
href={resolved.href}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
link: {
|
||||
target: inline.data.ref,
|
||||
position: SiteInsightsLinkPosition.Content,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Inlines
|
||||
context={context}
|
||||
document={document}
|
||||
nodes={inline.nodes}
|
||||
ancestorInlines={[...ancestorInlines, inline]}
|
||||
/>
|
||||
</StyledLink>
|
||||
<InlineLinkTooltip inline={inline} context={context.contentContext} resolved={resolved}>
|
||||
<StyledLink
|
||||
href={resolved.href}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
link: {
|
||||
target: inline.data.ref,
|
||||
position: SiteInsightsLinkPosition.Content,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Inlines
|
||||
context={context}
|
||||
document={document}
|
||||
nodes={inline.nodes}
|
||||
ancestorInlines={[...ancestorInlines, inline]}
|
||||
/>
|
||||
{isExternal ? (
|
||||
<Icon
|
||||
icon="arrow-up-right"
|
||||
className="ml-0.5 inline size-3 links-accent:text-tint-subtle"
|
||||
/>
|
||||
) : null}
|
||||
</StyledLink>
|
||||
</InlineLinkTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import type { DocumentInlineLink } from '@gitbook/api';
|
||||
|
||||
import type { ResolvedContentRef } from '@/lib/references';
|
||||
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { tString } from '@/intl/translate';
|
||||
import { languages } from '@/intl/translations';
|
||||
import { getNodeText } from '@/lib/document';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import type { GitBookAnyContext } from '@v2/lib/context';
|
||||
import { Fragment } from 'react';
|
||||
import { AIPageLinkSummary } from '../Adaptive/AIPageLinkSummary';
|
||||
import { Button, StyledLink } from '../primitives';
|
||||
|
||||
export async function InlineLinkTooltip(props: {
|
||||
inline: DocumentInlineLink;
|
||||
context: GitBookAnyContext;
|
||||
children: React.ReactNode;
|
||||
resolved: ResolvedContentRef;
|
||||
}) {
|
||||
const { inline, context, resolved, children } = props;
|
||||
|
||||
let breadcrumbs = resolved.ancestors;
|
||||
const language =
|
||||
'customization' in context ? getSpaceLanguage(context.customization) : languages.en;
|
||||
const isExternal = inline.data.ref.kind === 'url';
|
||||
const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined;
|
||||
if (isExternal) {
|
||||
breadcrumbs = [
|
||||
{
|
||||
label: tString(language, 'link_tooltip_external_link'),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (isSamePage) {
|
||||
breadcrumbs = [
|
||||
{
|
||||
label: tString(language, 'link_tooltip_page_anchor'),
|
||||
icon: <Icon icon="arrow-down-short-wide" className="size-3" />,
|
||||
},
|
||||
];
|
||||
resolved.subText = undefined;
|
||||
}
|
||||
|
||||
const hasAISummary =
|
||||
!isExternal &&
|
||||
!isSamePage &&
|
||||
'customization' in context &&
|
||||
context.customization.ai?.pageLinkSummaries.enabled &&
|
||||
(inline.data.ref.kind === 'page' || inline.data.ref.kind === 'anchor');
|
||||
|
||||
return (
|
||||
<Tooltip.Provider delayDuration={200}>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto">
|
||||
<div className="overflow-hidden rounded-md straight-corners:rounded-none shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1 ">
|
||||
<div className="bg-tint-base p-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex flex-col">
|
||||
{breadcrumbs && breadcrumbs.length > 0 ? (
|
||||
<div className="mb-1 flex grow flex-wrap items-center gap-x-2 gap-y-0.5 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
{breadcrumbs.map((crumb, index) => {
|
||||
const Tag = crumb.href ? StyledLink : 'div';
|
||||
|
||||
return (
|
||||
<Fragment key={crumb.label}>
|
||||
{index !== 0 ? (
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className="size-3 text-tint-subtle"
|
||||
/>
|
||||
) : null}
|
||||
<Tag
|
||||
className={tcls(
|
||||
'flex gap-1',
|
||||
crumb.href &&
|
||||
'links-default:text-tint no-underline hover:underline contrast-more:underline contrast-more:decoration-current'
|
||||
)}
|
||||
href={crumb.href ?? '#'}
|
||||
>
|
||||
{crumb.icon ? (
|
||||
<span className="mt-0.5 text-tint-subtle empty:hidden">
|
||||
{crumb.icon}
|
||||
</span>
|
||||
) : null}
|
||||
{crumb.label}
|
||||
</Tag>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={tcls(
|
||||
'flex gap-2 leading-snug',
|
||||
isExternal && 'text-sm [overflow-wrap:anywhere]'
|
||||
)}
|
||||
>
|
||||
{resolved.icon ? (
|
||||
<div className="mt-1 text-tint-subtle empty:hidden">
|
||||
{resolved.icon}
|
||||
</div>
|
||||
) : null}
|
||||
<h5 className="font-semibold">{resolved.text}</h5>
|
||||
</div>
|
||||
</div>
|
||||
{!isSamePage && resolved.href ? (
|
||||
<Button
|
||||
className={tcls(
|
||||
'-mx-2 -my-2 ml-auto',
|
||||
breadcrumbs?.length === 0
|
||||
? 'place-self-center'
|
||||
: null
|
||||
)}
|
||||
variant="blank"
|
||||
href={resolved.href}
|
||||
target="_blank"
|
||||
label={tString(language, 'open_in_new_tab')}
|
||||
size="small"
|
||||
icon="arrow-up-right-from-square"
|
||||
iconOnly={true}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{resolved.subText ? (
|
||||
<p className="mt-1 text-sm text-tint">{resolved.subText}</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{hasAISummary && 'page' in context && 'page' in inline.data.ref ? (
|
||||
<div className="border-tint-subtle border-t bg-tint p-4">
|
||||
<AIPageLinkSummary
|
||||
targetPageId={
|
||||
resolved.page?.id ??
|
||||
inline.data.ref.page ??
|
||||
context.page.id
|
||||
}
|
||||
targetSpaceId={inline.data.ref.space ?? context.space.id}
|
||||
linkTitle={getNodeText(inline)}
|
||||
linkPreview={`**${resolved.text}**: ${resolved.subText}`}
|
||||
showTrademark={
|
||||
'customization' in context &&
|
||||
context.customization.trademark.enabled
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Tooltip.Arrow className={hasAISummary ? 'fill-tint-3' : 'fill-tint-1'} />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
import type { JSONDocument } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { OpenAPIOperation as BaseOpenAPIOperation } from '@gitbook/react-openapi';
|
||||
|
||||
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
|
||||
import { getOpenAPIContext } from './context';
|
||||
|
||||
/**
|
||||
* Render an openapi block or an openapi-operation block.
|
||||
@@ -55,56 +51,7 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
|
||||
return (
|
||||
<BaseOpenAPIOperation
|
||||
data={data}
|
||||
context={{
|
||||
specUrl,
|
||||
icons: {
|
||||
chevronDown: <Icon icon="chevron-down" />,
|
||||
chevronRight: <Icon icon="chevron-right" />,
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
||||
renderDocument: (documentProps) => (
|
||||
<DocumentView
|
||||
document={documentProps.document as JSONDocument}
|
||||
context={props.context}
|
||||
style="space-y-6"
|
||||
blockStyle="max-w-full"
|
||||
/>
|
||||
),
|
||||
renderHeading: (headingProps) => (
|
||||
<Heading
|
||||
document={props.document}
|
||||
ancestorBlocks={props.ancestorBlocks}
|
||||
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||
context={props.context}
|
||||
style={tcls([
|
||||
headingProps.deprecated ? 'line-through' : undefined,
|
||||
headingProps.deprecated || !!headingProps.stability
|
||||
? '[&>div]:mt-0'
|
||||
: undefined,
|
||||
])}
|
||||
block={{
|
||||
object: 'block',
|
||||
key: `${block.key}-heading`,
|
||||
meta: block.meta,
|
||||
data: {},
|
||||
type: 'heading-2',
|
||||
nodes: [
|
||||
{
|
||||
key: `${block.key}-heading-text`,
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{ text: headingProps.title, object: 'leaf', marks: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
),
|
||||
defaultInteractiveOpened: context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
}}
|
||||
context={getOpenAPIContext({ props, specUrl })}
|
||||
className="openapi-block"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
@@ -8,6 +7,7 @@ import type { BlockProps } from '../Block';
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
|
||||
import { getOpenAPIContext } from './context';
|
||||
|
||||
/**
|
||||
* Render an openapi-schemas block.
|
||||
@@ -49,19 +49,9 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
|
||||
|
||||
return (
|
||||
<BaseOpenAPISchemas
|
||||
data={data}
|
||||
schemas={data.schemas}
|
||||
grouped={block.data.grouped}
|
||||
context={{
|
||||
specUrl,
|
||||
icons: {
|
||||
chevronDown: <Icon icon="chevron-down" />,
|
||||
chevronRight: <Icon icon="chevron-right" />,
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
defaultInteractiveOpened: context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
}}
|
||||
context={getOpenAPIContext({ props, specUrl })}
|
||||
className="openapi-block"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { JSONDocument } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import type { OpenAPIContext } from '@gitbook/react-openapi';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import type { AnyOpenAPIOperationsBlock, OpenAPISchemasBlock } from '@/lib/openapi/types';
|
||||
|
||||
/**
|
||||
* Get the OpenAPI context to render a block.
|
||||
*/
|
||||
export function getOpenAPIContext(args: {
|
||||
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>;
|
||||
specUrl: string;
|
||||
}): OpenAPIContext {
|
||||
const { props, specUrl } = args;
|
||||
const { block } = props;
|
||||
return {
|
||||
specUrl,
|
||||
icons: {
|
||||
chevronDown: <Icon icon="chevron-down" />,
|
||||
chevronRight: <Icon icon="chevron-right" />,
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
||||
renderDocument: (documentProps) => (
|
||||
<DocumentView
|
||||
document={documentProps.document as JSONDocument}
|
||||
context={props.context}
|
||||
style="space-y-6"
|
||||
blockStyle="max-w-full"
|
||||
/>
|
||||
),
|
||||
renderHeading: (headingProps) => (
|
||||
<Heading
|
||||
document={props.document}
|
||||
ancestorBlocks={props.ancestorBlocks}
|
||||
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||
context={props.context}
|
||||
style={tcls([
|
||||
headingProps.deprecated ? 'line-through' : undefined,
|
||||
headingProps.deprecated || !!headingProps.stability
|
||||
? '[&>div]:mt-0'
|
||||
: undefined,
|
||||
])}
|
||||
block={{
|
||||
object: 'block',
|
||||
key: `${block.key}-heading`,
|
||||
meta: block.meta,
|
||||
data: {},
|
||||
type: 'heading-2',
|
||||
nodes: [
|
||||
{
|
||||
key: `${block.key}-heading-text`,
|
||||
object: 'text',
|
||||
leaves: [{ text: headingProps.title, object: 'leaf', marks: [] }],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
),
|
||||
defaultInteractiveOpened: props.context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/* Layout Components */
|
||||
.openapi-operation {
|
||||
.openapi-operation,
|
||||
.openapi-schemas {
|
||||
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
|
||||
}
|
||||
|
||||
@@ -17,7 +18,7 @@
|
||||
}
|
||||
|
||||
.openapi-summary {
|
||||
@apply flex flex-col items-start justify-start gap-3;
|
||||
@apply flex flex-col items-start justify-start gap-3 scroll-m-12;
|
||||
}
|
||||
|
||||
.openapi-summary-tags {
|
||||
@@ -29,10 +30,6 @@
|
||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||
}
|
||||
|
||||
.openapi-stability-stable {
|
||||
@apply text-green-600 dark:text-green-300 bg-green-50 dark:bg-green-900/6 ring-green-500/5;
|
||||
}
|
||||
|
||||
.openapi-stability-alpha {
|
||||
@apply text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-900/6 ring-amber-500/5;
|
||||
}
|
||||
@@ -155,6 +152,14 @@
|
||||
/* unstyled */
|
||||
}
|
||||
|
||||
.openapi-schema-root-description.openapi-markdown {
|
||||
@apply prose-sm text-balance mt-1.5 !text-[0.813rem] text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description {
|
||||
@apply px-2.5 pt-1 !text-sm;
|
||||
}
|
||||
|
||||
.openapi-schema-properties {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
@@ -203,7 +208,7 @@
|
||||
.openapi-schema-name {
|
||||
/* To make double click on the property name select only the name,
|
||||
we disable selection on the parent and re-enable it on the children. */
|
||||
@apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap;
|
||||
@apply select-none text-sm text-balance *:whitespace-nowrap flex flex-wrap gap-y-1.5 gap-x-2.5;
|
||||
}
|
||||
|
||||
.openapi-schema-name .openapi-deprecated {
|
||||
@@ -265,15 +270,11 @@
|
||||
|
||||
/* Schema Enum */
|
||||
.openapi-schema-enum {
|
||||
@apply flex flex-row text-sm leading-relaxed gap-2 flex-wrap text-tint;
|
||||
}
|
||||
|
||||
.openapi-schema-enum-list {
|
||||
@apply flex flex-row gap-1.5 items-center;
|
||||
@apply text-sm leading-relaxed max-w-full text-tint;
|
||||
}
|
||||
|
||||
.openapi-schema-enum-value {
|
||||
@apply text-sm;
|
||||
@apply text-sm mr-1.5;
|
||||
}
|
||||
|
||||
.openapi-schema-enum-value:first-child {
|
||||
@@ -286,7 +287,7 @@
|
||||
|
||||
/* Schema Description */
|
||||
.openapi-schema-description.openapi-markdown {
|
||||
@apply prose-sm text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint overflow-hidden text-pretty !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-schema-description.openapi-markdown pre:has(code) {
|
||||
@@ -306,13 +307,15 @@
|
||||
|
||||
/* Schema Examples */
|
||||
.openapi-schema-example,
|
||||
.openapi-schema-pattern {
|
||||
.openapi-schema-pattern,
|
||||
.openapi-schema-default {
|
||||
@apply prose-sm text-tint;
|
||||
}
|
||||
|
||||
.openapi-schema-example code,
|
||||
.openapi-schema-pattern code,
|
||||
.openapi-schema-enum-value code {
|
||||
.openapi-schema-enum-value 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;
|
||||
}
|
||||
|
||||
@@ -326,7 +329,7 @@
|
||||
}
|
||||
|
||||
.openapi-securities-description.openapi-markdown {
|
||||
@apply prose-sm text-tint !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint !font-normal select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-securities-label {
|
||||
@@ -352,7 +355,7 @@
|
||||
}
|
||||
|
||||
.openapi-requestbody-description.openapi-markdown {
|
||||
@apply prose-sm text-tint !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint !font-normal text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
}
|
||||
|
||||
/* Responses */
|
||||
@@ -369,7 +372,7 @@
|
||||
}
|
||||
|
||||
.openapi-response-description.openapi-markdown {
|
||||
@apply text-left prose-sm text-[0.813rem] h-auto relative leading-[1.125rem] text-tint !font-normal truncate select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@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;
|
||||
}
|
||||
|
||||
.openapi-response-description.openapi-markdown::-webkit-scrollbar {
|
||||
@@ -485,12 +488,30 @@
|
||||
@apply flex flex-row items-center py-2 px-3 justify-end border-t border-tint-subtle;
|
||||
}
|
||||
|
||||
/* Response Example */
|
||||
.openapi-response-example {
|
||||
/* Panel */
|
||||
.openapi-panel {
|
||||
@apply border rounded bg-tint border-tint-subtle;
|
||||
}
|
||||
|
||||
.openapi-response-example-empty {
|
||||
.openapi-panel-heading {
|
||||
@apply font-medium px-4 py-2 text-xs uppercase;
|
||||
}
|
||||
|
||||
.openapi-panel-body {
|
||||
@apply relative;
|
||||
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
|
||||
}
|
||||
|
||||
.openapi-panel-footer {
|
||||
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
|
||||
}
|
||||
|
||||
.openapi-panel-footer .openapi-markdown {
|
||||
@apply text-[0.813rem] text-tint;
|
||||
}
|
||||
|
||||
/* Example */
|
||||
.openapi-example-empty {
|
||||
@apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center;
|
||||
}
|
||||
|
||||
@@ -561,15 +582,6 @@
|
||||
|
||||
.openapi-tabs-panel {
|
||||
@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-tabs-footer {
|
||||
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
|
||||
}
|
||||
|
||||
.openapi-tabs-footer .openapi-markdown {
|
||||
@apply text-[0.813rem] text-tint;
|
||||
}
|
||||
|
||||
/* Disclosure group */
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
key={index}
|
||||
href={ref.href}
|
||||
target="_blank"
|
||||
style={['flex', 'flex-row', 'items-center', 'gap-2']}
|
||||
className="flex flex-row items-center gap-2"
|
||||
insights={
|
||||
ref.file
|
||||
? {
|
||||
|
||||
@@ -142,10 +142,9 @@ function HeaderItemButton(
|
||||
position: SiteInsightsLinkPosition.Header,
|
||||
},
|
||||
}}
|
||||
label={title}
|
||||
{...rest}
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useDebounceCallback, useEventCallback } from 'usehooks-ts';
|
||||
import type { VisitorAuthClaims } from '@/lib/adaptive';
|
||||
import { getAllBrowserCookiesMap } from '@/lib/browser-cookies';
|
||||
import { getSession } from './sessions';
|
||||
import { useVisitedPages } from './useVisitedPages';
|
||||
import { getVisitorId } from './visitorId';
|
||||
|
||||
export type InsightsEventName = api.SiteInsightsEvent['type'];
|
||||
@@ -63,9 +64,19 @@ type TrackEventCallback = <EventName extends InsightsEventName>(
|
||||
const InsightsContext = React.createContext<TrackEventCallback>(() => {});
|
||||
|
||||
interface InsightsProviderProps extends InsightsEventContext {
|
||||
/** If true, the events will be sent to the server. */
|
||||
enabled: boolean;
|
||||
|
||||
/** If true, the visitor cookie tracking will be used */
|
||||
visitorCookieTrackingEnabled: boolean;
|
||||
|
||||
/** The URL of the app. */
|
||||
appURL: string;
|
||||
|
||||
/** The host of the API. */
|
||||
apiHost: string;
|
||||
|
||||
/** The children of the provider. */
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -73,8 +84,9 @@ interface InsightsProviderProps extends InsightsEventContext {
|
||||
* Wrap the content of the app with the InsightsProvider to track events.
|
||||
*/
|
||||
export function InsightsProvider(props: InsightsProviderProps) {
|
||||
const { enabled, appURL, apiHost, children, ...context } = props;
|
||||
const { enabled, appURL, apiHost, children, visitorCookieTrackingEnabled, ...context } = props;
|
||||
|
||||
const addVisitedPage = useVisitedPages((state) => state.addPage);
|
||||
const visitorIdRef = React.useRef<string | null>(null);
|
||||
const eventsRef = React.useRef<{
|
||||
[pathname: string]:
|
||||
@@ -124,6 +136,14 @@ export function InsightsProvider(props: InsightsProviderProps) {
|
||||
...eventsForPathname,
|
||||
events: [],
|
||||
};
|
||||
|
||||
// Mark the page as visited in our local state
|
||||
if (eventsForPathname.pageContext.pageId) {
|
||||
addVisitedPage({
|
||||
spaceId: context.spaceId,
|
||||
pageId: eventsForPathname.pageContext.pageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (allEvents.length > 0) {
|
||||
@@ -140,7 +160,8 @@ export function InsightsProvider(props: InsightsProviderProps) {
|
||||
});
|
||||
|
||||
const flushBatchedEvents = useDebounceCallback(async () => {
|
||||
const visitorId = visitorIdRef.current ?? (await getVisitorId(appURL));
|
||||
const visitorId =
|
||||
visitorIdRef.current ?? (await getVisitorId(appURL, visitorCookieTrackingEnabled));
|
||||
visitorIdRef.current = visitorId;
|
||||
|
||||
flushEventsSync();
|
||||
@@ -184,7 +205,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
|
||||
* Get the visitor ID and store it in a ref.
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
getVisitorId(appURL).then((visitorId) => {
|
||||
getVisitorId(appURL, visitorCookieTrackingEnabled).then((visitorId) => {
|
||||
visitorIdRef.current = visitorId;
|
||||
// When the page is unloaded, flush all events, but only if the visitor ID is set
|
||||
window.addEventListener('beforeunload', flushEventsSync);
|
||||
@@ -192,7 +213,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', flushEventsSync);
|
||||
};
|
||||
}, [flushEventsSync, appURL]);
|
||||
}, [flushEventsSync, appURL, visitorCookieTrackingEnabled]);
|
||||
|
||||
return (
|
||||
<InsightsContext.Provider value={trackEvent}>
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './InsightsProvider';
|
||||
export * from './visitorId';
|
||||
export * from './cookies';
|
||||
export * from './TrackPageViewEvent';
|
||||
export * from './useVisitedPages';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
type VisitedPage = {
|
||||
spaceId: string;
|
||||
pageId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A store for the pages that have been visited in the current session.
|
||||
*/
|
||||
export const useVisitedPages = create<{
|
||||
pages: VisitedPage[];
|
||||
addPage: (page: VisitedPage) => void;
|
||||
}>((set) => ({
|
||||
pages: [],
|
||||
addPage: (page) =>
|
||||
set((state) => {
|
||||
const lastPage = state.pages[state.pages.length - 1];
|
||||
if (lastPage && lastPage.spaceId === page.spaceId && lastPage.pageId === page.pageId) {
|
||||
return { pages: state.pages };
|
||||
}
|
||||
|
||||
return { pages: [...state.pages, page] };
|
||||
}),
|
||||
}));
|
||||
@@ -13,10 +13,13 @@ let pendingVisitorId: Promise<string> | null = null;
|
||||
/**
|
||||
* Return the current visitor identifier.
|
||||
*/
|
||||
export async function getVisitorId(appURL: string): Promise<string> {
|
||||
export async function getVisitorId(
|
||||
appURL: string,
|
||||
visitorCookieTrackingEnabled: boolean
|
||||
): Promise<string> {
|
||||
if (!visitorId) {
|
||||
if (!pendingVisitorId) {
|
||||
pendingVisitorId = fetchVisitorID(appURL).finally(() => {
|
||||
pendingVisitorId = fetchVisitorID(appURL, visitorCookieTrackingEnabled).finally(() => {
|
||||
pendingVisitorId = null;
|
||||
});
|
||||
}
|
||||
@@ -30,10 +33,13 @@ export async function getVisitorId(appURL: string): Promise<string> {
|
||||
/**
|
||||
* Propose a visitor identifier to the GitBook.com server and get the devideId back.
|
||||
*/
|
||||
async function fetchVisitorID(appURL: string): Promise<string> {
|
||||
async function fetchVisitorID(
|
||||
appURL: string,
|
||||
visitorCookieTrackingEnabled: boolean
|
||||
): Promise<string> {
|
||||
const withoutCookies = isCookiesTrackingDisabled();
|
||||
|
||||
if (withoutCookies) {
|
||||
if (withoutCookies || !visitorCookieTrackingEnabled) {
|
||||
return generateRandomId();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { TrademarkLink } from '@/components/TableOfContents/Trademark';
|
||||
import type { PolymorphicComponentProp } from '@/components/utils/types';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { tString } from '@/intl/translate';
|
||||
import { getPagePDFContainerId } from '@/lib/links';
|
||||
import { resolvePageId } from '@/lib/pages';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { defaultCustomization } from '@/lib/utils';
|
||||
@@ -29,6 +28,7 @@ import { type PDFSearchParams, getPDFSearchParams } from './urls';
|
||||
import { PageControlButtons } from './PageControlButtons';
|
||||
import { PrintButton } from './PrintButton';
|
||||
import './pdf.css';
|
||||
import { sanitizeGitBookAppURL } from '@/lib/app';
|
||||
|
||||
const DEFAULT_LIMIT = 100;
|
||||
|
||||
@@ -92,7 +92,10 @@ export async function PDFPage(props: {
|
||||
<div className={tcls('fixed', 'left-12', 'top-12', 'print:hidden', 'z-50')}>
|
||||
<a
|
||||
title={tString(language, 'pdf_goback')}
|
||||
href={pdfParams.back ?? linker.toAbsoluteURL(linker.toPathInSpace(''))}
|
||||
href={
|
||||
(pdfParams.back ? sanitizeGitBookAppURL(pdfParams.back) : null) ??
|
||||
linker.toAbsoluteURL(linker.toPathInSpace(''))
|
||||
}
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
@@ -353,3 +356,13 @@ function selectPages(
|
||||
});
|
||||
return limitTo(allPages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the HTML ID for the container of a page or a given anchor in it.
|
||||
*/
|
||||
function getPagePDFContainerId(
|
||||
page: RevisionPageDocument | RevisionPageGroup,
|
||||
anchor?: string
|
||||
): string {
|
||||
return `pdf-page-${page.id}${anchor ? `-${anchor}` : ''}`;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
|
||||
import { useScrollActiveId } from '@/components/hooks';
|
||||
import { Button } from '@/components/primitives';
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { t, tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { type PDFSearchParams, getPDFURLSearchParams } from './urls';
|
||||
|
||||
@@ -58,9 +58,8 @@ export function PageControlButtons(props: {
|
||||
only: true,
|
||||
}).toString()}`}
|
||||
variant="secondary"
|
||||
>
|
||||
{t(language, 'pdf_mode_only_page')}
|
||||
</Button>
|
||||
label={tString(language, 'pdf_mode_only_page')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
href={`?${getPDFURLSearchParams({
|
||||
@@ -69,9 +68,8 @@ export function PageControlButtons(props: {
|
||||
only: false,
|
||||
}).toString()}`}
|
||||
variant="secondary"
|
||||
>
|
||||
{t(language, 'pdf_mode_all')}
|
||||
</Button>
|
||||
label={tString(language, 'pdf_mode_all')}
|
||||
/>
|
||||
|
||||
{trademark ? <div className={tcls('mt-5')}>{trademark}</div> : null}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { t } from '@/intl/translate';
|
||||
import { hasFullWidthBlock, isNodeEmpty } from '@/lib/document';
|
||||
import type { AncestorRevisionPage } from '@/lib/pages';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { DocumentView, DocumentViewSkeleton } from '../DocumentView';
|
||||
import { TrackPageViewEvent } from '../Insights';
|
||||
import { PageFeedbackForm } from '../PageFeedback';
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { RevisionPageDocument, RevisionPageDocumentCover } from '@gitbook/api';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import type { StaticImageData } from 'next/image';
|
||||
|
||||
import { Image, type ImageSize } from '@/components/utils';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import defaultPageCover from './default-page-cover.svg';
|
||||
import defaultPageCoverSVG from './default-page-cover.svg';
|
||||
|
||||
const defaultPageCover = defaultPageCoverSVG as StaticImageData;
|
||||
const PAGE_COVER_SIZE: ImageSize = { width: 1990, height: 480 };
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function PageHeader(props: {
|
||||
>
|
||||
{ancestors.length > 0 && (
|
||||
<nav>
|
||||
<ol className={tcls('flex', 'flex-wrap', 'items-center', 'gap-2')}>
|
||||
<ol className={tcls('flex', 'flex-wrap', 'items-center', 'gap-2', 'text-tint')}>
|
||||
{ancestors.map((breadcrumb, index) => {
|
||||
const href = linker.toPathForPage({ pages, page: breadcrumb });
|
||||
return (
|
||||
@@ -35,7 +35,7 @@ export async function PageHeader(props: {
|
||||
<li key={breadcrumb.id}>
|
||||
<StyledLink
|
||||
href={href}
|
||||
style={tcls(
|
||||
className={tcls(
|
||||
'no-underline',
|
||||
'hover:underline',
|
||||
'text-xs',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export type PageContextType = {
|
||||
pageId: string;
|
||||
spaceId: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export const PageContext = React.createContext<PageContextType | null>(null);
|
||||
|
||||
/**
|
||||
* Client side context provider to pass information about the current page.
|
||||
*/
|
||||
export function PageContextProvider(props: PageContextType & { children: React.ReactNode }) {
|
||||
const { pageId, spaceId, title, children } = props;
|
||||
|
||||
const value = React.useMemo(() => ({ pageId, spaceId, title }), [pageId, spaceId, title]);
|
||||
|
||||
return <PageContext.Provider value={value}>{children}</PageContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to use the page context.
|
||||
*/
|
||||
export function usePageContext() {
|
||||
const context = React.useContext(PageContext);
|
||||
if (!context) {
|
||||
throw new Error('usePageContext must be used within a PageContextProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './PageContext';
|
||||
@@ -106,9 +106,8 @@ export function PageFeedbackForm(props: {
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => onSubmitComment(rating, comment)}
|
||||
>
|
||||
{t(languages, 'submit')}
|
||||
</Button>
|
||||
label={tString(languages, 'submit')}
|
||||
/>
|
||||
{comment.length > MAX_COMMENT_LENGTH * 0.8 ? (
|
||||
<span
|
||||
className={
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useScrollPage } from '@/components/hooks';
|
||||
export function PageClientLayout(props: { withSections?: boolean }) {
|
||||
// We use this hook in the page layout to ensure the elements for the blocks
|
||||
// are rendered before we scroll to a hash or to the top of the page
|
||||
useScrollPage({ scrollMarginTop: props.withSections ? 50 : undefined });
|
||||
useScrollPage({ scrollMarginTop: props.withSections ? 48 : undefined });
|
||||
|
||||
useStripFallbackQueryParam();
|
||||
return null;
|
||||
|
||||
@@ -11,12 +11,10 @@ import { getPagePath } from '@/lib/pages';
|
||||
import { isPageIndexable, isSiteIndexable } from '@/lib/seo';
|
||||
|
||||
import { getResizedImageURL } from '@v2/lib/images';
|
||||
import { PageContextProvider } from '../PageContext';
|
||||
import { PageClientLayout } from './PageClientLayout';
|
||||
import { type PagePathParams, fetchPageData, getPathnameParam } from './fetch';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export type SitePageProps = {
|
||||
context: GitBookSiteContext;
|
||||
pageParams: PagePathParams;
|
||||
@@ -67,7 +65,7 @@ export async function SitePage(props: SitePageProps) {
|
||||
const document = await getPageDocument(context.dataFetcher, context.space, page);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContextProvider pageId={page.id} spaceId={context.space.id} title={page.title}>
|
||||
{withFullPageCover && page.cover ? (
|
||||
<PageCover as="full" page={page} cover={page.cover} context={context} />
|
||||
) : null}
|
||||
@@ -92,7 +90,7 @@ export async function SitePage(props: SitePageProps) {
|
||||
<React.Suspense fallback={null}>
|
||||
<PageClientLayout withSections={withSections} />
|
||||
</React.Suspense>
|
||||
</>
|
||||
</PageContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export function SpaceLayout(props: {
|
||||
revisionId={context.revisionId}
|
||||
spaceId={context.space.id}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
visitorCookieTrackingEnabled={context.customization.insights?.trackingCookie}
|
||||
>
|
||||
<Announcement context={context} />
|
||||
<Header withTopHeader={withTopHeader} context={context} />
|
||||
|
||||
@@ -1,62 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributeAnchorTarget, HTMLAttributes } from 'react';
|
||||
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import { Link, type LinkInsightsProps } from './Link';
|
||||
|
||||
type ButtonProps = {
|
||||
href?: string;
|
||||
variant?: 'primary' | 'secondary';
|
||||
variant?: 'primary' | 'secondary' | 'blank';
|
||||
icon?: IconName;
|
||||
iconOnly?: boolean;
|
||||
size?: 'default' | 'medium' | 'small';
|
||||
className?: ClassValue;
|
||||
label?: string;
|
||||
} & LinkInsightsProps &
|
||||
HTMLAttributes<HTMLElement>;
|
||||
|
||||
const variantClasses = {
|
||||
primary: [
|
||||
'bg-primary-solid',
|
||||
'text-contrast-primary-solid',
|
||||
'hover:bg-primary-solid-hover',
|
||||
'hover:text-contrast-primary-solid-hover',
|
||||
'ring-0',
|
||||
'contrast-more:ring-1',
|
||||
],
|
||||
blank: [
|
||||
'bg-transparent',
|
||||
'text-tint',
|
||||
'ring-0',
|
||||
'shadow-none',
|
||||
'hover:bg-primary-hover',
|
||||
'hover:text-primary',
|
||||
'hover:scale-1',
|
||||
'hover:shadow-none',
|
||||
'contrast-more:bg-tint-subtle',
|
||||
],
|
||||
secondary: [
|
||||
'bg-tint',
|
||||
'text-tint',
|
||||
'hover:bg-tint-hover',
|
||||
'hover:text-primary',
|
||||
'contrast-more:bg-tint-subtle',
|
||||
],
|
||||
};
|
||||
|
||||
export function Button({
|
||||
href,
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'default',
|
||||
className,
|
||||
insights,
|
||||
target,
|
||||
label,
|
||||
icon,
|
||||
iconOnly = false,
|
||||
...rest
|
||||
}: ButtonProps) {
|
||||
const variantClasses =
|
||||
variant === 'primary'
|
||||
? //PRIMARY
|
||||
[
|
||||
'bg-primary-solid',
|
||||
'text-contrast-primary-solid',
|
||||
'hover:bg-primary-solid-hover',
|
||||
'hover:text-contrast-primary-solid-hover',
|
||||
'ring-0',
|
||||
'contrast-more:ring-1',
|
||||
]
|
||||
: // SECONDARY
|
||||
[
|
||||
'bg-tint',
|
||||
'text-tint',
|
||||
'hover:bg-tint-hover',
|
||||
'hover:text-primary',
|
||||
'contrast-more:bg-tint-subtle',
|
||||
];
|
||||
|
||||
}: ButtonProps & { target?: HTMLAttributeAnchorTarget }) {
|
||||
const sizes = {
|
||||
default: ['text-base', 'px-4', 'py-2'],
|
||||
medium: ['text-sm', 'px-3', 'py-1.5'],
|
||||
small: ['text-xs', 'px-3 py-2'],
|
||||
small: ['text-xs', 'py-2', iconOnly ? 'px-2' : 'px-3'],
|
||||
};
|
||||
|
||||
const sizeClasses = sizes[size] || sizes.default;
|
||||
|
||||
const domClassName = tcls(
|
||||
'button',
|
||||
'inline-block',
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'gap-2',
|
||||
'rounded-md',
|
||||
'straight-corners:rounded-none',
|
||||
'place-self-start',
|
||||
// 'place-self-start',
|
||||
|
||||
'ring-1',
|
||||
'ring-tint',
|
||||
@@ -79,22 +97,31 @@ export function Button({
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
'truncate',
|
||||
variantClasses,
|
||||
variantClasses[variant],
|
||||
sizeClasses,
|
||||
className
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className={domClassName} insights={insights} {...rest}>
|
||||
{children}
|
||||
<Link
|
||||
href={href}
|
||||
className={domClassName}
|
||||
insights={insights}
|
||||
aria-label={label}
|
||||
target={target}
|
||||
{...rest}
|
||||
>
|
||||
{icon ? <Icon icon={icon} className={tcls('size-[1em]')} /> : null}
|
||||
{iconOnly ? null : label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" className={domClassName} {...rest}>
|
||||
{children}
|
||||
<button type="button" className={domClassName} aria-label={label} {...rest}>
|
||||
{icon ? <Icon icon={icon} className={tcls('size-[1em]')} /> : null}
|
||||
{iconOnly ? null : label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { SVGProps } from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export const Loading = (props: Partial<SVGProps<SVGSVGElement>>) => {
|
||||
export const Loading = ({
|
||||
busy = true,
|
||||
...props
|
||||
}: { busy?: boolean } & SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
width="100%"
|
||||
@@ -14,7 +17,11 @@ export const Loading = (props: Partial<SVGProps<SVGSVGElement>>) => {
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
className={tcls('animate-[pathLoading_2s_ease_infinite_forwards]')}
|
||||
className={tcls(
|
||||
busy
|
||||
? 'animate-[pathLoading_2s_ease_infinite_forwards]'
|
||||
: 'animate-[pathLoading_2s_ease_forwards]'
|
||||
)}
|
||||
d="M6 59.5V56.291C6 45.8865 11.5194 36.263 20.5 31.0091V31.0091L60.9857 7.32407C63.4452 5.88525 66.4843 5.86317 68.9643 7.26611L116 33.8734L70.4183 60.2148C67.9468 61.6431 64.9014 61.6462 62.4269 60.223L29.9772 41.5592C19.3106 35.4242 6 43.1236 6 55.4288V64.8776C6 73.4486 10.5708 81.3691 17.9918 85.6575L54.59 106.807C62.0198 111.1 71.1766 111.1 78.6064 106.807L116.364 84.9874C120.074 82.8432 122.36 78.883 122.36 74.5975V59.2647C122.36 57.7248 120.692 56.7626 119.359 57.5331L72.6023 84.5529C68.8874 86.6996 64.309 86.6996 60.5941 84.5529L26 64.5617"
|
||||
stroke="currentColor"
|
||||
pathLength="100"
|
||||
@@ -27,7 +34,8 @@ export const Loading = (props: Partial<SVGProps<SVGSVGElement>>) => {
|
||||
d="M6 59.5V56.291C6 45.8865 11.5194 36.263 20.5 31.0091V31.0091L60.9857 7.32407C63.4452 5.88525 66.4843 5.86317 68.9643 7.26611L116 33.8734L70.4183 60.2148C67.9468 61.6431 64.9014 61.6462 62.4269 60.223L29.9772 41.5592C19.3106 35.4242 6 43.1236 6 55.4288V64.8776C6 73.4486 10.5708 81.3691 17.9918 85.6575L54.59 106.807C62.0198 111.1 71.1766 111.1 78.6064 106.807L116.364 84.9874C120.074 82.8432 122.36 78.883 122.36 74.5975V59.2647C122.36 57.7248 120.692 56.7626 119.359 57.5331L72.6023 84.5529C68.8874 86.6996 64.309 86.6996 60.5941 84.5529L26 64.5617"
|
||||
stroke="currentColor"
|
||||
pathLength="100"
|
||||
strokeOpacity="0.24"
|
||||
strokeOpacity={busy ? 0.24 : 1}
|
||||
className="transition-opacity duration-1000"
|
||||
fill="none"
|
||||
strokeWidth="11"
|
||||
strokeLinecap="round"
|
||||
|
||||
@@ -25,11 +25,11 @@ export const linkStyles = [
|
||||
/**
|
||||
* Styled version of Link component.
|
||||
*/
|
||||
export function StyledLink(props: Omit<LinkProps, 'style'> & { style?: ClassValue }) {
|
||||
const { style, ...rest } = props;
|
||||
export function StyledLink(props: Omit<LinkProps, 'style'> & { className?: ClassValue }) {
|
||||
const { className, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Link {...rest} className={tcls(linkStyles, style)}>
|
||||
<Link {...rest} className={tcls(linkStyles, className)}>
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -256,7 +256,7 @@ function ZoomImageModal(props: {
|
||||
)}
|
||||
onClick={onClose}
|
||||
>
|
||||
<Icon icon="compress-wide" className={tcls('size-5')} />
|
||||
<Icon icon="close" className={tcls('size-5')} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -58,4 +58,9 @@ export const de = {
|
||||
'Das PDF konnte für ${1} Seiten nicht generiert werden, Generierung wurde bei ${2} gestoppt.',
|
||||
pdf_limit_reached_continue: 'Mit ${1} weiteren Seiten erweitern.',
|
||||
more: 'Mehr',
|
||||
link_tooltip_external_link: 'Externe Verlinkung zu',
|
||||
link_tooltip_page_anchor: 'Zum Abschnitt springen',
|
||||
link_tooltip_ai_summary: 'Seitenhighlight',
|
||||
link_tooltip_ai_summary_description: 'Basierend auf Ihrem Kontext. Kann Fehler enthalten.',
|
||||
open_in_new_tab: 'In neuem Tab öffnen',
|
||||
};
|
||||
|
||||
@@ -56,4 +56,9 @@ export const en = {
|
||||
pdf_limit_reached: "Couldn't generate the PDF for ${1} pages, generation stopped at ${2}.",
|
||||
pdf_limit_reached_continue: 'Extend with ${1} more pages.',
|
||||
more: 'More',
|
||||
link_tooltip_external_link: 'External link to',
|
||||
link_tooltip_page_anchor: 'Jump to section',
|
||||
link_tooltip_ai_summary: 'Page highlight',
|
||||
link_tooltip_ai_summary_description: 'Based on your context. May contain mistakes.',
|
||||
open_in_new_tab: 'Open in new tab',
|
||||
};
|
||||
|
||||
@@ -60,4 +60,9 @@ export const es: TranslationLanguage = {
|
||||
'No se pudo generar el PDF para ${1} páginas, la generación se detuvo en ${2}.',
|
||||
pdf_limit_reached_continue: 'Extender con ${1} páginas más.',
|
||||
more: 'Más',
|
||||
link_tooltip_external_link: 'Enlace externo a',
|
||||
link_tooltip_page_anchor: 'Saltar a la sección',
|
||||
link_tooltip_ai_summary: 'Resumen de la página',
|
||||
link_tooltip_ai_summary_description: 'Basado en tu contexto. Puede contener errores.',
|
||||
open_in_new_tab: 'Abrir en una nueva pestaña',
|
||||
};
|
||||
|
||||
@@ -58,4 +58,9 @@ export const fr: TranslationLanguage = {
|
||||
pdf_limit_reached: 'Impossible de générer le PDF pour ${1} pages, génération arrêtée à ${2}.',
|
||||
pdf_limit_reached_continue: 'Étendre avec ${1} pages supplémentaires.',
|
||||
more: 'Plus',
|
||||
link_tooltip_external_link: 'Lien externe à',
|
||||
link_tooltip_page_anchor: 'Sauter à la section',
|
||||
link_tooltip_ai_summary: 'Résumé de la page',
|
||||
link_tooltip_ai_summary_description: 'Basé sur votre contexte. Peut contenir des erreurs.',
|
||||
open_in_new_tab: 'Ouvrir dans un nouvel onglet',
|
||||
};
|
||||
|
||||
@@ -58,4 +58,10 @@ export const ja: TranslationLanguage = {
|
||||
pdf_limit_reached: '${1}ページのPDFを生成できませんでした、${2}で生成が停止しました。',
|
||||
pdf_limit_reached_continue: 'さらに${1}ページで拡張',
|
||||
more: '詳細',
|
||||
link_tooltip_external_link: '外部リンク先',
|
||||
link_tooltip_page_anchor: 'ページ内リンク先',
|
||||
link_tooltip_ai_summary: 'ページのハイライト',
|
||||
link_tooltip_ai_summary_description:
|
||||
'あなたのコンテキストに基づいています。間違いが含まれる可能性があります。',
|
||||
open_in_new_tab: '新しいタブで開く',
|
||||
};
|
||||
|
||||
@@ -58,4 +58,9 @@ export const nl: TranslationLanguage = {
|
||||
pdf_limit_reached: "Kon de PDF niet genereren voor ${1} pagina's, generatie gestopt bij ${2}.",
|
||||
pdf_limit_reached_continue: 'Verleng met ${1} extra pagina’s.',
|
||||
more: 'Meer',
|
||||
link_tooltip_external_link: 'Externe link naar',
|
||||
link_tooltip_page_anchor: 'Spring naar sectie',
|
||||
link_tooltip_ai_summary: 'Pagina-samenvatting',
|
||||
link_tooltip_ai_summary_description: 'Gebaseerd op je context. Kan fouten bevatten.',
|
||||
open_in_new_tab: 'Open in nieuw tabblad',
|
||||
};
|
||||
|
||||
@@ -58,4 +58,9 @@ export const no: TranslationLanguage = {
|
||||
pdf_limit_reached: 'Kunne ikke generere PDF for ${1} sider, generering stoppet ved ${2}.',
|
||||
pdf_limit_reached_continue: 'Utvid med ${1} flere sider.',
|
||||
more: 'Mer',
|
||||
link_tooltip_external_link: 'Ekstern lenke til',
|
||||
link_tooltip_page_anchor: 'Hopp til seksjon',
|
||||
link_tooltip_ai_summary: 'Sidesammendrag',
|
||||
link_tooltip_ai_summary_description: 'Basert på din kontekst. Kan inneholde feil.',
|
||||
open_in_new_tab: 'Åpne i ny fane',
|
||||
};
|
||||
|
||||
@@ -58,4 +58,9 @@ export const pt_br = {
|
||||
'Não foi possível gerar o PDF para ${1} páginas, generation stopped at ${2}.',
|
||||
pdf_limit_reached_continue: 'Extender com mais ${1} páginas.',
|
||||
more: 'Mais',
|
||||
link_tooltip_external_link: 'Link externo para',
|
||||
link_tooltip_page_anchor: 'Pular para a seção',
|
||||
link_tooltip_ai_summary: 'Resumo da página',
|
||||
link_tooltip_ai_summary_description: 'Baseado no seu contexto. Pode conter erros.',
|
||||
open_in_new_tab: 'Abrir em uma nova guia',
|
||||
};
|
||||
|
||||
@@ -56,4 +56,9 @@ export const zh: TranslationLanguage = {
|
||||
pdf_limit_reached: '无法为${1}页生成 PDF,生成在${2}页时停止。',
|
||||
pdf_limit_reached_continue: '使用${1}页进行扩展。',
|
||||
more: '更多',
|
||||
link_tooltip_external_link: '外部链接到',
|
||||
link_tooltip_page_anchor: '跳转到页面',
|
||||
link_tooltip_ai_summary: '页面要点',
|
||||
link_tooltip_ai_summary_description: '基于您的上下文。可能包含错误。',
|
||||
open_in_new_tab: '在新标签页中打开',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { GITBOOK_APP_URL } from '@v2/lib/env';
|
||||
|
||||
/**
|
||||
* Create an absolute href in the GitBook application.
|
||||
*/
|
||||
export function getGitBookAppHref(pathname: string): string {
|
||||
const appUrl = new URL(GITBOOK_APP_URL);
|
||||
appUrl.pathname = pathname;
|
||||
|
||||
return appUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a URL to be a valid GitBook.com app URL.
|
||||
*/
|
||||
export function sanitizeGitBookAppURL(input: string): string | null {
|
||||
if (!URL.canParse(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = new URL(input);
|
||||
if (url.origin !== GITBOOK_APP_URL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
+51
-1
@@ -64,7 +64,10 @@ export const cloudflareDOCache: CacheBackend = {
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = await stub.purge();
|
||||
const keys = await retryOnDurableObjectError(async () => {
|
||||
return await stub.purge();
|
||||
});
|
||||
|
||||
keys.forEach((key) => {
|
||||
entries.push({ key, tag });
|
||||
});
|
||||
@@ -99,3 +102,50 @@ async function getStub(tag: string): Promise<CacheObjectStub | null> {
|
||||
|
||||
return stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry an operation on a Durable Object if it fails with a retriable error.
|
||||
* It will retry up to 4 times with an exponential backoff.
|
||||
*/
|
||||
export async function retryOnDurableObjectError<T>(
|
||||
operation: () => T | Promise<T>,
|
||||
attemptsLeft = 4,
|
||||
delay = 50
|
||||
): Promise<T> {
|
||||
if (attemptsLeft <= 0) {
|
||||
return operation();
|
||||
}
|
||||
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (!shouldRetryError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (attemptsLeft > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
return retryOnDurableObjectError(operation, attemptsLeft - 1, delay * 2);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const RETRIABLE_ERROR_MESSAGES = new Set([
|
||||
'Cannot resolve Durable Object due to transient issue on remote node.',
|
||||
'internal error',
|
||||
`Durable Object's isolate exceeded its memory limit and was reset.`,
|
||||
'cannot access storage because object has moved to a different machine',
|
||||
'Durable Object reset because its code was updated.',
|
||||
"The Durable Object's code has been updated, this version can no longer access storage.",
|
||||
// https://developers.cloudflare.com/workers/observability/errors/#runtime-errors
|
||||
'Network connection lost.',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if an error should be retried based on its error message.
|
||||
*/
|
||||
function shouldRetryError(error: unknown): boolean {
|
||||
return error instanceof Error && RETRIABLE_ERROR_MESSAGES.has(error.message);
|
||||
}
|
||||
|
||||
+19
-11
@@ -36,18 +36,25 @@ export async function revalidateTags(tags: string[]): Promise<{
|
||||
|
||||
await Promise.all(
|
||||
cacheBackends.map(async (backend, backendIndex) => {
|
||||
const { entries: addedEntries } = await backend.revalidateTags(tags);
|
||||
try {
|
||||
const { entries: addedEntries } = await backend.revalidateTags(tags);
|
||||
|
||||
addedEntries.forEach(({ key, tag }) => {
|
||||
stats[key] = stats[key] ?? {
|
||||
tag,
|
||||
backends: {},
|
||||
};
|
||||
stats[key].backends[backend.name] = { set: true };
|
||||
addedEntries.forEach(({ key, tag }) => {
|
||||
stats[key] = stats[key] ?? {
|
||||
tag,
|
||||
backends: {},
|
||||
};
|
||||
stats[key].backends[backend.name] = { set: true };
|
||||
|
||||
entries.set(key, { tag, key });
|
||||
keysByBackend.set(backendIndex, [...(keysByBackend.get(backendIndex) ?? []), key]);
|
||||
});
|
||||
entries.set(key, { tag, key });
|
||||
keysByBackend.set(backendIndex, [
|
||||
...(keysByBackend.get(backendIndex) ?? []),
|
||||
key,
|
||||
]);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`error revalidating tags on backend ${backend.name}: ${err}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -71,7 +78,8 @@ export async function revalidateTags(tags: string[]): Promise<{
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
|
||||
throw new Error(`error deleting entries on backend ${backend.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GitBookAnyContext } from '@v2/lib/context';
|
||||
|
||||
import { getNodeText } from './document';
|
||||
import { resolveOpenAPIOperationBlock } from './openapi/resolveOpenAPIOperationBlock';
|
||||
import { resolveOpenAPISchemasBlock } from './openapi/resolveOpenAPISchemasBlock';
|
||||
|
||||
export interface DocumentSection {
|
||||
id: string;
|
||||
@@ -52,6 +53,26 @@ export async function getDocumentSections(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
block.type === 'openapi-schemas' &&
|
||||
!block.data.grouped &&
|
||||
block.meta?.id &&
|
||||
block.data.schemas.length === 1
|
||||
) {
|
||||
const { data } = await resolveOpenAPISchemasBlock({
|
||||
block,
|
||||
context,
|
||||
});
|
||||
const schema = data?.schemas[0];
|
||||
if (schema) {
|
||||
sections.push({
|
||||
id: block.meta.id,
|
||||
title: `The ${schema.name} object`,
|
||||
depth: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import 'server-only';
|
||||
|
||||
import {
|
||||
type RevisionPage,
|
||||
type RevisionPageDocument,
|
||||
type RevisionPageGroup,
|
||||
RevisionPageType,
|
||||
} from '@gitbook/api';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { GITBOOK_APP_URL } from '@v2/lib/env';
|
||||
import { getPagePath } from './pages';
|
||||
import { withLeadingSlash, withTrailingSlash } from './paths';
|
||||
import { assertIsNotV2 } from './v2';
|
||||
|
||||
export interface PageHrefContext {
|
||||
/**
|
||||
* If defined, we are generating a PDF of the specific page IDs,
|
||||
* and these pages will be rendered in the same HTML output.
|
||||
*/
|
||||
pdf?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base path for the current request.
|
||||
* The value will start and finish with /
|
||||
*/
|
||||
export async function getBasePath(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const path = headersList.get('x-gitbook-basepath') ?? '/';
|
||||
|
||||
return withTrailingSlash(withLeadingSlash(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the site base path for the current request.
|
||||
* The value will start and finish with /
|
||||
*/
|
||||
export async function getSiteBasePath(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const path = headersList.get('x-gitbook-site-basepath') ?? '/';
|
||||
|
||||
return withTrailingSlash(withLeadingSlash(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current host for the current request.
|
||||
*/
|
||||
export async function getHost(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const mode = headersList.get('x-gitbook-mode');
|
||||
if (mode === 'proxy') {
|
||||
return headersList.get('x-forwarded-host') ?? '';
|
||||
}
|
||||
|
||||
return headersList.get('x-gitbook-host') ?? headersList.get('host') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root URL for the GitBook Open instance (not the content).
|
||||
* Use `baseUrl` to get the base URL for the current content.
|
||||
*
|
||||
* The URL will end with "/".
|
||||
*/
|
||||
export async function getRootUrl(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const [headersList, host] = await Promise.all([headers(), getHost()]);
|
||||
const protocol = headersList.get('x-forwarded-proto') ?? 'https';
|
||||
let path = headersList.get('x-gitbook-origin-basepath') ?? '/';
|
||||
|
||||
if (!path.startsWith('/')) {
|
||||
path = `/${path}`;
|
||||
}
|
||||
|
||||
if (!path.endsWith('/')) {
|
||||
path = `${path}/`;
|
||||
}
|
||||
|
||||
return `${protocol}://${host}${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base URL for the current content.
|
||||
* The URL will end with "/".
|
||||
*/
|
||||
export async function getBaseUrl(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const [headersList, host, basePath] = await Promise.all([headers(), getHost(), getBasePath()]);
|
||||
const protocol = headersList.get('x-forwarded-proto') ?? 'https';
|
||||
return `${protocol}://${host}${basePath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an absolute href in the current content.
|
||||
*/
|
||||
export async function getAbsoluteHref(href: string, withHost = false): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const base = withHost ? await getBaseUrl() : await getBasePath();
|
||||
return `${base}${href.startsWith('/') ? href.slice(1) : href}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an absolute href in the GitBook application.
|
||||
*/
|
||||
export function getGitbookAppHref(pathname: string): string {
|
||||
const appUrl = new URL(GITBOOK_APP_URL);
|
||||
appUrl.pathname = pathname;
|
||||
|
||||
return appUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a link to a page path in the current space.
|
||||
*/
|
||||
export async function getPageHref(
|
||||
rootPages: RevisionPage[],
|
||||
page: RevisionPageDocument | RevisionPageGroup,
|
||||
context: PageHrefContext = {},
|
||||
/** Anchor to link to in the page. */
|
||||
anchor?: string
|
||||
): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const { pdf } = context;
|
||||
|
||||
if (pdf) {
|
||||
if (pdf.includes(page.id)) {
|
||||
return `#${getPagePDFContainerId(page, anchor)}`;
|
||||
}
|
||||
if (page.type === RevisionPageType.Group) {
|
||||
return '#';
|
||||
}
|
||||
|
||||
// Use an absolute URL to the page
|
||||
return page.urls.app;
|
||||
}
|
||||
|
||||
const href =
|
||||
(await getAbsoluteHref(getPagePath(rootPages, page))) + (anchor ? `#${anchor}` : '');
|
||||
return href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the HTML ID for the container of a page during a PDF rendering.
|
||||
*/
|
||||
export function getPagePDFContainerId(
|
||||
page: RevisionPageDocument | RevisionPageGroup,
|
||||
anchor?: string
|
||||
): string {
|
||||
return `pdf-page-${page.id}${anchor ? `-${anchor}` : ''}`;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { parseOpenAPI } from '@gitbook/openapi-parser';
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cache';
|
||||
import type {
|
||||
@@ -6,7 +7,8 @@ import type {
|
||||
OpenAPISchemasBlock,
|
||||
ResolveOpenAPIBlockArgs,
|
||||
} from '@/lib/openapi/types';
|
||||
import { memoize } from '@v2/lib/data/memoize';
|
||||
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
|
||||
import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize';
|
||||
import { assert } from 'ts-essentials';
|
||||
import { resolveContentRef } from '../references';
|
||||
import { isV2 } from '../v2';
|
||||
@@ -66,15 +68,25 @@ const fetchFilesystemV1 = cache({
|
||||
},
|
||||
});
|
||||
|
||||
const fetchFilesystemV2 = memoize(async function fetchFilesystemV2(url: string) {
|
||||
'use cache';
|
||||
|
||||
// TODO: add cache lifetime once we can use next.js 15 code here
|
||||
|
||||
const response = await fetchFilesystemUncached(url);
|
||||
const fetchFilesystemV2 = withCacheKey(async (cacheKey, url: string) => {
|
||||
const uncached = unstable_cache(
|
||||
async () => fetchFilesystemV2Uncached(cacheKey, url),
|
||||
[cacheKey],
|
||||
{
|
||||
revalidate: 60 * 60 * 24,
|
||||
}
|
||||
);
|
||||
|
||||
const response = await uncached();
|
||||
return response;
|
||||
});
|
||||
const fetchFilesystemV2Uncached = withoutConcurrentExecution(
|
||||
getCloudflareRequestGlobal,
|
||||
async function fetchFilesystemV2(url: string) {
|
||||
const response = await fetchFilesystemUncached(url);
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
||||
async function fetchFilesystemUncached(
|
||||
url: string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenAPIParseError } from '@gitbook/openapi-parser';
|
||||
import { type OpenAPISchemasData, resolveOpenAPISchemas } from '@gitbook/react-openapi';
|
||||
import { OpenAPIParseError, type OpenAPISchema } from '@gitbook/openapi-parser';
|
||||
import { resolveOpenAPISchemas } from '@gitbook/react-openapi';
|
||||
import { fetchOpenAPIFilesystem } from './fetch';
|
||||
import type {
|
||||
OpenAPISchemasBlock,
|
||||
@@ -7,7 +7,9 @@ import type {
|
||||
ResolveOpenAPIBlockResult,
|
||||
} from './types';
|
||||
|
||||
type ResolveOpenAPISchemasBlockResult = ResolveOpenAPIBlockResult<OpenAPISchemasData>;
|
||||
type ResolveOpenAPISchemasBlockResult = ResolveOpenAPIBlockResult<{
|
||||
schemas: OpenAPISchema[];
|
||||
}>;
|
||||
|
||||
const weakmap = new WeakMap<OpenAPISchemasBlock, Promise<ResolveOpenAPISchemasBlockResult>>();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ContentRef,
|
||||
RevisionFile,
|
||||
RevisionPageDocument,
|
||||
RevisionReusableContent,
|
||||
SiteSpace,
|
||||
Space,
|
||||
@@ -14,11 +15,12 @@ import type React from 'react';
|
||||
|
||||
import { PageIcon } from '@/components/PageIcon';
|
||||
|
||||
import { getGitBookAppHref } from './app';
|
||||
import { getBlockById, getBlockTitle } from './document';
|
||||
import { getGitbookAppHref } from './links';
|
||||
import { resolvePageId } from './pages';
|
||||
import { findSiteSpaceById } from './sites';
|
||||
import type { ClassValue } from './tailwind';
|
||||
import { filterOutNullable } from './typescript';
|
||||
|
||||
export interface ResolvedContentRef {
|
||||
/** Text to render in the content ref */
|
||||
@@ -29,12 +31,16 @@ export interface ResolvedContentRef {
|
||||
icon?: React.ReactNode;
|
||||
/** Emoji associated with the reference */
|
||||
emoji?: string;
|
||||
/** The content ref's ancestors */
|
||||
ancestors?: { icon?: React.ReactNode; label: string; href?: string }[];
|
||||
/** URL to open for the content ref */
|
||||
href: string;
|
||||
/** True if the content ref is active */
|
||||
active: boolean;
|
||||
/** File, if the reference is a file */
|
||||
file?: RevisionFile;
|
||||
/** Page document resolved from the content ref */
|
||||
page?: RevisionPageDocument;
|
||||
/** Resolved reusable content, if the ref points to reusable content on a revision. */
|
||||
reusableContent?: RevisionReusableContent;
|
||||
/** Resolve OpenAPI spec filesystem. */
|
||||
@@ -115,6 +121,14 @@ export async function resolveContentRef(
|
||||
: resolvePageId(pages, contentRef.page);
|
||||
|
||||
const page = resolvePageResult?.page;
|
||||
const ancestors =
|
||||
resolvePageResult?.ancestors.map((ancestor) => ({
|
||||
label: ancestor.title,
|
||||
icon: <PageIcon page={ancestor} style={iconStyle} />,
|
||||
href: resolveAsAbsoluteURL
|
||||
? linker.toAbsoluteURL(linker.toPathForPage({ page: ancestor, pages }))
|
||||
: linker.toPathForPage({ page: ancestor, pages }),
|
||||
})) ?? [];
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
@@ -125,10 +139,16 @@ export async function resolveContentRef(
|
||||
let text = '';
|
||||
let icon: React.ReactNode | undefined = undefined;
|
||||
let emoji: string | undefined = undefined;
|
||||
const href = linker.toPathForPage({ page, pages, anchor });
|
||||
|
||||
// Compute the text to display for the link
|
||||
if (anchor) {
|
||||
text = `#${anchor}`;
|
||||
ancestors.push({
|
||||
label: page.title,
|
||||
icon: <PageIcon page={page} style={iconStyle} />,
|
||||
href: resolveAsAbsoluteURL ? linker.toAbsoluteURL(href) : href,
|
||||
});
|
||||
|
||||
if (resolveAnchorText) {
|
||||
const document = await getPageDocument(dataFetcher, space, page);
|
||||
@@ -151,13 +171,14 @@ export async function resolveContentRef(
|
||||
icon = <PageIcon page={page} style={iconStyle} />;
|
||||
}
|
||||
|
||||
const href = linker.toPathForPage({ page, pages, anchor });
|
||||
|
||||
return {
|
||||
href: resolveAsAbsoluteURL ? linker.toAbsoluteURL(href) : href,
|
||||
text,
|
||||
subText: page.description,
|
||||
ancestors: ancestors,
|
||||
emoji,
|
||||
icon,
|
||||
page,
|
||||
active: !anchor && page.id === activePage?.id,
|
||||
};
|
||||
}
|
||||
@@ -173,7 +194,7 @@ export async function resolveContentRef(
|
||||
|
||||
if (!targetSpace) {
|
||||
return {
|
||||
href: getGitbookAppHref(`/s/${contentRef.space}`),
|
||||
href: getGitBookAppHref(`/s/${contentRef.space}`),
|
||||
text: 'space',
|
||||
active: false,
|
||||
};
|
||||
@@ -203,7 +224,7 @@ export async function resolveContentRef(
|
||||
|
||||
case 'collection': {
|
||||
return {
|
||||
href: getGitbookAppHref('/home'),
|
||||
href: getGitBookAppHref('/home'),
|
||||
text: 'collection',
|
||||
active: false,
|
||||
};
|
||||
@@ -221,7 +242,7 @@ export async function resolveContentRef(
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
href: getGitbookAppHref(`/s/${space.id}`),
|
||||
href: getGitBookAppHref(`/s/${space.id}`),
|
||||
text: reusableContent.title,
|
||||
active: false,
|
||||
reusableContent,
|
||||
@@ -346,6 +367,12 @@ async function resolveContentRefInSpace(
|
||||
|
||||
return {
|
||||
...resolved,
|
||||
subText: space.title,
|
||||
ancestors: [
|
||||
{
|
||||
label: space.title,
|
||||
href: baseURL.toString(),
|
||||
},
|
||||
...(resolved.ancestors ?? []),
|
||||
].filter(filterOutNullable),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ export function defaultCustomization(): api.SiteCustomizationSettings {
|
||||
internationalization: {
|
||||
locale: api.CustomizationLocale.En,
|
||||
},
|
||||
insights: {
|
||||
trackingCookie: true,
|
||||
},
|
||||
favicon: {},
|
||||
header: {
|
||||
preset: api.CustomizationHeaderPreset.Default,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createImageResizer } from '@v2/lib/images';
|
||||
import { createLinker } from '@v2/lib/links';
|
||||
|
||||
import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data';
|
||||
import { headers } from 'next/headers';
|
||||
import {
|
||||
type SiteContentPointer,
|
||||
type SpaceContentPointer,
|
||||
@@ -31,7 +32,8 @@ import {
|
||||
searchSiteContent,
|
||||
} from './api';
|
||||
import { getDynamicCustomizationSettings } from './customization';
|
||||
import { getBasePath, getHost, getSiteBasePath } from './links';
|
||||
import { withLeadingSlash, withTrailingSlash } from './paths';
|
||||
import { assertIsNotV2 } from './v2';
|
||||
|
||||
/*
|
||||
* Code that will be used until the migration to v2 is complete.
|
||||
@@ -270,6 +272,10 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
streamAIResponse() {
|
||||
throw new Error('Not implemented in v1');
|
||||
},
|
||||
};
|
||||
|
||||
return dataFetcher;
|
||||
@@ -325,3 +331,41 @@ export function getSitePointerFromContext(context: GitBookSiteContext): SiteCont
|
||||
siteShareKey: context.shareKey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base path for the current request.
|
||||
* The value will start and finish with /
|
||||
*/
|
||||
async function getBasePath(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const path = headersList.get('x-gitbook-basepath') ?? '/';
|
||||
|
||||
return withTrailingSlash(withLeadingSlash(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the site base path for the current request.
|
||||
* The value will start and finish with /
|
||||
*/
|
||||
async function getSiteBasePath(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const path = headersList.get('x-gitbook-site-basepath') ?? '/';
|
||||
|
||||
return withTrailingSlash(withLeadingSlash(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current host for the current request.
|
||||
*/
|
||||
async function getHost(): Promise<string> {
|
||||
assertIsNotV2();
|
||||
const headersList = await headers();
|
||||
const mode = headersList.get('x-gitbook-mode');
|
||||
if (mode === 'proxy') {
|
||||
return headersList.get('x-forwarded-host') ?? '';
|
||||
}
|
||||
|
||||
return headersList.get('x-gitbook-host') ?? headersList.get('host') ?? '';
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ImageResponse } from 'next/og';
|
||||
|
||||
import { getEmojiForCode } from '@/lib/emojis';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { getCacheTag } from '@gitbook/cache-tags';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { getResizedImageURL } from '@v2/lib/images';
|
||||
|
||||
@@ -73,6 +74,14 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
|
||||
{
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
headers: {
|
||||
'cache-tag': [
|
||||
getCacheTag({
|
||||
tag: 'site',
|
||||
site: context.site.id,
|
||||
}),
|
||||
].join(','),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import { type PageParams, fetchPageData } from '@/components/SitePage';
|
||||
import { getFontSourcesToPreload } from '@/fonts/custom';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
import { getCacheTag } from '@gitbook/cache-tags';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { getCloudflareContext } from '@v2/lib/data/cloudflare';
|
||||
import { getResizedImageURL } from '@v2/lib/images';
|
||||
|
||||
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
|
||||
@@ -169,8 +171,12 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
{String.fromCodePoint(Number.parseInt(`0x${customization.favicon.emoji}`))}
|
||||
</span>
|
||||
);
|
||||
const src = linker.toAbsoluteURL(
|
||||
linker.toPathInSpace(`~gitbook/icon?size=medium&theme=${customization.themes.default}`)
|
||||
const src = await readSelfImage(
|
||||
linker.toAbsoluteURL(
|
||||
linker.toPathInSpace(
|
||||
`~gitbook/icon?size=medium&theme=${customization.themes.default}`
|
||||
)
|
||||
)
|
||||
);
|
||||
return <img src={src} alt="Icon" width={40} height={40} tw="mr-4" />;
|
||||
})();
|
||||
@@ -192,7 +198,11 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
/>
|
||||
|
||||
{/* Grid */}
|
||||
<img tw="absolute inset-0 w-[100vw] h-[100vh]" src={gridAsset} alt="Grid" />
|
||||
<img
|
||||
tw="absolute inset-0 w-[100vw] h-[100vh]"
|
||||
src={await readStaticImage(gridAsset)}
|
||||
alt="Grid"
|
||||
/>
|
||||
|
||||
{/* Logo */}
|
||||
{customization.header.logo ? (
|
||||
@@ -228,6 +238,18 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
width: 1200,
|
||||
height: 630,
|
||||
fonts: fonts.length ? fonts : undefined,
|
||||
headers: {
|
||||
'cache-tag': [
|
||||
getCacheTag({
|
||||
tag: 'site',
|
||||
site: baseContext.site.id,
|
||||
}),
|
||||
getCacheTag({
|
||||
tag: 'space',
|
||||
space: baseContext.space.id,
|
||||
}),
|
||||
].join(','),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -285,3 +307,55 @@ async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
|
||||
weight,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a resource from the function itself.
|
||||
* To avoid error with worker to worker requests in the same zone, we use the `WORKER_SELF_REFERENCE` binding.
|
||||
*/
|
||||
async function fetchSelf(url: string) {
|
||||
const cloudflare = getCloudflareContext();
|
||||
if (cloudflare?.env.WORKER_SELF_REFERENCE) {
|
||||
return await cloudflare.env.WORKER_SELF_REFERENCE.fetch(url);
|
||||
}
|
||||
|
||||
return await fetch(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image from a response as a base64 encoded string.
|
||||
*/
|
||||
async function readImage(response: Response) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
throw new Error(`Invalid content type: ${contentType}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
||||
return `data:${contentType};base64,${base64}`;
|
||||
}
|
||||
|
||||
const staticImagesCache = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* Read a static image and cache it in memory.
|
||||
*/
|
||||
async function readStaticImage(url: string) {
|
||||
const cached = staticImagesCache.get(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const image = await readSelfImage(url);
|
||||
staticImagesCache.set(url, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image from GitBook itself.
|
||||
*/
|
||||
async function readSelfImage(url: string) {
|
||||
const response = await fetchSelf(url);
|
||||
const image = await readImage(response);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ const config: Config = {
|
||||
),
|
||||
},
|
||||
animation: {
|
||||
present: 'present .5s ease-out both',
|
||||
present: 'present 200ms cubic-bezier(0.25, 1, 0.5, 1) both',
|
||||
scaleIn: 'scaleIn 200ms ease',
|
||||
scaleOut: 'scaleOut 200ms ease',
|
||||
fadeIn: 'fadeIn 200ms ease forwards',
|
||||
@@ -330,7 +330,7 @@ const config: Config = {
|
||||
present: {
|
||||
from: {
|
||||
opacity: '0',
|
||||
transform: 'translateY(2rem) scale(0.9)',
|
||||
transform: 'translateY(1rem) scale(90%)',
|
||||
},
|
||||
to: {
|
||||
opacity: '1',
|
||||
|
||||
@@ -65,12 +65,12 @@ export interface OpenAPICustomOperationProperties {
|
||||
|
||||
/**
|
||||
* Stability of the operation.
|
||||
* @enum 'experimental' | 'alpha' | 'beta' | 'stable'
|
||||
* @enum 'experimental' | 'alpha' | 'beta'
|
||||
*/
|
||||
'x-stability'?: OpenAPIStability;
|
||||
}
|
||||
|
||||
export type OpenAPIStability = 'experimental' | 'alpha' | 'beta' | 'stable';
|
||||
export type OpenAPIStability = 'experimental' | 'alpha' | 'beta';
|
||||
|
||||
/**
|
||||
* Custom code samples that can be defined at the operation level.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @gitbook/react-openapi
|
||||
|
||||
## 1.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- da7b369: Fix missing headers in OpenAPIResponses
|
||||
- da485f5: Fix read-only in generateSchemaExample
|
||||
- 139a805: Fix OpenAPI enum display
|
||||
|
||||
## 1.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user