mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-22 10:33:22 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8723182795 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Implement retry logic for the DO cache to prevent when revalidating content.
|
||||
@@ -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,12 +19,6 @@ 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"
|
||||
@@ -55,13 +49,10 @@ runs:
|
||||
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
|
||||
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
|
||||
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
|
||||
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
|
||||
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
|
||||
@@ -72,7 +63,7 @@ runs:
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '3.112.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc
|
||||
command: ${{ fromJSON(inputs.deploy) == true && 'deploy' || 'versions upload' }} --config ./packages/gitbook-v2/wrangler.toml
|
||||
- name: Outputs
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -54,7 +54,6 @@ runs:
|
||||
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
|
||||
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
|
||||
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
|
||||
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
|
||||
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
|
||||
- name: Build Project Artifacts
|
||||
@@ -63,7 +62,6 @@ 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
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
deploy-v1-cloudflare:
|
||||
name: Deploy v1 to Cloudflare Pages
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
environment:
|
||||
name: ${{ github.ref == 'refs/heads/main' && '1c-production' || '1c-preview' }}
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
permissions:
|
||||
@@ -40,7 +40,6 @@ 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
|
||||
@@ -57,7 +56,7 @@ jobs:
|
||||
deploy-v2-vercel:
|
||||
name: Deploy v2 to Vercel (preview)
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
environment:
|
||||
name: 2v-preview
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
outputs:
|
||||
@@ -78,11 +77,11 @@ jobs:
|
||||
deploy-v2-cloudflare:
|
||||
name: Deploy v2 to Cloudflare Worker (preview)
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
environment:
|
||||
name: 2c-preview
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
outputs:
|
||||
deployment-url: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}
|
||||
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -96,19 +95,9 @@ 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: Extract Worker ID
|
||||
id: extract-worker-id
|
||||
if: ${{ !steps.deploy.outputs.deployment-url }}
|
||||
run: |
|
||||
if [[ "${{ steps.deploy.outputs.command-output }}" =~ Worker\ Version\ ID:\ ([0-9a-f]{8})-([0-9a-f-]+) ]]; then
|
||||
WORKER_ID_FIRST_PART="${BASH_REMATCH[1]}"
|
||||
echo "worker-url=https://${WORKER_ID_FIRST_PART}-gitbook-open-v2-preview.gitbook.workers.dev/" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Outputs
|
||||
run: |
|
||||
echo "URL: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}"
|
||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||
comment-deployments:
|
||||
runs-on: ubuntu-latest
|
||||
name: Comment Deployments (preview)
|
||||
@@ -134,14 +123,14 @@ jobs:
|
||||
body: |
|
||||
Summary of the deployments:
|
||||
|
||||
### Version 1
|
||||
### Version 1 (production)
|
||||
|
||||
| Version | URL | Status |
|
||||
| --- | --- | --- |
|
||||
| Latest commit | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}) | ${{ needs.deploy-v1-cloudflare.result == 'success' && '✅' || '❌' }} |
|
||||
| PR | [${{ needs.deploy-v1-cloudflare.outputs.deployment-alias-url }}](${{ needs.deploy-v1-cloudflare.outputs.deployment-alias-url }}) | ${{ needs.deploy-v1-cloudflare.result == 'success' && '✅' || '❌' }} |
|
||||
|
||||
### Version 2
|
||||
### Version 2 (experimental)
|
||||
|
||||
| Version | URL | Status |
|
||||
| --- | --- | --- |
|
||||
@@ -150,10 +139,10 @@ jobs:
|
||||
|
||||
### Test content
|
||||
|
||||
| Site | `v1` | `2v` | `2c` |
|
||||
| --- | --- | --- | --- |
|
||||
| GitBook | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/docs.gitbook.com) |
|
||||
| E2E | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) |
|
||||
| Site | v1 | v2 |
|
||||
| --- | --- | --- |
|
||||
| GitBook | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com) |
|
||||
| E2E | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) |
|
||||
edit-mode: replace
|
||||
visual-testing-v1:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -195,33 +184,11 @@ jobs:
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: 'v2-vercel'
|
||||
visual-testing-v2-cloudflare:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing v2 (Cloudflare)
|
||||
needs: deploy-v2-cloudflare
|
||||
timeout-minutes: 10
|
||||
if: startsWith(github.head_ref || github.ref_name, 'cloudflare/')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
- name: Run Playwright tests
|
||||
run: bun e2e
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: 'v2-cloudflare'
|
||||
visual-testing-customers-v1:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v1
|
||||
needs: deploy-v1-cloudflare
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 6
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -241,7 +208,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2
|
||||
needs: deploy-v2-vercel
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 6
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -258,28 +225,6 @@ jobs:
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: 'customers-v2'
|
||||
visual-testing-customers-v2-cloudflare:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2 (Cloudflare)
|
||||
needs: deploy-v2-cloudflare
|
||||
timeout-minutes: 8
|
||||
if: startsWith(github.head_ref || github.ref_name, 'cloudflare/')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
- name: Run Playwright tests
|
||||
run: bun e2e-customers
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: 'customers-v2'
|
||||
pagespeed-testing-v1:
|
||||
runs-on: ubuntu-latest
|
||||
name: PageSpeed Testing v1
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Deploy
|
||||
- name: Deploy staging
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-vercel
|
||||
with:
|
||||
@@ -48,8 +48,6 @@ 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
|
||||
- name: Deploy staging
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-vercel
|
||||
with:
|
||||
@@ -48,8 +48,6 @@ 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 }}"
|
||||
@@ -59,15 +59,15 @@ bun install
|
||||
4. Start your local development server.
|
||||
|
||||
```
|
||||
bun dev:v2
|
||||
bun dev
|
||||
```
|
||||
|
||||
5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`.
|
||||
|
||||
examples:
|
||||
|
||||
- http://localhost:3000/url/docs.gitbook.com
|
||||
- http://localhost:3000/url/open-source.gitbook.io/midjourney
|
||||
- http://localhost:3000/docs.gitbook.com
|
||||
- http://localhost:3000/open-source.gitbook.io/midjourney
|
||||
|
||||
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"turbo": "^2.5.0",
|
||||
"turbo": "^2.4.4",
|
||||
"vercel": "^39.3.0",
|
||||
},
|
||||
},
|
||||
@@ -24,9 +24,9 @@
|
||||
},
|
||||
"packages/cache-tags": {
|
||||
"name": "@gitbook/cache-tags",
|
||||
"version": "0.3.1",
|
||||
"version": "0.2.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.109.0",
|
||||
"@gitbook/api": "*",
|
||||
"assert-never": "^1.2.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"packages/colors": {
|
||||
"name": "@gitbook/colors",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
@@ -49,7 +49,7 @@
|
||||
},
|
||||
"packages/gitbook": {
|
||||
"name": "gitbook",
|
||||
"version": "0.10.1",
|
||||
"version": "0.9.2",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "*",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
@@ -68,6 +68,7 @@
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vercel/og": "0.6.8.",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "^1.2.1",
|
||||
"bun-types": "^1.1.20",
|
||||
@@ -110,7 +111,7 @@
|
||||
"zustand": "^5.0.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@argos-ci/playwright": "^5.0.3",
|
||||
"@argos-ci/playwright": "^4.3.0",
|
||||
"@cloudflare/next-on-pages": "1.13.7",
|
||||
"@cloudflare/workers-types": "^4.20241230.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
@@ -140,14 +141,14 @@
|
||||
},
|
||||
"packages/gitbook-v2": {
|
||||
"name": "gitbook-v2",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.3",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "*",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"assert-never": "^1.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"next": "canary",
|
||||
"p-memoize": "^7.1.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"rison": "^0.1.1",
|
||||
@@ -155,7 +156,7 @@
|
||||
"warn-once": "^0.1.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@8f7ed38",
|
||||
"@opennextjs/cloudflare": "^0.5.10",
|
||||
"@types/rison": "^0.0.9",
|
||||
"gitbook": "*",
|
||||
"postcss": "^8",
|
||||
@@ -166,7 +167,7 @@
|
||||
"name": "@gitbook/icons",
|
||||
"version": "0.2.0",
|
||||
"bin": {
|
||||
"gitbook-icons": "./bin/gitbook-icons.js",
|
||||
"gitbook-icons": "./bin/gitbook-icons.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
@@ -184,7 +185,7 @@
|
||||
},
|
||||
"packages/openapi-parser": {
|
||||
"name": "@gitbook/openapi-parser",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.2",
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.10",
|
||||
"@scalar/openapi-types": "^0.1.9",
|
||||
@@ -197,6 +198,13 @@
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
},
|
||||
"packages/proxy": {
|
||||
"name": "@gitbook/proxy",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
},
|
||||
"packages/react-contentkit": {
|
||||
"name": "@gitbook/react-contentkit",
|
||||
"version": "0.7.0",
|
||||
@@ -216,7 +224,7 @@
|
||||
"name": "@gitbook/react-math",
|
||||
"version": "0.6.0",
|
||||
"bin": {
|
||||
"gitbook-math": "./bin/gitbook-math.js",
|
||||
"gitbook-math": "./bin/gitbook-math.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -231,7 +239,7 @@
|
||||
},
|
||||
"packages/react-openapi": {
|
||||
"name": "@gitbook/react-openapi",
|
||||
"version": "1.1.10",
|
||||
"version": "1.1.9",
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "^1.2.5",
|
||||
@@ -259,7 +267,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@gitbook/api": "0.109.0",
|
||||
"@gitbook/api": "0.108.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
},
|
||||
@@ -274,35 +282,35 @@
|
||||
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@argos-ci/api-client": ["@argos-ci/api-client@0.8.1", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.5" } }, "sha512-3IHv7ANSPNO6OwWgwULlHbP9/tFV9kQDu6+nL9jysfPkGj0GgtrOsyBb+iU931c7wSMo1OD+XNujCnRzDD968w=="],
|
||||
"@argos-ci/api-client": ["@argos-ci/api-client@0.8.0", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.4" } }, "sha512-UHa1vAf8gwHVpkqM/RaSryrFe1juqWH6dHpPeMtT4e/ZMB9hNYwYFinaGq/KRWe88JEi2WeAu776YdoeUSZQkQ=="],
|
||||
|
||||
"@argos-ci/browser": ["@argos-ci/browser@4.1.1", "", {}, "sha512-UyKdnprGftUjWQkb0jqJ0zGHJmcWBzdko8zRy4y+4efukVX4jjC/Px2HvWW8aqwjoR4aplouMZuzhmOkq2SCsA=="],
|
||||
"@argos-ci/browser": ["@argos-ci/browser@3.2.0", "", {}, "sha512-mHLUamfywbdzrM6SRV4KB4kEQs2lrFxC1yS+QWB/9lME7FKzr7MJW/Q++iWcQli8IbmtLsZ+PqP2eqLaDiRsUw=="],
|
||||
|
||||
"@argos-ci/core": ["@argos-ci/core@3.1.1", "", { "dependencies": { "@argos-ci/api-client": "0.8.1", "@argos-ci/util": "2.3.1", "axios": "^1.8.4", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-7iE3o1XGxlfHC5AF05pzT0OxuO387sryrZt3gKGj/e+6R20DXz7J49yI8++nQ2cuT+wLhcJp8+X0ox+SGMYHmw=="],
|
||||
"@argos-ci/core": ["@argos-ci/core@3.1.0", "", { "dependencies": { "@argos-ci/api-client": "0.8.0", "@argos-ci/util": "2.3.0", "axios": "^1.7.9", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-bo/pNKk6P0pz4NRdymgU1letwQrRbMPTeFyMsUEW8fhKNdesSFnFIWZBFGsGkkh05uw75PBjl2ZN4PvQ2TxSog=="],
|
||||
|
||||
"@argos-ci/playwright": ["@argos-ci/playwright@5.0.3", "", { "dependencies": { "@argos-ci/browser": "4.1.1", "@argos-ci/core": "3.1.1", "@argos-ci/util": "2.3.1", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-sqoARsgnDRrwKm1x10L3Z8+OQukk0F9OksVj9v9rvbzNI2WVCw5zbCUMY0qD4Q3Ba7vMFbl1ELUODRc2mfCbNw=="],
|
||||
"@argos-ci/playwright": ["@argos-ci/playwright@4.3.0", "", { "dependencies": { "@argos-ci/browser": "3.2.0", "@argos-ci/core": "3.1.0", "@argos-ci/util": "2.3.0", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-UQBC78zWg+bWXDaevWOL4m6zkrNeprMfdqjy6zagvU9kq7sPQC7nN/qZQ15hs/FT+JpTCa8CbbSAq13eQrmxKQ=="],
|
||||
|
||||
"@argos-ci/util": ["@argos-ci/util@2.3.1", "", {}, "sha512-kE61HU2480fbAnimmA4x9HK45ZJvkoqLdW5GxT5uvwhkclQykVd2S6WfGFUr3JokTXfZ5LZEEfoWgtGA316KSQ=="],
|
||||
"@argos-ci/util": ["@argos-ci/util@2.3.0", "", {}, "sha512-tkxnCpaj7yN9nCFzo9MX0FJ5YjUepEOGYfdvF8COQqp+EdY1qubOPpc4Z0l1B60BlC8YtjQv/oRxHSh1XzxWFg=="],
|
||||
|
||||
"@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": ["@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-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.35.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-T+MN4Oinc+sXjXCIHzfxDDWY7r2pKgPxM6zVeVlkMTrJV2mJtyKYBIS+CABhRM6kflps2T2I6l4DGaKV/8Ym9w=="],
|
||||
"@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-x64": ["@ast-grep/napi-darwin-x64@0.35.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pEYiN6JI1HY2uWhMYJ9+3yIMyVYKuYdFzeD+dL7odA3qzK0o9N9AM3/NOt4ynU2EhufaWCJr0P5NoQ636qN6MQ=="],
|
||||
"@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-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NBuzQngABGKz7lhG08IQb+7nPqUx81Ol37xmS3ZhVSdSgM0mtp93rCbgFTkJcAFE8IMfCHQSg7G4g0Iotz4ABQ=="],
|
||||
"@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-musl": ["@ast-grep/napi-linux-arm64-musl@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EcvHPwyWpCL/96LuItBYGfeI5FaMTRvL+dHbO/hL5q1npqbb5qn+ppJwtNOjTPz8tayvgggxVk9T4C2O7taYA=="],
|
||||
"@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-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FDzNdlqmQnsiWXhnLxusw5AOfEcEM+5xtmrnAf3SBRFr86JyWD9qsynnFYC2pnP9hlMfifNH2TTmMpyGJW49Xw=="],
|
||||
"@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-musl": ["@ast-grep/napi-linux-x64-musl@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wlmndjfBafT8u5p4DBnoRQyoCSGNuVSz7rT3TqhvlHcPzUouRWMn95epU9B1LNLyjXvr9xHeRjSktyCN28w57Q=="],
|
||||
"@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-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.35.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-gkhJeYc4rrZLX2icLxalPikTLMR57DuIYLwLr9g+StHYXIsGHrbfrE6Nnbdd8Izfs34ArFCrcwdaMrGlvOPSeg=="],
|
||||
"@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-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.35.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OdUuRa3chHCZ65y+qALfkUjz0W0Eg21YZ9TyPquV5why07M6HAK38mmYGzLxFH6294SvRQhs+FA/rAfbKeH0jA=="],
|
||||
"@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-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.35.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pcQRUHqbroTN1oQ56V982a7IZTUUySQYWa2KEyksiifHGuBuitlzcyzFGjT96ThcqD9XW0UVJMvpoF2Qjh006Q=="],
|
||||
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.34.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OY1Cswkz+0bRZgt9sLjzgyA+y0X164UFjwf5WTYpbCTPUlwdpJP6L7FqJNRMemzQp0qQwwRR7ejpBUF4o/V0Aw=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -620,7 +628,7 @@
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
|
||||
|
||||
"@gitbook/api": ["@gitbook/api@0.109.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-nzhxmiNR2MMHh+HoU7fFdOJ+u4BFyXjtsVquQf3VhZMjeI71yeLx5JEbjkOcijVd/GMAtvpYlYR2qcPqzq27gw=="],
|
||||
"@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"],
|
||||
|
||||
@@ -636,6 +644,8 @@
|
||||
|
||||
"@gitbook/openapi-parser": ["@gitbook/openapi-parser@workspace:packages/openapi-parser"],
|
||||
|
||||
"@gitbook/proxy": ["@gitbook/proxy@workspace:packages/proxy"],
|
||||
|
||||
"@gitbook/react-contentkit": ["@gitbook/react-contentkit@workspace:packages/react-contentkit"],
|
||||
|
||||
"@gitbook/react-math": ["@gitbook/react-math@workspace:packages/react-math"],
|
||||
@@ -668,8 +678,6 @@
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.1.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
|
||||
@@ -786,9 +794,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@3.5.6", "", { "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-hYI3uYLVEK15zoxjlGKLSlzOrSf1GvnFWARCmS2gllL6aYaDspw2HIJ2OP+RS3wn+Npqs9iKVV2Dwiejdt5vxQ=="],
|
||||
"@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/cloudflare": ["@opennextjs/cloudflare@https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@8f7ed38", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.5.6", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^3.114.3 || ^4.7.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }],
|
||||
"@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=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
@@ -1066,6 +1074,8 @@
|
||||
|
||||
"@replit/codemirror-css-color-picker": ["@replit/codemirror-css-color-picker@6.3.0", "", { "peerDependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A=="],
|
||||
|
||||
"@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.4.0", "", {}, "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
|
||||
|
||||
"@scalar/api-client": ["@scalar/api-client@2.3.5", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.37", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.2", "@scalar/oas-utils": "0.2.120", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.10", "@scalar/openapi-types": "0.1.9", "@scalar/postman-to-openapi": "0.1.43", "@scalar/snippetz": "0.2.16", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "@scalar/use-codemirror": "0.11.82", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-bVBP8H3laa4VG3dExv4Ak/Kf+q9z8aK/Glpv6CPE0wxgZJWxwkJoSrvwxUE+46JktSObUrhu4xQiqSCmBz8esQ=="],
|
||||
@@ -1120,6 +1130,8 @@
|
||||
|
||||
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
||||
|
||||
"@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.25.24", "", {}, "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ=="],
|
||||
|
||||
"@sindresorhus/fnv1a": ["@sindresorhus/fnv1a@3.1.0", "", {}, "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w=="],
|
||||
@@ -1334,6 +1346,8 @@
|
||||
|
||||
"@vercel/node": ["@vercel/node@5.0.2", "", { "dependencies": { "@edge-runtime/node-utils": "2.3.0", "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "16.18.11", "@vercel/build-utils": "9.0.1", "@vercel/error-utils": "2.0.3", "@vercel/nft": "0.27.10", "@vercel/static-config": "3.0.0", "async-listen": "3.0.0", "cjs-module-lexer": "1.2.3", "edge-runtime": "2.5.9", "es-module-lexer": "1.4.1", "esbuild": "0.14.47", "etag": "1.8.1", "node-fetch": "2.6.9", "path-to-regexp": "6.2.1", "ts-morph": "12.0.0", "ts-node": "10.9.1", "typescript": "4.9.5", "undici": "5.28.4" } }, "sha512-UcUVBC6i4j3WPxLA5GYnSvRd/E1fpqJ5dnMZMLromGCTzIXaw+Uzj7bsSSQ2Y9yPtwnWWrcEDmF3IumSTcdr/w=="],
|
||||
|
||||
"@vercel/og": ["@vercel/og@0.6.8", "", { "dependencies": { "@resvg/resvg-wasm": "2.4.0", "satori": "0.12.2", "yoga-wasm-web": "0.3.3" } }, "sha512-e4kQK9mP8ntpo3dACWirGod/hHv4qO5JMj9a/0a2AZto7b4persj5YP7t1Er372gTtYFTYxNhMx34jRvHooglw=="],
|
||||
|
||||
"@vercel/python": ["@vercel/python@4.7.0", "", {}, "sha512-mkHmzYYZBLFLdvSdgrnBl1Qc1+LI5YIafSNJOj8oW4YU8vvALLMbwgZp42pZnyXW0e/3uHcesiRp4P0jSB0wyg=="],
|
||||
|
||||
"@vercel/redwood": ["@vercel/redwood@2.1.12", "", { "dependencies": { "@vercel/nft": "0.27.10", "@vercel/routing-utils": "5.0.0", "@vercel/static-config": "3.0.0", "semver": "6.3.1", "ts-morph": "12.0.0" } }, "sha512-9CLwF8QKmxWlt1CMxoVD5gxTfwnojo1UlFONRSoUfjpNC+nTHEMCADVyawmDkANXs7Olx4QQSwJqsaWT8A9Jgg=="],
|
||||
@@ -1442,7 +1456,7 @@
|
||||
|
||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||
|
||||
"axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="],
|
||||
"axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
@@ -1500,6 +1514,8 @@
|
||||
|
||||
"camelcase-keys": ["camelcase-keys@6.2.2", "", { "dependencies": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" } }, "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg=="],
|
||||
|
||||
"camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001668", "", {}, "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw=="],
|
||||
|
||||
"capnp-ts": ["capnp-ts@0.7.0", "", { "dependencies": { "debug": "^4.3.1", "tslib": "^2.2.0" } }, "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g=="],
|
||||
@@ -1584,8 +1600,18 @@
|
||||
|
||||
"crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="],
|
||||
|
||||
"css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="],
|
||||
|
||||
"css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="],
|
||||
|
||||
"css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="],
|
||||
|
||||
"css-functions-list": ["css-functions-list@3.2.3", "", {}, "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA=="],
|
||||
|
||||
"css-gradient-parser": ["css-gradient-parser@0.0.16", "", {}, "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA=="],
|
||||
|
||||
"css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="],
|
||||
|
||||
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
@@ -1802,6 +1828,8 @@
|
||||
|
||||
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
|
||||
|
||||
"fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@10.0.7", "", { "dependencies": { "flat-cache": "^6.1.7" } }, "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw=="],
|
||||
|
||||
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
||||
@@ -1946,6 +1974,8 @@
|
||||
|
||||
"hastscript": ["hastscript@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw=="],
|
||||
|
||||
"hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="],
|
||||
|
||||
"highlight.js": ["highlight.js@11.10.0", "", {}, "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ=="],
|
||||
|
||||
"highlightjs-curl": ["highlightjs-curl@1.3.0", "", {}, "sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A=="],
|
||||
@@ -2110,6 +2140,8 @@
|
||||
|
||||
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
||||
|
||||
"linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
@@ -2276,7 +2308,7 @@
|
||||
|
||||
"mime-types": ["mime-types@3.0.0", "", { "dependencies": { "mime-db": "^1.53.0" } }, "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||
|
||||
"mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
|
||||
|
||||
@@ -2364,7 +2396,7 @@
|
||||
|
||||
"oniguruma-to-es": ["oniguruma-to-es@4.1.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "oniguruma-parser": "^0.5.4", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA=="],
|
||||
|
||||
"openapi-fetch": ["openapi-fetch@0.13.5", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.15" } }, "sha512-AQK8T9GSKFREFlN1DBXTYsLjs7YV2tZcJ7zUWxbjMoQmj8dDSFRrzhLCbHPZWA1TMV3vACqfCxLEZcwf2wxV6Q=="],
|
||||
"openapi-fetch": ["openapi-fetch@0.13.4", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.15" } }, "sha512-JHX7UYjLEiHuQGCPxa3CCCIqe/nc4bTIF9c4UYVC8BegAbWoS3g4gJxKX5XcG7UtYQs2060kY6DH64KkvNZahg=="],
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
@@ -2388,6 +2420,8 @@
|
||||
|
||||
"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=="],
|
||||
@@ -2398,10 +2432,14 @@
|
||||
|
||||
"package-manager-manager": ["package-manager-manager@0.2.0", "", { "dependencies": { "js-yaml": "^4.1.0", "shellac": "^0.8.0" } }, "sha512-V02gl0bafXJ2gcY6j+5IHM7UdnYwmF+2OsFZuqVcha6iMSStD4dpIOBOsypnUIwOi4jLcPz6RQuyifmAE3mG8g=="],
|
||||
|
||||
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-cache-control": ["parse-cache-control@1.0.1", "", {}, "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="],
|
||||
|
||||
"parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"parse-ms": ["parse-ms@2.1.0", "", {}, "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="],
|
||||
@@ -2602,6 +2640,8 @@
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"satori": ["satori@0.12.2", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.16", "css-to-react-native": "^3.0.0", "emoji-regex": "^10.2.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-wasm-web": "^0.3.3" } }, "sha512-3C/laIeE6UUe9A+iQ0A48ywPVCCMKCNSTU5Os101Vhgsjd3AAxGNjyq0uAA8kulMPK5n0csn8JlxPN9riXEjLA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
|
||||
@@ -2684,6 +2724,8 @@
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
|
||||
@@ -2748,6 +2790,8 @@
|
||||
|
||||
"timers-ext": ["timers-ext@0.1.8", "", { "dependencies": { "es5-ext": "^0.10.64", "next-tick": "^1.1.0" } }, "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww=="],
|
||||
|
||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||
|
||||
"tippy.js": ["tippy.js@6.3.7", "", { "dependencies": { "@popperjs/core": "^2.9.0" } }, "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ=="],
|
||||
|
||||
"tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
|
||||
@@ -2782,27 +2826,25 @@
|
||||
|
||||
"ts-toolbelt": ["ts-toolbelt@6.15.5", "", {}, "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A=="],
|
||||
|
||||
"ts-tqdm": ["ts-tqdm@0.8.6", "", {}, "sha512-3X3M1PZcHtgQbnwizL+xU8CAgbYbeLHrrDwL9xxcZZrV5J+e7loJm1XrXozHjSkl44J0Zg0SgA8rXbh83kCkcQ=="],
|
||||
|
||||
"tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="],
|
||||
|
||||
"turbo": ["turbo@2.5.0", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.0", "turbo-darwin-arm64": "2.5.0", "turbo-linux-64": "2.5.0", "turbo-linux-arm64": "2.5.0", "turbo-windows-64": "2.5.0", "turbo-windows-arm64": "2.5.0" }, "bin": { "turbo": "bin/turbo" } }, "sha512-PvSRruOsitjy6qdqwIIyolv99+fEn57gP6gn4zhsHTEcCYgXPhv6BAxzAjleS8XKpo+Y582vTTA9nuqYDmbRuA=="],
|
||||
"turbo": ["turbo@2.4.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.4.4", "turbo-darwin-arm64": "2.4.4", "turbo-linux-64": "2.4.4", "turbo-linux-arm64": "2.4.4", "turbo-windows-64": "2.4.4", "turbo-windows-arm64": "2.4.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-N9FDOVaY3yz0YCOhYIgOGYad7+m2ptvinXygw27WPLQvcZDl3+0Sa77KGVlLSiuPDChOUEnTKE9VJwLSi9BPGQ=="],
|
||||
|
||||
"turbo-darwin-64": ["turbo-darwin-64@2.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fP1hhI9zY8hv0idym3hAaXdPi80TLovmGmgZFocVAykFtOxF+GlfIgM/l4iLAV9ObIO4SUXPVWHeBZQQ+Hpjag=="],
|
||||
"turbo-darwin-64": ["turbo-darwin-64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-5kPvRkLAfmWI0MH96D+/THnDMGXlFNmjeqNRj5grLKiry+M9pKj3pRuScddAXPdlxjO5Ptz06UNaOQrrYGTx1g=="],
|
||||
|
||||
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p9sYq7kXH7qeJwIQE86cOWv/xNqvow846l6c/qWc26Ib1ci5W7V0sI5thsrP3eH+VA0d+SHalTKg5SQXgNQBWA=="],
|
||||
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/gtHPqbGQXDFhrmy+Q/MFW2HUTUlThJ97WLLSe4bxkDrKHecDYhAjbZ4rN3MM93RV9STQb3Tqy4pZBtsd4DfCw=="],
|
||||
|
||||
"turbo-linux-64": ["turbo-linux-64@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1iEln2GWiF3iPPPS1HQJT6ZCFXynJPd89gs9SkggH2EJsj3eRUSVMmMC8y6d7bBbhBFsiGGazwFIYrI12zs6uQ=="],
|
||||
"turbo-linux-64": ["turbo-linux-64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-SR0gri4k0bda56hw5u9VgDXLKb1Q+jrw4lM7WAhnNdXvVoep4d6LmnzgMHQQR12Wxl3KyWPbkz9d1whL6NTm2Q=="],
|
||||
|
||||
"turbo-linux-arm64": ["turbo-linux-arm64@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-bKBcbvuQHmsX116KcxHJuAcppiiBOfivOObh2O5aXNER6mce7YDDQJy00xQQNp1DhEfcSV2uOsvb3O3nN2cbcA=="],
|
||||
"turbo-linux-arm64": ["turbo-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-COXXwzRd3vslQIfJhXUklgEqlwq35uFUZ7hnN+AUyXx7hUOLIiD5NblL+ETrHnhY4TzWszrbwUMfe2BYWtaPQg=="],
|
||||
|
||||
"turbo-windows-64": ["turbo-windows-64@2.5.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9BCo8oQ7BO7J0K913Czbc3tw8QwLqn2nTe4E47k6aVYkM12ASTScweXPTuaPFP5iYXAT6z5Dsniw704Ixa5eGg=="],
|
||||
"turbo-windows-64": ["turbo-windows-64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-PV9rYNouGz4Ff3fd6sIfQy5L7HT9a4fcZoEv8PKRavU9O75G7PoDtm8scpHU10QnK0QQNLbE9qNxOAeRvF0fJg=="],
|
||||
|
||||
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-OUHCV+ueXa3UzfZ4co/ueIHgeq9B2K48pZwIxKSm5VaLVuv8M13MhM7unukW09g++dpdrrE1w4IOVgxKZ0/exg=="],
|
||||
"turbo-windows-arm64": ["turbo-windows-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-403sqp9t5sx6YGEC32IfZTVWkRAixOQomGYB8kEc6ZD+//LirSxzeCHCnM8EmSXw7l57U1G+Fb0kxgTcKPU/Lg=="],
|
||||
|
||||
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
||||
|
||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
"type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -2820,6 +2862,8 @@
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.1", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg=="],
|
||||
|
||||
"unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="],
|
||||
@@ -2942,6 +2986,8 @@
|
||||
|
||||
"yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
|
||||
|
||||
"yoga-wasm-web": ["yoga-wasm-web@0.3.3", "", {}, "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="],
|
||||
|
||||
"youch": ["youch@3.2.3", "", { "dependencies": { "cookie": "^0.5.0", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw=="],
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
@@ -4018,7 +4064,7 @@
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"gitbook-v2/next": ["next@15.3.1-canary.7", "", { "dependencies": { "@next/env": "15.3.1-canary.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.7", "@next/swc-darwin-x64": "15.3.1-canary.7", "@next/swc-linux-arm64-gnu": "15.3.1-canary.7", "@next/swc-linux-arm64-musl": "15.3.1-canary.7", "@next/swc-linux-x64-gnu": "15.3.1-canary.7", "@next/swc-linux-x64-musl": "15.3.1-canary.7", "@next/swc-win32-arm64-msvc": "15.3.1-canary.7", "@next/swc-win32-x64-msvc": "15.3.1-canary.7", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RblvC7A7k8cKpSa6JSCbaatb6JAncv2niUSH7TqXt6xHUoItnxa3Fu7xkducFJQdcW2Hv2h0+NBu6kaKf0S4zw=="],
|
||||
"gitbook-v2/next": ["next@15.3.0-canary.38", "", { "dependencies": { "@next/env": "15.3.0-canary.38", "@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.38", "@next/swc-darwin-x64": "15.3.0-canary.38", "@next/swc-linux-arm64-gnu": "15.3.0-canary.38", "@next/swc-linux-arm64-musl": "15.3.0-canary.38", "@next/swc-linux-x64-gnu": "15.3.0-canary.38", "@next/swc-linux-x64-musl": "15.3.0-canary.38", "@next/swc-win32-arm64-msvc": "15.3.0-canary.38", "@next/swc-win32-x64-msvc": "15.3.0-canary.38", "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-oSRXCgoVuQNkMKCbAhmbAopXIbcd3TEmcxvT8pVX5WhK7fJRSugCEem4PJ85KCeMl/76nyvqjG4g6KjtNRcB8w=="],
|
||||
|
||||
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
|
||||
|
||||
@@ -4050,6 +4096,8 @@
|
||||
|
||||
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||
|
||||
"linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
|
||||
|
||||
"lowlight/highlight.js": ["highlight.js@11.9.0", "", {}, "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw=="],
|
||||
|
||||
"lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||
@@ -4068,6 +4116,8 @@
|
||||
|
||||
"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=="],
|
||||
@@ -4096,6 +4146,8 @@
|
||||
|
||||
"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=="],
|
||||
@@ -4154,6 +4206,8 @@
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||
|
||||
"satori/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||
|
||||
"semver-diff/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"send/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
@@ -4886,28 +4940,26 @@
|
||||
|
||||
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.7", "", {}, "sha512-QSzAlzxaGrQYj3Nzgxd35WM6pmxmNk9lOCn0nsNLPidvc3H6wj/fV3Pl8/tVCYQP61luGuA0Ib0VwZc8ja119w=="],
|
||||
"gitbook-v2/next/@next/env": ["@next/env@15.3.0-canary.38", "", {}, "sha512-RXi1aJKCOiuc5OD9JmRgeNGo4vpFU0Cid0C9X7rFR65HQBpaUx+NuCib2pz5KLqx/a2R4n1EThyxWLUUKYWvIQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fs7JYU0MKtpgsDBIvbT/wn1MwKP8C+bqcb6IjWnDtVcciFKzYDs/tPCBW6CtYg1wpHqm9horZDA1ytb/sAghxA=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.0-canary.38", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/RxCcNPZaNayi24Mm8Bad/p1vbncvahNcP3y9bjtMaGG0ldUKWRH/AXfWaBCKPe0t2tp2AR4g7iTArwn8fl8xg=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Efl6es/IEXJf5groF78fc1PrTEwK9bOyi6fRjp1bsa/AWOHp8rTc3l0T6EeY5T0SWeYilisZZa9rax7nTgw4bw=="],
|
||||
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.0-canary.38", "", { "os": "darwin", "cpu": "x64" }, "sha512-WAKsNcW4QHvPOGNk2fJulEkx390E6mcHgeV/ATiCWIeQZU5lVsuJEC1aUz1OGv/vEh0FzfCURdL6oavEZSjWOA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-IS71I8z2wlecRZRHXqWTtxVozZt1h9IH/2MX1P/WkCjQH98acFmCkw0jo/y70mrZ4lw6tRGTNkSBMun+5P5P+w=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.0-canary.38", "", { "os": "linux", "cpu": "arm64" }, "sha512-SRktmhD+YFpjVYpfc8DKkQN9CaGx/SJkx4Ls2mzcvaf1IKgLDhNBFOOepO1tzgjxJRmJaXm3TdgomNwEX/Tr8g=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-uJ4env9fZxIFM+aRBCjNeG91HHZ2cRXiF25xJqGmCC1Y5jT5oYAB/tIBCFRSj6NRzdt4pZAamCMRS41WzzVVUQ=="],
|
||||
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.0-canary.38", "", { "os": "linux", "cpu": "arm64" }, "sha512-pBCa7pB/2gTbJ4l1yC9WWTuScGD7Gu+6frxXnb3pSE3S0cJDTOZCdWktyvdmTq6tR6OABZXCNty1GNDVr7lduQ=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-/+efTL2TxoSAYCxdf+g6WvDKIJDoaETvF3I+lxpixhctfrIwpr2qnG0e6u2M55k1iYiiyFIOA63fpIsGnIMJZA=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.0-canary.38", "", { "os": "linux", "cpu": "x64" }, "sha512-2TyYiVuMzoM0OAGNygagfVuQk9LnGm9ip/vDELpSSgJ1mxFIGg1M7g/zDBmmzmv4MGyACEiunGwdOsVI2tHyQA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Hb+agfULOjEhJAmq/r9OTVAo8KxFlnuCT7BEUbSuVP/Gxn883al6juSG7ZZfk7P4a+ZU6iVYyHKIFAKCarZmUA=="],
|
||||
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.0-canary.38", "", { "os": "linux", "cpu": "x64" }, "sha512-ibeAJmyFHqHioq1DAiIh+OAiyu+iOFLs0x/jXIdiTPizGgommoh0QytRezC/AFEip5qlx2mPdgYVfTlk6FL+GA=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-gNd5Vq7k9xIEQj85JoavHGRsyvu1zKOjVoPUYtsZRxXw6kcBg84x2g8NeT/3SmwH1PiFU/sjQXtRzr2Kuc2j3A=="],
|
||||
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.0-canary.38", "", { "os": "win32", "cpu": "arm64" }, "sha512-e1rF3XvjTAdWK6PRBwuTS2P6fsEbTuBLU5BRtbdtvwzuizskNEzz9XTd+h0/UxowoyvEjaLi9UdIHRsj0+bbTg=="],
|
||||
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5i+KinvNNmspXfgIvEZ1RFOKVhOgdtoSC7/xEUm55hEkcWFVHOtVVPvgV7WUeo1elNxbgxytzmyKbLaifP03g=="],
|
||||
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.0-canary.38", "", { "os": "win32", "cpu": "x64" }, "sha512-3H3IQhQG7KoMd7kTem0BsV0R8tepPcrXD6oe+ZJUXZbv1N3laeRExeqsCxdWjeMb26LWcVARBAJqu8A8v+iTzg=="],
|
||||
|
||||
"gitbook-v2/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"gitbook-v2/next/sharp": ["sharp@0.34.1", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.7.1" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.1", "@img/sharp-darwin-x64": "0.34.1", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.1", "@img/sharp-linux-arm64": "0.34.1", "@img/sharp-linux-s390x": "0.34.1", "@img/sharp-linux-x64": "0.34.1", "@img/sharp-linuxmusl-arm64": "0.34.1", "@img/sharp-linuxmusl-x64": "0.34.1", "@img/sharp-wasm32": "0.34.1", "@img/sharp-win32-ia32": "0.34.1", "@img/sharp-win32-x64": "0.34.1" } }, "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg=="],
|
||||
|
||||
"gitbook-v2/next/styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
@@ -5124,46 +5176,6 @@
|
||||
|
||||
"gitbook-v2/next/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.1.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.1.0" }, "os": "linux", "cpu": "arm" }, "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.1.0" }, "os": "linux", "cpu": "s390x" }, "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.1", "", { "dependencies": { "@emnapi/runtime": "^1.4.0" }, "cpu": "none" }, "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.1", "", { "os": "win32", "cpu": "x64" }, "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw=="],
|
||||
|
||||
"gitbook-v2/next/sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||
|
||||
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
@@ -5206,8 +5218,6 @@
|
||||
|
||||
"@vercel/nft/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.4.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw=="],
|
||||
|
||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
|
||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
@@ -5215,7 +5225,5 @@
|
||||
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
|
||||
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
|
||||
"gitbook-v2/next/sharp/@img/sharp-wasm32/@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"turbo": "^2.5.0",
|
||||
"turbo": "^2.4.4",
|
||||
"vercel": "^39.3.0"
|
||||
},
|
||||
"packageManager": "bun@1.2.8",
|
||||
@@ -12,7 +12,7 @@
|
||||
"@codemirror/state": "6.4.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"@gitbook/api": "0.109.0"
|
||||
"@gitbook/api": "0.108.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"generate": {
|
||||
"outputs": ["worker-configuration.d.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,5 @@
|
||||
# @gitbook/cache-tags
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 77397ca: Fix version of @gitbook/api referenced in package.json
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 116575c: Improve typing of getComputedContentSourceCacheTags to match latest API specification
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "0.3.1",
|
||||
"version": "0.2.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.109.0",
|
||||
"@gitbook/api": "*",
|
||||
"assert-never": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @gitbook/colors
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cdffd7c: Desaturate text colors by decreasing chroma for the last steps of the color scale
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
# gitbook-v2
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [77397ca]
|
||||
- @gitbook/cache-tags@0.3.1
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4234289: Fix incoming URL for requests that were proxied
|
||||
- Updated dependencies [116575c]
|
||||
- @gitbook/cache-tags@0.3.0
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -7,6 +7,8 @@ const nextConfig = {
|
||||
experimental: {
|
||||
// This is needed to throw "forbidden" when the api token expired during revalidation
|
||||
authInterrupts: true,
|
||||
|
||||
// This is needed to use 'use cache'
|
||||
useCache: true,
|
||||
|
||||
// Content is fully static, we can cache it in the session memory cache for a long time
|
||||
@@ -31,9 +33,7 @@ const nextConfig = {
|
||||
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
||||
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
|
||||
GITBOOK_IMAGE_RESIZE_MODE: process.env.GITBOOK_IMAGE_RESIZE_MODE,
|
||||
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,18 +1,10 @@
|
||||
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
|
||||
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
|
||||
import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache';
|
||||
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
|
||||
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
|
||||
import d1TagCache from '@opennextjs/cloudflare/d1-tag-cache';
|
||||
import kvIncrementalCache from '@opennextjs/cloudflare/kv-cache';
|
||||
import memoryQueue from '@opennextjs/cloudflare/memory-queue';
|
||||
|
||||
export default defineCloudflareConfig({
|
||||
incrementalCache: withRegionalCache(r2IncrementalCache, { mode: 'long-lived' }),
|
||||
tagCache: doShardedTagCache({
|
||||
baseShardSize: 12,
|
||||
regionalCache: true,
|
||||
shardReplication: {
|
||||
numberOfSoftReplicas: 2,
|
||||
numberOfHardReplicas: 1,
|
||||
},
|
||||
}),
|
||||
queue: doQueue,
|
||||
incrementalCache: kvIncrementalCache,
|
||||
queue: memoryQueue,
|
||||
tagCache: d1TagCache,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook-v2",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
@@ -13,11 +13,11 @@
|
||||
"warn-once": "^0.1.1",
|
||||
"rison": "^0.1.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"assert-never": "^1.2.1"
|
||||
"p-memoize": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gitbook": "*",
|
||||
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@8f7ed38",
|
||||
"@opennextjs/cloudflare": "^0.5.10",
|
||||
"@types/rison": "^0.0.9",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"postcss": "^8"
|
||||
@@ -28,7 +28,7 @@
|
||||
"build": "next build",
|
||||
"build:v2": "next build",
|
||||
"start": "next start",
|
||||
"build:v2:cloudflare": "opennextjs-cloudflare build",
|
||||
"build:v2:cloudflare": "opennextjs-cloudflare",
|
||||
"dev:v2:cloudflare": "wrangler dev --port 8771",
|
||||
"unit": "bun test",
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
+313
-1053
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -98,10 +98,6 @@ export async function wrapDataFetcherError<T>(
|
||||
*/
|
||||
export function getExposableError(error: Error): DataFetcherErrorData {
|
||||
if (error instanceof GitBookAPIError) {
|
||||
if (error.code >= 500) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
code: error.code,
|
||||
message: error.errorMessage,
|
||||
@@ -109,10 +105,6 @@ export function getExposableError(error: Error): DataFetcherErrorData {
|
||||
}
|
||||
|
||||
if (error instanceof DataFetcherError) {
|
||||
if (error.code >= 500) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
|
||||
@@ -4,5 +4,3 @@ export * from './pages';
|
||||
export * from './urls';
|
||||
export * from './errors';
|
||||
export * from './lookup';
|
||||
export * from './proxy';
|
||||
export * from './visitor';
|
||||
|
||||
@@ -1,52 +1,39 @@
|
||||
import { describe, expect, it, mock } from 'bun:test';
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { withCacheKey, withoutConcurrentExecution } from './memoize';
|
||||
import { memoize } from './memoize';
|
||||
|
||||
describe('withoutConcurrentExecution', () => {
|
||||
it('should memoize the function based on the cache key', async () => {
|
||||
const fn = mock(async (_cacheKey: string, a: number, b: number) => a + b);
|
||||
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 support caching per request', async () => {
|
||||
describe('memoize', () => {
|
||||
it('should memoize the function', async () => {
|
||||
const fn = mock(async () => Math.random());
|
||||
const memoized = memoize(fn);
|
||||
expect(await memoized()).toBe(await 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);
|
||||
it('should memoize the function with different arguments', 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));
|
||||
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}`
|
||||
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 })
|
||||
);
|
||||
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(2);
|
||||
});
|
||||
|
||||
it('should wrap concurrent async calls', async () => {
|
||||
const fn = mock(async () => Math.random());
|
||||
const memoized = memoize(fn);
|
||||
const promise1 = memoized();
|
||||
const promise2 = memoized();
|
||||
expect(await promise1).toBe(await promise2);
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,61 +1,17 @@
|
||||
/**
|
||||
* 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: (key: string, ...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(key, ...args);
|
||||
return result;
|
||||
} finally {
|
||||
promiseCache.delete(key);
|
||||
}
|
||||
})();
|
||||
|
||||
promiseCache.set(key, promise);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
import pMemoize from 'p-memoize';
|
||||
|
||||
/**
|
||||
* Wrap a function by passing it a cache key that is computed from the function arguments.
|
||||
* 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'.
|
||||
*/
|
||||
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));
|
||||
export function memoize<F extends (...args: any[]) => any>(f: F): F {
|
||||
return pMemoize(f, {
|
||||
cacheKey: (args) => {
|
||||
return JSON.stringify(deepSortValue(args));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function deepSortValue(value: unknown): unknown {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getProxyRequestIdentifier, isProxyRequest } from './proxy';
|
||||
|
||||
describe('isProxyRequest', () => {
|
||||
it('should return true for proxy requests', () => {
|
||||
const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world');
|
||||
expect(isProxyRequest(proxyRequestURL)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-proxy requests', () => {
|
||||
const nonProxyRequestURL = new URL('https://example.com/docs/foo/hello/world');
|
||||
expect(isProxyRequest(nonProxyRequestURL)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProxyRequestIdentifier', () => {
|
||||
it('should return the correct identifier for proxy requests', () => {
|
||||
const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world');
|
||||
expect(getProxyRequestIdentifier(proxyRequestURL)).toBe('sites/site_foo');
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Check if the request to the site was through a proxy.
|
||||
*/
|
||||
export function isProxyRequest(requestURL: URL): boolean {
|
||||
return (
|
||||
requestURL.host === 'proxy.gitbook.site' || requestURL.host === 'proxy.gitbook-staging.site'
|
||||
);
|
||||
}
|
||||
|
||||
export function getProxyRequestIdentifier(requestURL: URL): string {
|
||||
// For proxy requests, we extract the site ID from the pathname
|
||||
// e.g. https://proxy.gitbook.site/site/siteId/...
|
||||
const pathname = requestURL.pathname.slice(1).split('/');
|
||||
return pathname.slice(0, 2).join('/');
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getVisitorAuthBasePath } from './visitor';
|
||||
|
||||
describe('getVisitorAuthBasePath', () => {
|
||||
it('should return the correct base path for proxy requests', () => {
|
||||
expect(
|
||||
getVisitorAuthBasePath(
|
||||
new URL('https://proxy.gitbook.site/sites/site_foo/hello/world'),
|
||||
{
|
||||
site: 'site_foo',
|
||||
siteSpace: 'sitesp_foo',
|
||||
basePath: '/foo',
|
||||
siteBasePath: '/foo',
|
||||
organization: 'org_foo',
|
||||
space: 'space_foo',
|
||||
pathname: '/hello/world',
|
||||
complete: false,
|
||||
apiToken: 'api_token_foo',
|
||||
canonicalUrl: 'https://example.com/docs/foo/hello/world',
|
||||
}
|
||||
)
|
||||
).toBe('/sites/site_foo/');
|
||||
});
|
||||
|
||||
it('should return the correct base path for non-proxy requests', () => {
|
||||
expect(
|
||||
getVisitorAuthBasePath(new URL('https://example.com/docs/foo/hello/world'), {
|
||||
site: 'site_foo',
|
||||
siteSpace: 'sitesp_foo',
|
||||
basePath: '/foo/',
|
||||
siteBasePath: '/foo/',
|
||||
organization: 'org_foo',
|
||||
space: 'space_foo',
|
||||
pathname: '/hello/world',
|
||||
complete: false,
|
||||
apiToken: 'api_token_foo',
|
||||
canonicalUrl: 'https://example.com/docs/foo/hello/world',
|
||||
})
|
||||
).toBe('/foo/');
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { withLeadingSlash, withTrailingSlash } from '@/lib/paths';
|
||||
import type { PublishedSiteContent } from '@gitbook/api';
|
||||
import { getProxyRequestIdentifier, isProxyRequest } from './proxy';
|
||||
|
||||
/**
|
||||
* Get the appropriate base path for the visitor authentication cookie.
|
||||
*/
|
||||
export function getVisitorAuthBasePath(
|
||||
siteRequestURL: URL,
|
||||
siteURLData: PublishedSiteContent
|
||||
): string {
|
||||
// The siteRequestURL for proxy requests is of the form `https://proxy.gitbook.com/site/siteId/...`
|
||||
// In such cases, we should not use the resolved siteBasePath for the cookie because for subsequent requests
|
||||
// we will not have the siteBasePath in the request URL in order to retrieve the cookie. So we use the
|
||||
// proxy identifier instead.
|
||||
return isProxyRequest(siteRequestURL)
|
||||
? withLeadingSlash(withTrailingSlash(getProxyRequestIdentifier(siteRequestURL)))
|
||||
: siteURLData.siteBasePath;
|
||||
}
|
||||
-26
@@ -6,14 +6,6 @@ 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.
|
||||
*/
|
||||
@@ -85,15 +77,6 @@ export const GITBOOK_IMAGE_RESIZE_URL = process.env.GITBOOK_IMAGE_RESIZE_URL ??
|
||||
export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY =
|
||||
process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null;
|
||||
|
||||
/**
|
||||
* Mode used for resizing images.
|
||||
*/
|
||||
export const GITBOOK_IMAGE_RESIZE_MODE = enforceEnum(
|
||||
'GITBOOK_IMAGE_RESIZE_MODE',
|
||||
process.env.GITBOOK_IMAGE_RESIZE_MODE || 'cdn-cgi',
|
||||
['cdn-cgi', 'cf-fetch']
|
||||
);
|
||||
|
||||
/**
|
||||
* Endpoint where icons are served.
|
||||
*/
|
||||
@@ -109,12 +92,3 @@ export const GITBOOK_ICONS_TOKEN = process.env.GITBOOK_ICONS_TOKEN;
|
||||
* Secret used to validate requests from the GitBook app.
|
||||
*/
|
||||
export const GITBOOK_SECRET = process.env.GITBOOK_SECRET ?? null;
|
||||
|
||||
function enforceEnum<T extends string>(key: string, value: string, enumValues: T[]): T {
|
||||
if (!enumValues.includes(value as T)) {
|
||||
throw new Error(
|
||||
`Invalid value for ${key}: "${value}", expected one of: ${enumValues.join(', ')}`
|
||||
);
|
||||
}
|
||||
return value as T;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { checkIsHttpURL } from '@/lib/urls';
|
||||
|
||||
/**
|
||||
* Check if an image URL is resizable.
|
||||
* Skip it for non-http(s) URLs (data, etc).
|
||||
* Skip it for SVGs.
|
||||
* Skip it for GitBook images (to avoid recursion).
|
||||
*/
|
||||
export function checkIsSizableImageURL(input: string): boolean {
|
||||
if (!URL.canParse(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input.includes('/~gitbook/image')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsed = new URL(input);
|
||||
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
|
||||
return false;
|
||||
}
|
||||
if (!checkIsHttpURL(parsed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,11 +1,34 @@
|
||||
import 'server-only';
|
||||
|
||||
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
|
||||
import type { GitBookLinker } from '../links';
|
||||
import { checkIsSizableImageURL } from './checkIsSizableImageURL';
|
||||
import { getImageSize } from './resizer';
|
||||
import { type SignatureVersion, generateImageSignature } from './signatures';
|
||||
import type { ImageResizer } from './types';
|
||||
|
||||
interface CloudflareImageJsonFormat {
|
||||
width: number;
|
||||
height: number;
|
||||
original: {
|
||||
file_size: number;
|
||||
width: number;
|
||||
height: number;
|
||||
format: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developers.cloudflare.com/images/image-resizing/resize-with-workers/
|
||||
*/
|
||||
export interface CloudflareImageOptions {
|
||||
format?: 'webp' | 'avif' | 'json' | 'jpeg';
|
||||
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
||||
width?: number;
|
||||
height?: number;
|
||||
dpr?: number;
|
||||
anim?: boolean;
|
||||
quality?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an image resizer for a rendering context.
|
||||
*/
|
||||
@@ -83,6 +106,124 @@ export function createNoopImageResizer(): ImageResizer {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL is an HTTP URL.
|
||||
*/
|
||||
export function checkIsHttpURL(input: string | URL): boolean {
|
||||
if (!URL.canParse(input)) {
|
||||
return false;
|
||||
}
|
||||
const parsed = new URL(input);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an image URL is resizable.
|
||||
* Skip it for non-http(s) URLs (data, etc).
|
||||
* Skip it for SVGs.
|
||||
* Skip it for GitBook images (to avoid recursion).
|
||||
*/
|
||||
export function checkIsSizableImageURL(input: string): boolean {
|
||||
if (!URL.canParse(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input.includes('/~gitbook/image')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsed = new URL(input);
|
||||
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
|
||||
return false;
|
||||
}
|
||||
if (!checkIsHttpURL(parsed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of an image.
|
||||
*/
|
||||
export async function getImageSize(
|
||||
input: string,
|
||||
defaultSize: Partial<CloudflareImageOptions> = {}
|
||||
): Promise<{ width: number; height: number } | null> {
|
||||
if (!checkIsSizableImageURL(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await resizeImage(input, {
|
||||
// Abort the request after 2 seconds to avoid blocking rendering for too long
|
||||
signal: AbortSignal.timeout(2000),
|
||||
// Measure size and resize it to the most common size
|
||||
// to optimize caching
|
||||
...defaultSize,
|
||||
format: 'json',
|
||||
anim: false,
|
||||
});
|
||||
|
||||
const json = (await response.json()) as CloudflareImageJsonFormat;
|
||||
return {
|
||||
width: json.original.width,
|
||||
height: json.original.height,
|
||||
};
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Cloudflare Image Resize operation on an image.
|
||||
*/
|
||||
export async function resizeImage(
|
||||
input: string,
|
||||
options: CloudflareImageOptions & {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
): Promise<Response> {
|
||||
const { signal, ...resizeOptions } = options;
|
||||
|
||||
const parsed = new URL(input);
|
||||
if (parsed.protocol === 'data:') {
|
||||
throw new Error('Cannot resize data: URLs');
|
||||
}
|
||||
|
||||
if (parsed.hostname === 'localhost') {
|
||||
throw new Error('Cannot resize localhost URLs');
|
||||
}
|
||||
|
||||
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
|
||||
// we need to use the Cloudflare Image Resize API directly.
|
||||
if (!GITBOOK_IMAGE_RESIZE_URL) {
|
||||
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set');
|
||||
}
|
||||
|
||||
return await fetch(
|
||||
`${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
|
||||
resizeOptions
|
||||
)}/${encodeURIComponent(input)}`,
|
||||
{
|
||||
headers: {
|
||||
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
|
||||
Accept:
|
||||
resizeOptions.format === 'json'
|
||||
? 'application/json'
|
||||
: `image/${resizeOptions.format || 'jpeg'}`,
|
||||
},
|
||||
signal,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function stringifyOptions(options: CloudflareImageOptions): string {
|
||||
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
|
||||
return `${rest}${rest ? ',' : ''}${key}=${value}`;
|
||||
}, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Because of a bug in Cloudflare, 127.0.0.1 is replaced by localhost.
|
||||
* We protect against it by converting to a special token, and then parsing
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { getProxyRequestIdentifier, isProxyRequest } from '../data';
|
||||
|
||||
/**
|
||||
* Get the site identifier to use for image resizing for an incoming request.
|
||||
* This identifier can be obtained before resolving the request URL.
|
||||
*/
|
||||
export function getImageResizingContextId(url: URL): string {
|
||||
if (isProxyRequest(url)) {
|
||||
return getProxyRequestIdentifier(url);
|
||||
if (url.host === 'proxy.gitbook.site' || url.host === 'proxy.gitbook-staging.site') {
|
||||
// For proxy requests, we extract the site ID from the pathname
|
||||
// e.g. https://proxy.gitbook.site/site/siteId/...
|
||||
const pathname = url.pathname.slice(1).split('/');
|
||||
return pathname.slice(0, 2).join('/');
|
||||
}
|
||||
|
||||
return url.host;
|
||||
|
||||
@@ -3,5 +3,3 @@ export * from './createImageResizer';
|
||||
export * from './signatures';
|
||||
export * from './utils';
|
||||
export * from './getImageResizingContextId';
|
||||
export * from './resizer';
|
||||
export * from './checkIsSizableImageURL';
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { GITBOOK_IMAGE_RESIZE_URL } from '@v2/lib/env';
|
||||
import type { CloudflareImageOptions } from './types';
|
||||
import { copyImageResponse } from './utils';
|
||||
|
||||
/**
|
||||
* Resize an image by doing a request to a /cdn/cgi/ endpoint.
|
||||
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
|
||||
*/
|
||||
export async function resizeImageWithCDNCgi(
|
||||
input: string,
|
||||
options: CloudflareImageOptions & {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
): Promise<Response> {
|
||||
const { signal, ...resizeOptions } = options;
|
||||
|
||||
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
|
||||
// we need to use the Cloudflare Image Resize API directly.
|
||||
if (!GITBOOK_IMAGE_RESIZE_URL) {
|
||||
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set for cdn-cgi image resize mode');
|
||||
}
|
||||
|
||||
const resizeURL = `${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
|
||||
resizeOptions
|
||||
)}/${encodeURIComponent(input)}`;
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: this log is useful for debugging
|
||||
console.log(`resize image using cdn-cgi: ${resizeURL}`);
|
||||
|
||||
return copyImageResponse(
|
||||
await fetch(resizeURL, {
|
||||
headers: {
|
||||
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
|
||||
Accept:
|
||||
resizeOptions.format === 'json'
|
||||
? 'application/json'
|
||||
: `image/${resizeOptions.format || 'jpeg'}`,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function stringifyOptions(options: CloudflareImageOptions): string {
|
||||
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
|
||||
return `${rest}${rest ? ',' : ''}${key}=${value}`;
|
||||
}, '');
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { CloudflareImageOptions } from './types';
|
||||
import { copyImageResponse } from './utils';
|
||||
|
||||
/**
|
||||
* Resize an image by doing a request to the image itself using the Cloudflare fetch.
|
||||
* https://developers.cloudflare.com/images/transform-images/transform-via-workers/
|
||||
*
|
||||
* This method doesn't work in Cloudflare Pages and is only supported in workers.
|
||||
*/
|
||||
export async function resizeImageWithCFFetch(
|
||||
input: string,
|
||||
options: CloudflareImageOptions & {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
): Promise<Response> {
|
||||
const { signal, ...resizeOptions } = options;
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: this log is useful for debugging
|
||||
console.log(`resize image using cf-fetch: ${input}`);
|
||||
|
||||
return copyImageResponse(
|
||||
await fetch(input, {
|
||||
headers: {
|
||||
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
|
||||
Accept:
|
||||
resizeOptions.format === 'json'
|
||||
? 'application/json'
|
||||
: `image/${resizeOptions.format || 'jpeg'}`,
|
||||
},
|
||||
signal,
|
||||
cf: { image: resizeOptions },
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './types';
|
||||
export * from './resizeImage';
|
||||
@@ -1,68 +0,0 @@
|
||||
import 'server-only';
|
||||
import assertNever from 'assert-never';
|
||||
import { GITBOOK_IMAGE_RESIZE_MODE } from '../../env';
|
||||
import { checkIsSizableImageURL } from '../checkIsSizableImageURL';
|
||||
import { resizeImageWithCDNCgi } from './cdn-cgi';
|
||||
import { resizeImageWithCFFetch } from './cf-fetch';
|
||||
import type { CloudflareImageJsonFormat, CloudflareImageOptions } from './types';
|
||||
|
||||
/**
|
||||
* Get the size of an image.
|
||||
*/
|
||||
export async function getImageSize(
|
||||
input: string,
|
||||
defaultSize: Partial<CloudflareImageOptions> = {}
|
||||
): Promise<{ width: number; height: number } | null> {
|
||||
if (!checkIsSizableImageURL(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await resizeImage(input, {
|
||||
// Abort the request after 2 seconds to avoid blocking rendering for too long
|
||||
signal: AbortSignal.timeout(2000),
|
||||
// Measure size and resize it to the most common size
|
||||
// to optimize caching
|
||||
...defaultSize,
|
||||
format: 'json',
|
||||
anim: false,
|
||||
});
|
||||
|
||||
const json = (await response.json()) as CloudflareImageJsonFormat;
|
||||
return {
|
||||
width: json.original.width,
|
||||
height: json.original.height,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`Error getting image size for ${input}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Cloudflare Image Resize operation on an image.
|
||||
*/
|
||||
export async function resizeImage(
|
||||
input: string,
|
||||
options: CloudflareImageOptions & {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
): Promise<Response> {
|
||||
const parsed = new URL(input);
|
||||
if (parsed.protocol === 'data:') {
|
||||
throw new Error('Cannot resize data: URLs');
|
||||
}
|
||||
|
||||
if (parsed.hostname === 'localhost') {
|
||||
throw new Error('Cannot resize localhost URLs');
|
||||
}
|
||||
|
||||
switch (GITBOOK_IMAGE_RESIZE_MODE) {
|
||||
case 'cdn-cgi':
|
||||
return resizeImageWithCDNCgi(input, options);
|
||||
case 'cf-fetch':
|
||||
return resizeImageWithCFFetch(input, options);
|
||||
default:
|
||||
assertNever(GITBOOK_IMAGE_RESIZE_MODE);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
export interface CloudflareImageJsonFormat {
|
||||
width: number;
|
||||
height: number;
|
||||
original: {
|
||||
file_size: number;
|
||||
width: number;
|
||||
height: number;
|
||||
format: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developers.cloudflare.com/images/image-resizing/resize-with-workers/
|
||||
*/
|
||||
export interface CloudflareImageOptions {
|
||||
format?: 'webp' | 'avif' | 'json' | 'jpeg';
|
||||
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
||||
width?: number;
|
||||
height?: number;
|
||||
dpr?: number;
|
||||
anim?: boolean;
|
||||
quality?: number;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copy a response to make sure it can be mutated by the rest of the middleware.
|
||||
* To avoid errors "Can't modify immutable headers".
|
||||
*/
|
||||
export function copyImageResponse(response: Response) {
|
||||
return new Response(response.body, response);
|
||||
}
|
||||
@@ -32,11 +32,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -70,9 +65,7 @@ const generateSignatureV2: SignFn = async (input) => {
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(':');
|
||||
|
||||
const signature = fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
||||
return signature;
|
||||
return fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
||||
};
|
||||
|
||||
// Reused buffer for FNV-1a hashing in the v1 algorithm
|
||||
|
||||
@@ -72,6 +72,17 @@ export function createLinker(
|
||||
|
||||
const siteBasePath = withTrailingSlash(withLeadingSlash(servedOn.siteBasePath));
|
||||
const spaceBasePath = withTrailingSlash(withLeadingSlash(servedOn.spaceBasePath));
|
||||
const protocol = (() => {
|
||||
if (servedOn.protocol) {
|
||||
return servedOn.protocol;
|
||||
}
|
||||
|
||||
if (servedOn.host) {
|
||||
return servedOn.host.startsWith('localhost') ? 'http:' : 'https:';
|
||||
}
|
||||
|
||||
return 'https:';
|
||||
})();
|
||||
|
||||
const linker: GitBookLinker = {
|
||||
toPathInSpace(relativePath: string): string {
|
||||
@@ -97,7 +108,7 @@ export function createLinker(
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
return `${servedOn.protocol ?? 'https:'}//${joinPaths(servedOn.host, absolutePath)}`;
|
||||
return `${protocol}//${joinPaths(servedOn.host, absolutePath)}`;
|
||||
},
|
||||
|
||||
toPathForPage({ pages, page, anchor }) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { serveResizedImage } from '@/routes/image';
|
||||
import {
|
||||
DataFetcherError,
|
||||
getPublishedContentByURL,
|
||||
getVisitorAuthBasePath,
|
||||
normalizeURL,
|
||||
throwIfDataError,
|
||||
} from '@v2/lib/data';
|
||||
@@ -25,6 +24,7 @@ import { isGitBookAssetsHostURL, isGitBookHostURL } from '@v2/lib/env';
|
||||
import { getImageResizingContextId } from '@v2/lib/images';
|
||||
import { MiddlewareHeaders } from '@v2/lib/middleware';
|
||||
import type { SiteURLData } from './lib/context';
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!_next/static|_next/image|~gitbook/static|~gitbook/revalidate|~gitbook/monitoring|~scalar/proxy).*)',
|
||||
@@ -137,32 +137,24 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
return NextResponse.redirect(siteURLData.redirect);
|
||||
}
|
||||
|
||||
cookies.push(
|
||||
...getResponseCookiesForVisitorAuth(
|
||||
getVisitorAuthBasePath(siteRequestURL, siteURLData),
|
||||
visitorToken
|
||||
)
|
||||
);
|
||||
cookies.push(...getResponseCookiesForVisitorAuth(siteURLData.siteBasePath, visitorToken));
|
||||
|
||||
// We use the host/origin from the canonical URL to ensure the links are
|
||||
// correctly generated when the site is proxied. e.g. https://proxy.gitbook.com/site/siteId/...
|
||||
const siteCanonicalURL = new URL(siteURLData.canonicalUrl);
|
||||
|
||||
let incomingURL = requestURL;
|
||||
// For cases where the site is proxied, we use the canonical URL
|
||||
// as the incoming URL along with all the search params from the request.
|
||||
if (mode !== 'url') {
|
||||
incomingURL = siteCanonicalURL;
|
||||
incomingURL.search = requestURL.search;
|
||||
}
|
||||
//
|
||||
// Make sure the URL is clean of any va token after a successful lookup
|
||||
// The token is stored in a cookie that is set on the redirect response
|
||||
//
|
||||
const incomingURLWithoutToken = normalizeVisitorAuthURL(incomingURL);
|
||||
if (incomingURLWithoutToken.toString() !== incomingURL.toString()) {
|
||||
const incomingURL = mode === 'url' ? requestURL : siteCanonicalURL;
|
||||
const requestURLWithoutToken = normalizeVisitorAuthURL(incomingURL);
|
||||
if (
|
||||
requestURLWithoutToken !== incomingURL &&
|
||||
requestURLWithoutToken.toString() !== incomingURL.toString()
|
||||
) {
|
||||
return writeResponseCookies(
|
||||
NextResponse.redirect(incomingURLWithoutToken.toString()),
|
||||
NextResponse.redirect(requestURLWithoutToken.toString()),
|
||||
cookies
|
||||
);
|
||||
}
|
||||
@@ -175,23 +167,6 @@ 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);
|
||||
@@ -199,7 +174,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
MiddlewareHeaders.SiteURL,
|
||||
`${siteCanonicalURL.origin}${siteURLData.basePath}`
|
||||
);
|
||||
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(stableSiteURLData));
|
||||
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(siteURLData));
|
||||
|
||||
// Preview of customization/theme
|
||||
const customization = siteRequestURL.searchParams.get('customization');
|
||||
@@ -229,6 +204,23 @@ 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,
|
||||
|
||||
@@ -1,158 +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": {
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "NEXT_CACHE_D1",
|
||||
"database_id": "f59ddb40-ad72-4312-9395-0ac6a129af8e",
|
||||
"database_name": "gitbook-open-v2-tags-preview"
|
||||
}
|
||||
],
|
||||
"r2_buckets": [
|
||||
{
|
||||
"binding": "NEXT_INC_CACHE_R2_BUCKET",
|
||||
"bucket_name": "gitbook-open-v2-cache-preview"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"binding": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-preview"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-prod-api-cache"
|
||||
}
|
||||
]
|
||||
// No durable objects on preview, as they block the generation of preview URLs
|
||||
// and we don't need tags invalidation on preview
|
||||
},
|
||||
"staging": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "open-2c.gitbook-staging.com/*",
|
||||
"zone_name": "gitbook-staging.com"
|
||||
},
|
||||
{
|
||||
"pattern": "static-2c.gitbook-staging.com/*",
|
||||
"zone_name": "gitbook-staging.com"
|
||||
}
|
||||
],
|
||||
"r2_buckets": [
|
||||
{
|
||||
"binding": "NEXT_INC_CACHE_R2_BUCKET",
|
||||
"bucket_name": "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": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-staging"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-staging-api-cache"
|
||||
}
|
||||
],
|
||||
"tail_consumers": [
|
||||
{
|
||||
"service": "gitbook-x-staging-tail"
|
||||
}
|
||||
],
|
||||
"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"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"production": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "open-2c.gitbook.com/*",
|
||||
"zone_name": "gitbook.com"
|
||||
},
|
||||
{
|
||||
"pattern": "static-2c.gitbook.com/*",
|
||||
"zone_name": "gitbook.com"
|
||||
}
|
||||
],
|
||||
"r2_buckets": [
|
||||
{
|
||||
"binding": "NEXT_INC_CACHE_R2_BUCKET",
|
||||
"bucket_name": "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": "WORKER_SELF_REFERENCE",
|
||||
"service": "gitbook-open-v2-production"
|
||||
},
|
||||
{
|
||||
"binding": "GITBOOK_API",
|
||||
"service": "gitbook-x-prod-api-cache"
|
||||
}
|
||||
],
|
||||
"tail_consumers": [
|
||||
{
|
||||
"service": "gitbook-x-prod-tail"
|
||||
}
|
||||
],
|
||||
"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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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,48 +1,5 @@
|
||||
# gitbook
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [77397ca]
|
||||
- @gitbook/cache-tags@0.3.1
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- b62b101: Do not set cookie to identify visitor for insights when disabled.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 95ea22d: Cache AI Page Link summary
|
||||
- daf41fc: Tweak footer design (and refactor)
|
||||
- de53946: Fix security issue with injection of "javacript:` url in the back button of PDFs
|
||||
- b92ecfa: Implement retry logic for the DO cache to prevent when revalidating content.
|
||||
- 528eee3: Add superscript and subscript text rendering
|
||||
- aa3357a: Fix OpenAPISchemas description padding
|
||||
- 168a4fa: Add support for buttons to GitBook.
|
||||
- 70c4182: Improve OpenAPI schema style
|
||||
- 2b6c593: Remove stable from x-stability
|
||||
- 580f7ad: Improve the error message returned by the revalidate endpoint.
|
||||
- cbd768a: Improve OpenAPI codesample (add OpenAPISelect component)
|
||||
- c765463: Fix ogimage generation crashing when site is using a custom WOFF2 font
|
||||
- e59076a: Improve OpenAPI schemas block ungrouped style. Classnames have changed, please refer to this PR to update GBX.
|
||||
- 29aaba5: Override Scalar's overscroll-behavior
|
||||
- 90ead98: Better error handling in cache revalidation.
|
||||
- Updated dependencies [116575c]
|
||||
- Updated dependencies [cdffd7c]
|
||||
- Updated dependencies [70c4182]
|
||||
- Updated dependencies [2b6c593]
|
||||
- Updated dependencies [cbd768a]
|
||||
- Updated dependencies [e59076a]
|
||||
- Updated dependencies [eedefdd]
|
||||
- Updated dependencies [23cedd2]
|
||||
- @gitbook/cache-tags@0.3.0
|
||||
- @gitbook/colors@0.3.2
|
||||
- @gitbook/react-openapi@1.1.10
|
||||
- @gitbook/openapi-parser@2.1.3
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -92,6 +92,11 @@ 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',
|
||||
@@ -239,15 +244,6 @@ const testCases: TestsCase[] = [
|
||||
contentBaseURL: 'https://docs.fluentbit.io',
|
||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
},
|
||||
{
|
||||
name: 'run-ai-docs.nvidia.com',
|
||||
contentBaseURL: 'https://run-ai-docs.nvidia.com',
|
||||
skip: process.env.ARGOS_BUILD_NAME !== 'customers-v2',
|
||||
tests: [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'OG Image', url: '/~gitbook/ogimage/h17zQIFwy3MaafVNmItO', mode: 'image' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
runTestCases(testCases);
|
||||
|
||||
@@ -419,43 +419,6 @@ const testCases: TestsCase[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Site subdirectory (proxy)',
|
||||
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
|
||||
contentBaseURL: 'https://nextjs-gbo-proxy.vercel.app/documentation/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Main',
|
||||
url: '',
|
||||
fullPage: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Site subdirectory (proxy) with VA',
|
||||
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
|
||||
contentBaseURL: 'https://nextjs-gbo-proxy-va.vercel.app/va/docs/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Main',
|
||||
url: () => {
|
||||
const privateKey =
|
||||
'rqSfA6x7eAKx1qDRCDq9aCXwivpUvQ8YkXeDdFvCCUa9QchIcM7pF1iJ4o7AGOU49spmOWjKoIPtX0pVUVQ81w==';
|
||||
const token = jwt.sign(
|
||||
{
|
||||
name: 'gitbook-open-tests',
|
||||
},
|
||||
privateKey,
|
||||
{
|
||||
expiresIn: '24h',
|
||||
}
|
||||
);
|
||||
return `?jwt_token=${token}`;
|
||||
},
|
||||
fullPage: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Content tests',
|
||||
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
||||
@@ -874,20 +837,6 @@ const testCases: TestsCase[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Content Redirects',
|
||||
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/gitbook-doc/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Redirect to new location',
|
||||
url: '/content-editor/editing-content/inline/redirect-test',
|
||||
run: async (page) => {
|
||||
await expect(page.locator('h1')).toHaveText('Redirect test');
|
||||
},
|
||||
screenshot: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Site Redirects with sections',
|
||||
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/sections/',
|
||||
|
||||
@@ -34,10 +34,6 @@ export interface Test {
|
||||
* Test to run
|
||||
*/
|
||||
run?: (page: Page, response: Response | null) => Promise<unknown>;
|
||||
/**
|
||||
* Mode for the test.
|
||||
*/
|
||||
mode?: 'page' | 'image';
|
||||
/**
|
||||
* Whether the test should be fullscreened during testing.
|
||||
*/
|
||||
@@ -158,7 +154,6 @@ export function runTestCases(testCases: TestsCase[]) {
|
||||
|
||||
test.describe(testCase.name, () => {
|
||||
for (const testEntry of testCase.tests) {
|
||||
const { mode = 'page' } = testEntry;
|
||||
const testFn = testEntry.only ? test.only : test;
|
||||
testFn(testEntry.name, async ({ page, context }) => {
|
||||
const testEntryPathname =
|
||||
@@ -199,33 +194,24 @@ export function runTestCases(testCases: TestsCase[]) {
|
||||
}
|
||||
const screenshotOptions = testEntry.screenshot;
|
||||
if (screenshotOptions !== false) {
|
||||
const screenshotName = `${testCase.name} - ${testEntry.name}`;
|
||||
if (mode === 'image') {
|
||||
await argosScreenshot(page, screenshotName, {
|
||||
viewports: ['macbook-13'],
|
||||
threshold: screenshotOptions?.threshold ?? undefined,
|
||||
fullPage: true,
|
||||
});
|
||||
} else {
|
||||
await argosScreenshot(page, screenshotName, {
|
||||
viewports: ['macbook-16', 'macbook-13', 'ipad-2', 'iphone-x'],
|
||||
argosCSS: `
|
||||
await argosScreenshot(page, `${testCase.name} - ${testEntry.name}`, {
|
||||
viewports: ['macbook-16', 'macbook-13', 'ipad-2', 'iphone-x'],
|
||||
argosCSS: `
|
||||
/* Hide Intercom */
|
||||
.intercom-lightweight-app {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
threshold: screenshotOptions?.threshold ?? undefined,
|
||||
fullPage: testEntry.fullPage ?? false,
|
||||
beforeScreenshot: async ({ runStabilization }) => {
|
||||
await runStabilization();
|
||||
if (screenshotOptions?.waitForTOCScrolling !== false) {
|
||||
await waitForTOCScrolling(page);
|
||||
}
|
||||
await waitForIcons(page);
|
||||
},
|
||||
});
|
||||
}
|
||||
threshold: screenshotOptions?.threshold ?? undefined,
|
||||
fullPage: testEntry.fullPage ?? false,
|
||||
beforeScreenshot: async ({ runStabilization }) => {
|
||||
await runStabilization();
|
||||
await waitForIcons(page);
|
||||
if (screenshotOptions?.waitForTOCScrolling !== false) {
|
||||
await waitForTOCScrolling(page);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -345,74 +331,61 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
||||
*/
|
||||
async function waitForIcons(page: Page) {
|
||||
await page.waitForFunction(() => {
|
||||
const urlStates: Record<string, 'pending' | 'loaded'> =
|
||||
(window as any).__ICONS_STATES__ || {};
|
||||
(window as any).__ICONS_STATES__ = urlStates;
|
||||
|
||||
const loadUrl = (url: string) => {
|
||||
// Mark the URL as pending.
|
||||
urlStates[url] = 'pending';
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
urlStates[url] = 'loaded';
|
||||
};
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
const urls = new Set<string>();
|
||||
const icons = Array.from(document.querySelectorAll('svg.gb-icon'));
|
||||
const results = icons.map((icon) => {
|
||||
if (!(icon instanceof SVGElement)) {
|
||||
throw new Error('Icon is not an SVGElement');
|
||||
}
|
||||
|
||||
// If loaded, good it passes the test.
|
||||
if (icon.dataset.loadingState === 'loaded') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not loaded yet, we need to load it.
|
||||
if (icon.dataset.loadingState === 'pending') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore icons that are not visible.
|
||||
if (!icon.checkVisibility()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const state = icon.getAttribute('data-argos-state');
|
||||
|
||||
if (state === 'pending') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state === 'loaded') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93")
|
||||
const maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
|
||||
const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
|
||||
const url = urlMatch?.[1];
|
||||
const url = urlMatch ? urlMatch[1] : null;
|
||||
|
||||
// If URL is invalid we throw an error.
|
||||
if (!url) {
|
||||
throw new Error('No mask-image');
|
||||
}
|
||||
|
||||
// If the URL is already queued for loading, we return the state.
|
||||
if (urlStates[url]) {
|
||||
if (urlStates[url] === 'loaded') {
|
||||
icon.setAttribute('data-argos-state', 'pending');
|
||||
const bckMaskImage = icon.style.maskImage;
|
||||
const bckDisplay = icon.style.display;
|
||||
icon.style.maskImage = '';
|
||||
icon.style.display = 'none';
|
||||
requestAnimationFrame(() => {
|
||||
icon.style.maskImage = bckMaskImage;
|
||||
icon.style.display = bckDisplay;
|
||||
requestAnimationFrame(() => {
|
||||
icon.setAttribute('data-argos-state', 'loaded');
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// If the URL is already loaded, we just mark it as loaded.
|
||||
if (urls.has(url)) {
|
||||
icon.dataset.loadingState = 'loaded';
|
||||
return true;
|
||||
}
|
||||
|
||||
loadUrl(url);
|
||||
// Mark the icon as pending and load the image.
|
||||
icon.dataset.loadingState = 'pending';
|
||||
|
||||
// Mark the URL as seen.
|
||||
urls.add(url);
|
||||
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.decode().then(() => {
|
||||
// Wait two frames to let the time to the icon to repaint.
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
icon.dataset.loadingState = 'loaded';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ 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.10.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent -f ../../.env.local next dev",
|
||||
@@ -33,6 +33,7 @@
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vercel/og": "0.6.8.",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "^1.2.1",
|
||||
"bun-types": "^1.1.20",
|
||||
@@ -75,7 +76,7 @@
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@argos-ci/playwright": "^5.0.3",
|
||||
"@argos-ci/playwright": "^4.3.0",
|
||||
"@cloudflare/next-on-pages": "1.13.7",
|
||||
"@cloudflare/workers-types": "^4.20241230.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
|
||||
@@ -2,106 +2,12 @@
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t } from '@/intl/translate';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useVisitedPages } from '../Insights';
|
||||
import { usePageContext } from '../PageContext';
|
||||
import { Loading } from '../primitives';
|
||||
import { streamLinkPageSummary } from './server-actions/streamLinkPageSummary';
|
||||
|
||||
/**
|
||||
* Get a unique cache key for a page summary
|
||||
*/
|
||||
function getCacheKey(targetSpaceId: string, targetPageId: string): string {
|
||||
return `${targetSpaceId}:${targetPageId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global state for the summaries.
|
||||
*/
|
||||
const useSummaries = create<{
|
||||
/**
|
||||
* Cache of all summaries generated so far.
|
||||
*/
|
||||
cache: Map<string, string>;
|
||||
|
||||
/**
|
||||
* Get a summary for a page.
|
||||
*/
|
||||
getSummary: (params: { targetSpaceId: string; targetPageId: string }) => string;
|
||||
|
||||
/**
|
||||
* Stream the generation of a summary for a page.
|
||||
*/
|
||||
streamSummary: (params: {
|
||||
currentSpaceId: string;
|
||||
currentPageId: string;
|
||||
currentPageTitle: string;
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
linkPreview?: string;
|
||||
linkTitle?: string;
|
||||
visitedPages: { spaceId: string; pageId: string }[];
|
||||
}) => Promise<void>;
|
||||
}>((set, get) => ({
|
||||
cache: new Map(),
|
||||
|
||||
getSummary: ({
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
}: {
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
}) => {
|
||||
return get().cache.get(getCacheKey(targetSpaceId, targetPageId)) ?? '';
|
||||
},
|
||||
|
||||
streamSummary: async ({
|
||||
currentSpaceId,
|
||||
currentPageId,
|
||||
currentPageTitle,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
}) => {
|
||||
const cacheKey = getCacheKey(targetSpaceId, targetPageId);
|
||||
|
||||
if (get().cache.has(cacheKey)) {
|
||||
// Already generated or generating
|
||||
return;
|
||||
}
|
||||
|
||||
const update = (summary: string) => {
|
||||
set((prev) => {
|
||||
const newCache = new Map(prev.cache);
|
||||
newCache.set(cacheKey, summary);
|
||||
return { cache: newCache };
|
||||
});
|
||||
};
|
||||
|
||||
update('');
|
||||
const stream = await streamLinkPageSummary({
|
||||
currentSpaceId,
|
||||
currentPageId,
|
||||
currentPageTitle,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
});
|
||||
|
||||
let generatedSummary = '';
|
||||
for await (const highlight of stream) {
|
||||
generatedSummary = highlight ?? '';
|
||||
update(generatedSummary);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Summarise a page's content for use in a link preview
|
||||
*/
|
||||
@@ -115,28 +21,37 @@ export function AIPageLinkSummary(props: {
|
||||
const { targetSpaceId, targetPageId, linkPreview, linkTitle, showTrademark = true } = props;
|
||||
|
||||
const currentPage = usePageContext();
|
||||
|
||||
const language = useLanguage();
|
||||
const visitedPages = useVisitedPages((state) => state.pages);
|
||||
const { summary, streamSummary } = useSummaries(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
summary: state.getSummary({ targetSpaceId, targetPageId }),
|
||||
streamSummary: state.streamSummary,
|
||||
};
|
||||
})
|
||||
);
|
||||
const [highlight, setHighlight] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
streamSummary({
|
||||
currentSpaceId: currentPage.spaceId,
|
||||
currentPageId: currentPage.pageId,
|
||||
currentPageTitle: currentPage.title,
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
});
|
||||
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,
|
||||
@@ -146,7 +61,6 @@ export function AIPageLinkSummary(props: {
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
visitedPages,
|
||||
streamSummary,
|
||||
]);
|
||||
|
||||
const shimmerBlocks = [
|
||||
@@ -162,14 +76,14 @@ export function AIPageLinkSummary(props: {
|
||||
<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={!summary || summary.length === 0} />
|
||||
<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>
|
||||
{summary.length > 0 ? (
|
||||
<p className="animate-fadeIn">{summary}</p>
|
||||
{highlight.length > 0 ? (
|
||||
<p className="animate-fadeIn">{highlight}</p>
|
||||
) : (
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{shimmerBlocks.map((block, index) => (
|
||||
@@ -180,7 +94,7 @@ export function AIPageLinkSummary(props: {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{summary.length > 0 ? (
|
||||
{highlight.length > 0 ? (
|
||||
<div className="animate-fadeIn text-tint-subtle text-xs">
|
||||
{t(language, 'link_tooltip_ai_summary_description')}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ export function ClientCodeBlock(props: ClientBlockProps) {
|
||||
const [isInViewport, setIsInViewport] = useState(false);
|
||||
const plainLines = useMemo(() => plainHighlight(block, []), [block]);
|
||||
const [lines, setLines] = useState<null | HighlightLine[]>(null);
|
||||
const [highlighting, setHighlighting] = useState(false);
|
||||
|
||||
// Preload the highlighter when the block is mounted.
|
||||
useEffect(() => {
|
||||
@@ -78,7 +77,6 @@ export function ClientCodeBlock(props: ClientBlockProps) {
|
||||
let cancelled = false;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
setHighlighting(true);
|
||||
import('./highlight').then(({ highlight }) => {
|
||||
highlight(block, inlines).then((lines) => {
|
||||
if (cancelled) {
|
||||
@@ -86,7 +84,6 @@ export function ClientCodeBlock(props: ClientBlockProps) {
|
||||
}
|
||||
|
||||
setLines(lines);
|
||||
setHighlighting(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -101,12 +98,6 @@ export function ClientCodeBlock(props: ClientBlockProps) {
|
||||
}, [isInViewport, block, inlines]);
|
||||
|
||||
return (
|
||||
<CodeBlockRenderer
|
||||
ref={blockRef}
|
||||
aria-busy={highlighting}
|
||||
block={block}
|
||||
style={style}
|
||||
lines={lines ?? plainLines}
|
||||
/>
|
||||
<CodeBlockRenderer ref={blockRef} block={block} style={style} lines={lines ?? plainLines} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import './CodeBlockRenderer.css';
|
||||
|
||||
type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
|
||||
lines: HighlightLine[];
|
||||
'aria-busy'?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,7 +23,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
|
||||
props: CodeBlockRendererProps,
|
||||
ref: React.ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const { block, style, lines, 'aria-busy': ariaBusy } = props;
|
||||
const { block, style, lines } = props;
|
||||
|
||||
const id = useId();
|
||||
const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1;
|
||||
@@ -32,11 +31,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
|
||||
const title = block.data.title;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
aria-busy={ariaBusy}
|
||||
className={tcls('group/codeblock grid grid-flow-col', style)}
|
||||
>
|
||||
<div ref={ref} className={tcls('group/codeblock grid grid-flow-col', style)}>
|
||||
<div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]">
|
||||
{title ? (
|
||||
<div className="inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s bg-tint px-3 py-2 text-tint text-xs leading-none tracking-wide">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type {
|
||||
DocumentInline,
|
||||
DocumentInlineAnnotation,
|
||||
DocumentInlineButton,
|
||||
DocumentInlineEmoji,
|
||||
DocumentInlineImage,
|
||||
DocumentInlineLink,
|
||||
@@ -14,7 +13,6 @@ import assertNever from 'assert-never';
|
||||
import { Annotation } from './Annotation/Annotation';
|
||||
import type { DocumentContextProps } from './DocumentView';
|
||||
import { Emoji } from './Emoji';
|
||||
import { InlineButton } from './InlineButton';
|
||||
import { InlineImage } from './InlineImage';
|
||||
import { InlineLink } from './InlineLink';
|
||||
import { InlineMath } from './Math';
|
||||
@@ -46,8 +44,7 @@ export function Inline<
|
||||
| DocumentInlineEmoji
|
||||
| DocumentInlineLink
|
||||
| DocumentInlineMath
|
||||
| DocumentInlineMention
|
||||
| DocumentInlineButton,
|
||||
| DocumentInlineMention,
|
||||
>(props: InlineProps<T>) {
|
||||
const { inline, ...contextProps } = props;
|
||||
|
||||
@@ -64,8 +61,6 @@ export function Inline<
|
||||
return <Mention {...contextProps} inline={inline} />;
|
||||
case 'inline-image':
|
||||
return <InlineImage {...contextProps} inline={inline} />;
|
||||
case 'button':
|
||||
return <InlineButton {...contextProps} inline={inline} />;
|
||||
default:
|
||||
assertNever(inline);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import * as api from '@gitbook/api';
|
||||
import { Button } from '../primitives';
|
||||
import type { InlineProps } from './Inline';
|
||||
|
||||
export async function InlineButton(props: InlineProps<api.DocumentInlineButton>) {
|
||||
const { inline, context } = props;
|
||||
|
||||
if (!context.contentContext) {
|
||||
throw new Error('InlineButton requires a contentContext');
|
||||
}
|
||||
|
||||
const resolved = await resolveContentRef(inline.data.ref, context.contentContext);
|
||||
|
||||
if (!resolved) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
href={resolved.href}
|
||||
label={inline.data.label}
|
||||
// TODO: use a variant specifically for user-defined buttons.
|
||||
variant={inline.data.kind}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
link: {
|
||||
target: inline.data.ref,
|
||||
position: api.SiteInsightsLinkPosition.Content,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
@import "@scalar/api-client-react/style.css";
|
||||
|
||||
html,
|
||||
body {
|
||||
/** Override Scalar's overscroll-behavior */
|
||||
@apply !overscroll-auto;
|
||||
}
|
||||
|
||||
.light .scalar-modal-layout,
|
||||
.light .scalar-app,
|
||||
.light .scalar {
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
/* Method Tags */
|
||||
.openapi-method {
|
||||
@apply rounded uppercase font-mono shrink-0 font-bold text-xs px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap;
|
||||
@apply rounded uppercase font-mono font-bold text-xs px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap;
|
||||
}
|
||||
|
||||
.openapi-method-get {
|
||||
@@ -156,10 +156,6 @@
|
||||
@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;
|
||||
}
|
||||
@@ -423,20 +419,8 @@
|
||||
@apply flex flex-row items-center;
|
||||
}
|
||||
|
||||
.openapi-codesample-header .openapi-select > button {
|
||||
@apply border-none;
|
||||
}
|
||||
|
||||
.openapi-codesample-header-content {
|
||||
@apply flex flex-row items-center justify-between h-fit p-2.5;
|
||||
}
|
||||
|
||||
.openapi-codesample-header-content .openapi-path {
|
||||
@apply flex items-center font-mono *:text-[0.813rem] gap-2 h-fit *:truncate overflow-x-auto min-w-0 max-w-full font-normal text-tint-strong;
|
||||
}
|
||||
|
||||
.openapi-codesample-header-content .openapi-path .openapi-path-variable {
|
||||
@apply text-[0.813rem];
|
||||
@apply flex flex-row items-center h-fit;
|
||||
}
|
||||
|
||||
.openapi-codesample-footer {
|
||||
@@ -449,7 +433,7 @@
|
||||
|
||||
/* Path */
|
||||
.openapi-path {
|
||||
@apply flex items-center text-sm gap-2 h-fit overflow-x-auto min-w-0 max-w-full;
|
||||
@apply flex items-start text-sm gap-2 h-fit overflow-x-auto min-w-0 max-w-full;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
@@ -463,12 +447,12 @@
|
||||
}
|
||||
|
||||
.openapi-path .openapi-method {
|
||||
@apply text-[0.813rem] m-0 mt-0.5 items-center flex px-1;
|
||||
@apply text-[0.813rem] m-0 mt-0.5 items-center flex px-2;
|
||||
}
|
||||
|
||||
.openapi-path-title {
|
||||
@apply flex-1 relative font-normal text-left font-mono text-tint-strong/10;
|
||||
@apply py-0.5 px-1 rounded hover:bg-tint transition-colors;
|
||||
@apply py-0.5 px-1 rounded hover:bg-tint cursor-pointer transition-colors;
|
||||
@apply whitespace-nowrap md:whitespace-normal;
|
||||
}
|
||||
|
||||
@@ -480,6 +464,14 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .openapi-path-copy {
|
||||
@apply absolute opacity-0 h-fit right-0 top-1/2 -translate-y-1/2 bg-light dark:bg-dark border rounded-md border-tint-subtle px-1.5 py-0;
|
||||
}
|
||||
|
||||
.openapi-path-title:hover .openapi-path-copy {
|
||||
@apply opacity-11;
|
||||
} */
|
||||
|
||||
.openapi-path-title em {
|
||||
@apply not-italic text-primary font-medium;
|
||||
}
|
||||
@@ -506,8 +498,7 @@
|
||||
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
|
||||
}
|
||||
|
||||
.openapi-panel-footer,
|
||||
.openapi-codesample-footer {
|
||||
.openapi-panel-footer {
|
||||
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
|
||||
}
|
||||
|
||||
@@ -522,57 +513,7 @@
|
||||
|
||||
/* Common Elements */
|
||||
.openapi-select {
|
||||
/* unstyled */
|
||||
}
|
||||
|
||||
/* Prevent react-aria popover from setting overflow:auto on body */
|
||||
body:has(.openapi-select-popover) {
|
||||
overflow: unset !important;
|
||||
}
|
||||
|
||||
.openapi-select > button {
|
||||
@apply flex items-center cursor-pointer gap-1.5 text-tint-strong max-w-60 rounded text-xs leading-6 px-1.5 truncate border border-tint-subtle bg-tint;
|
||||
@apply hover:bg-tint-hover transition-all;
|
||||
}
|
||||
|
||||
.openapi-select > button > span.react-aria-SelectValue {
|
||||
@apply shrink truncate;
|
||||
}
|
||||
|
||||
.openapi-select > button > .gb-icon {
|
||||
@apply shrink-0;
|
||||
}
|
||||
|
||||
.openapi-select > button svg {
|
||||
@apply size-2.5;
|
||||
}
|
||||
|
||||
.openapi-select-popover {
|
||||
@apply min-w-32 max-w-fit w-auto max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md;
|
||||
@apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1;
|
||||
}
|
||||
|
||||
.openapi-select-popover[data-entering] {
|
||||
animation: popover-enter 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.openapi-select-popover[data-exiting] {
|
||||
animation: popover-leave 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.openapi-select-item {
|
||||
@apply text-sm cursor-pointer px-1.5 py-0.5 truncate text-tint ring-0 border-none rounded !outline-none;
|
||||
@apply hover:bg-tint-hover theme-gradient:hover:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
|
||||
}
|
||||
|
||||
.openapi-select-item-selected {
|
||||
@apply text-primary-subtle hover:text-primary hover:bg-primary-hover;
|
||||
@apply theme-muted:hover:bg-primary-active theme-gradient:hover:bg-primary-active tint:font-semibold;
|
||||
@apply contrast-more:text-primary contrast-more:hover:text-primary-strong contrast-more:font-semibold;
|
||||
}
|
||||
|
||||
.openapi-select-listbox {
|
||||
@apply flex flex-col gap-1 focus:ring-0 focus:outline-none;
|
||||
@apply max-w-60 rounded font-mono text-xs leading-6 px-1 py-0.5 truncate border border-tint-subtle bg-tint;
|
||||
}
|
||||
|
||||
.openapi-select:focus {
|
||||
@@ -635,10 +576,8 @@ body:has(.openapi-select-popover) {
|
||||
@apply text-primary after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
|
||||
}
|
||||
|
||||
.openapi-tabs-panel,
|
||||
.openapi-codesample-panel {
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Disclosure group */
|
||||
@@ -788,32 +727,6 @@ body:has(.openapi-select-popover) {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popover-enter {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(4px) scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popover-leave {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(4px) scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.openapi-copy-button {
|
||||
@apply hover:brightness-95 cursor-pointer;
|
||||
}
|
||||
|
||||
.openapi-copy-button[data-disabled="true"] {
|
||||
@apply cursor-default;
|
||||
@apply hover:brightness-95;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import type {
|
||||
DocumentMarkItalic,
|
||||
DocumentMarkKeyboard,
|
||||
DocumentMarkStrikethrough,
|
||||
DocumentMarkSubscript,
|
||||
DocumentMarkSuperscript,
|
||||
DocumentText,
|
||||
DocumentTextMark,
|
||||
} from '@gitbook/api';
|
||||
@@ -49,8 +47,6 @@ const MARK_STYLES = {
|
||||
strikethrough: Strikethrough,
|
||||
color: Color,
|
||||
keyboard: Keyboard,
|
||||
superscript: Superscript,
|
||||
subscript: Subscript,
|
||||
};
|
||||
|
||||
interface MarkedLeafProps<Mark extends DocumentTextMark> {
|
||||
@@ -78,14 +74,6 @@ function Keyboard(props: MarkedLeafProps<DocumentMarkKeyboard>) {
|
||||
);
|
||||
}
|
||||
|
||||
function Superscript(props: MarkedLeafProps<DocumentMarkSuperscript>) {
|
||||
return <sup>{props.children}</sup>;
|
||||
}
|
||||
|
||||
function Subscript(props: MarkedLeafProps<DocumentMarkSubscript>) {
|
||||
return <sub>{props.children}</sub>;
|
||||
}
|
||||
|
||||
function Code(props: MarkedLeafProps<DocumentMarkCode>) {
|
||||
return (
|
||||
<code
|
||||
|
||||
@@ -15,123 +15,123 @@ export function Footer(props: { context: GitBookSiteContext }) {
|
||||
const { context } = props;
|
||||
const { customization } = context;
|
||||
|
||||
const hasLogo = customization.footer.logo;
|
||||
const groupCount = customization.footer.groups?.length ?? 0;
|
||||
const hasGroups = groupCount > 0;
|
||||
const hasCopyright = customization.footer.copyright;
|
||||
const hasThemeToggle = customization.themes.toggeable;
|
||||
|
||||
const mobileOnly = !hasLogo && !hasGroups && !hasCopyright && hasThemeToggle;
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={tcls(
|
||||
'border-tint-subtle border-t',
|
||||
// If the footer only contains a mode toggle, we only show it on smaller screens
|
||||
mobileOnly ? 'xl:hidden' : null
|
||||
customization.themes.toggeable &&
|
||||
!customization.footer.copyright &&
|
||||
!customization.footer.logo &&
|
||||
customization.footer.groups?.length === 0
|
||||
? 'xl:hidden'
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<div className="scroll-nojump">
|
||||
<div className={tcls(CONTAINER_STYLE, 'px-4', 'py-8', 'lg:py-12', 'mx-auto')}>
|
||||
<div className={tcls(CONTAINER_STYLE, 'px-4', 'mx-auto', 'flex', 'gap-12')}>
|
||||
<div className="hidden page-no-toc:hidden basis-72 lg:block" />
|
||||
<div
|
||||
className={tcls(
|
||||
'mx-auto grid max-w-3xl justify-between gap-12 lg:max-w-none',
|
||||
'grid-cols-[auto_auto]',
|
||||
'lg:grid-cols-[18rem_minmax(auto,_48rem)_auto]',
|
||||
'xl:grid-cols-[18rem_minmax(auto,_48rem)_14rem]',
|
||||
'page-no-toc:lg:grid-cols-[minmax(auto,_48rem)_auto]',
|
||||
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]'
|
||||
'py-8',
|
||||
'gap-12',
|
||||
'flex',
|
||||
'flex-wrap',
|
||||
'items-start',
|
||||
'w-full',
|
||||
'flex-1',
|
||||
'max-w-3xl',
|
||||
'page-full-width:max-w-none',
|
||||
'mx-auto'
|
||||
)}
|
||||
>
|
||||
{
|
||||
// Footer Logo
|
||||
customization.footer.logo ? (
|
||||
<div className="col-start-1 row-start-1">
|
||||
<Image
|
||||
alt="Logo"
|
||||
resize={context.imageResizer}
|
||||
sources={{
|
||||
light: {
|
||||
src: customization.footer.logo.light,
|
||||
},
|
||||
dark: customization.footer.logo.dark
|
||||
? {
|
||||
src: customization.footer.logo.dark,
|
||||
}
|
||||
: null,
|
||||
}}
|
||||
priority="lazy"
|
||||
style={[
|
||||
'w-auto',
|
||||
'max-w-40',
|
||||
'lg:max-w-64',
|
||||
'max-h-10',
|
||||
'lg:max-h-12',
|
||||
'object-contain',
|
||||
'object-left',
|
||||
'rounded',
|
||||
'straight-corners:rounded-sm',
|
||||
]}
|
||||
sizes={[
|
||||
{
|
||||
width: 320,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{/* Footer Logo */}
|
||||
{customization.footer.logo ? (
|
||||
<Image
|
||||
alt="Logo"
|
||||
resize={context.imageResizer}
|
||||
sources={{
|
||||
light: {
|
||||
src: customization.footer.logo.light,
|
||||
},
|
||||
dark: customization.footer.logo.dark
|
||||
? {
|
||||
src: customization.footer.logo.dark,
|
||||
}
|
||||
: null,
|
||||
}}
|
||||
priority="lazy"
|
||||
style={[
|
||||
'w-auto',
|
||||
'max-w-40',
|
||||
'lg:max-w-64',
|
||||
'max-h-12',
|
||||
'object-contain',
|
||||
'object-left',
|
||||
'rounded',
|
||||
'straight-corners:rounded-sm',
|
||||
'order-1',
|
||||
]}
|
||||
sizes={[
|
||||
{
|
||||
width: 320,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{
|
||||
// Theme Toggle
|
||||
customization.themes.toggeable ? (
|
||||
<div className="-col-start-2 row-start-1 flex items-start justify-end xl:hidden">
|
||||
<React.Suspense fallback={null}>
|
||||
<ThemeToggler />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{/* Mode Switcher */}
|
||||
{customization.themes.toggeable ? (
|
||||
<div className="order-2 ml-auto flex items-center justify-end xl:hidden">
|
||||
<React.Suspense fallback={null}>
|
||||
<ThemeToggler />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{
|
||||
// Navigation groups (split into equal columns)
|
||||
customization.footer.groups?.length > 0 ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'col-span-2 page-has-toc:lg:col-span-1 page-has-toc:lg:col-start-2 page-no-toc:xl:col-span-1 page-no-toc:xl:col-start-2'
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto flex max-w-3xl flex-col gap-10 sm:flex-row sm:gap-6">
|
||||
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
|
||||
(column, columnIndex) => (
|
||||
<div
|
||||
key={columnIndex}
|
||||
className="flex flex-1 grow flex-col gap-10"
|
||||
>
|
||||
{column.map((group, groupIndex) => (
|
||||
<FooterLinksGroup
|
||||
key={groupIndex}
|
||||
group={group}
|
||||
context={context}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{/* Navigation Groups (split into equal columns) */}
|
||||
{customization.footer.groups?.length > 0 ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'order-3 mx-auto flex w-full grow flex-col gap-10 sm:flex-row sm:gap-6',
|
||||
!customization.footer.logo &&
|
||||
customization.footer.groups.length < 2 &&
|
||||
'self-center sm:order-1 sm:w-auto sm:max-w-3xl sm:flex-1 sm:items-start sm:text-start'
|
||||
)}
|
||||
>
|
||||
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
|
||||
(column, columnIndex) => (
|
||||
<div
|
||||
key={columnIndex}
|
||||
className="flex w-full flex-col gap-10"
|
||||
>
|
||||
{column.map((group, groupIndex) => (
|
||||
<FooterLinksGroup
|
||||
key={groupIndex}
|
||||
group={group}
|
||||
context={context}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{
|
||||
// Legal
|
||||
customization.footer.copyright ? (
|
||||
<div className="order-last col-span-full flex w-full grow flex-col items-center gap-2 text-center text-tint text-xs">
|
||||
<p>{customization.footer.copyright}</p>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{/* Legal */}
|
||||
<div
|
||||
className={tcls(
|
||||
'order-4 mx-auto flex w-full grow flex-col items-center gap-2 text-center text-tint text-xs empty:hidden',
|
||||
customization.footer.groups.length === 0 &&
|
||||
'self-center sm:order-1 sm:w-auto sm:max-w-3xl sm:flex-1 sm:items-start sm:text-start'
|
||||
)}
|
||||
>
|
||||
{customization.footer.copyright ? (
|
||||
<p>{customization.footer.copyright}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden page-no-toc:hidden lg:block xl:basis-56" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -58,7 +58,6 @@ export function Dropdown<E extends HTMLElement>(props: {
|
||||
className={tcls(
|
||||
'mt-2',
|
||||
'w-full',
|
||||
'max-h-80',
|
||||
'bg-tint-base',
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-sm',
|
||||
|
||||
@@ -1,65 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { useRef } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { usePathnameChange } from '../hooks';
|
||||
import { useScrollListener } from '../hooks/useScrollListener';
|
||||
|
||||
const globalClassName = 'navigation-open';
|
||||
|
||||
|
||||
/**
|
||||
* Detect if the user has scrolled in the page, meaning the TOC is not visible.
|
||||
*/
|
||||
function useHasScrolledRef() {
|
||||
const hasScrollRef = useRef(false);
|
||||
const windowRef = useRef(typeof window === 'undefined' ? null : window);
|
||||
useScrollListener(() => {
|
||||
hasScrollRef.current = window.scrollY >= 320;
|
||||
}, windowRef);
|
||||
return hasScrollRef
|
||||
}
|
||||
const SCROLL_DISTANCE = 320;
|
||||
|
||||
/**
|
||||
* Button to show/hide the table of content on mobile.
|
||||
*/
|
||||
export function HeaderMobileMenu(props: Partial<React.ButtonHTMLAttributes<HTMLButtonElement>>) {
|
||||
const language = useLanguage();
|
||||
const hasScrollRef = useHasScrolledRef();
|
||||
|
||||
// Close the navigation when navigating to a page
|
||||
usePathnameChange(() => {
|
||||
document.body.classList.remove(globalClassName);
|
||||
});
|
||||
const pathname = usePathname();
|
||||
const hasScrollRef = useRef(false);
|
||||
|
||||
const handleNavigationClick = () => {
|
||||
// If already opened
|
||||
if (document.body.classList.contains(globalClassName)) {
|
||||
// If the user has scrolled down, we need to scroll back to the top.
|
||||
if (hasScrollRef.current) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
// Else we close the table of contents
|
||||
else {
|
||||
document.body.classList.remove(globalClassName);
|
||||
}
|
||||
}
|
||||
// If not opened, we open it an scroll to the top
|
||||
else {
|
||||
const toggleNavigation = () => {
|
||||
if (!hasScrollRef.current && document.body.classList.contains(globalClassName)) {
|
||||
document.body.classList.remove(globalClassName);
|
||||
} else {
|
||||
document.body.classList.add(globalClassName);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const windowRef = useRef(typeof window === 'undefined' ? null : window);
|
||||
useScrollListener(() => {
|
||||
hasScrollRef.current = window.scrollY >= SCROLL_DISTANCE;
|
||||
}, windowRef);
|
||||
|
||||
// Close the navigation when navigating to a page
|
||||
useEffect(() => {
|
||||
document.body.classList.remove(globalClassName);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
aria-label={tString(language, 'table_of_contents_button_label')}
|
||||
onClick={handleNavigationClick}
|
||||
onClick={toggleNavigation}
|
||||
className={tcls(
|
||||
'flex flex-row items-center rounded straight-corners:rounded-sm px-2 py-1',
|
||||
props.className
|
||||
|
||||
@@ -20,6 +20,7 @@ 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';
|
||||
@@ -28,7 +29,6 @@ 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,10 +92,7 @@ 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 ? sanitizeGitBookAppURL(pdfParams.back) : null) ??
|
||||
linker.toAbsoluteURL(linker.toPathInSpace(''))
|
||||
}
|
||||
href={pdfParams.back ?? linker.toAbsoluteURL(linker.toPathInSpace(''))}
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
@@ -356,13 +353,3 @@ 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}` : ''}`;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
'use client';
|
||||
import { motion } from 'framer-motion';
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
// import { useScrollActiveId } from '@/components/hooks';
|
||||
import { useScrollActiveId } from '@/components/hooks';
|
||||
import type { DocumentSection } from '@/lib/document-sections';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { usePageActiveSections } from '../hooks/usePageActiveSections';
|
||||
// import { HEADER_HEIGHT_DESKTOP } from '../layout';
|
||||
import { HEADER_HEIGHT_DESKTOP } from '../layout';
|
||||
import { AsideSectionHighlight } from './AsideSectionHighlight';
|
||||
|
||||
/**
|
||||
* The threshold at which we consider a section as intersecting the viewport.
|
||||
*/
|
||||
// const SECTION_INTERSECTING_THRESHOLD = 0.9;
|
||||
const SECTION_INTERSECTING_THRESHOLD = 0.9;
|
||||
|
||||
const springCurve = {
|
||||
type: 'spring',
|
||||
@@ -31,13 +30,10 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
});
|
||||
}, [sections]);
|
||||
|
||||
const windowRef = useRef(typeof window === 'undefined' ? null : window);
|
||||
const activeIds = usePageActiveSections(ids, windowRef);
|
||||
|
||||
// const activeId = useScrollActiveId(ids, {
|
||||
// rootMargin: `-${HEADER_HEIGHT_DESKTOP}px 0px -40% 0px`,
|
||||
// threshold: SECTION_INTERSECTING_THRESHOLD,
|
||||
// });
|
||||
const activeId = useScrollActiveId(ids, {
|
||||
rootMargin: `-${HEADER_HEIGHT_DESKTOP}px 0px -40% 0px`,
|
||||
threshold: SECTION_INTERSECTING_THRESHOLD,
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className={tcls('sidebar-list-line:border-l', 'border-tint-subtle')}>
|
||||
@@ -53,7 +49,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
section.depth > 1 && ['ml-3', 'my-0', 'sidebar-list-line:ml-0']
|
||||
)}
|
||||
>
|
||||
{activeIds.includes(section.id) ? (
|
||||
{activeId === section.id ? (
|
||||
<AsideSectionHighlight
|
||||
transition={springCurve}
|
||||
className={tcls(
|
||||
@@ -106,7 +102,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
'sidebar-list-default:border-tint',
|
||||
],
|
||||
|
||||
activeIds.includes(section.id) && [
|
||||
activeId === section.id && [
|
||||
'text-primary-subtle',
|
||||
'hover:text-primary',
|
||||
'contrast-more:text-primary',
|
||||
|
||||
@@ -39,7 +39,7 @@ export function SpaceLayout(props: {
|
||||
|
||||
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
|
||||
|
||||
const withSections = Boolean(sections && sections.list.length > 1);
|
||||
const withSections = Boolean(sections && sections.list.length > 0);
|
||||
const isMultiVariants = Boolean(siteSpaces.length > 1);
|
||||
|
||||
const withFooter =
|
||||
|
||||
@@ -4,5 +4,3 @@ export * from './useHash';
|
||||
export * from './useIsMounted';
|
||||
export * from './useToggleAnimation';
|
||||
export * from './useCurrentPagePath';
|
||||
export * from './usePrevious';
|
||||
export * from './usePathnameChange';
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
// How do I get the pathname with hash.
|
||||
// source: https://github.com/vercel/next.js/discussions/49465
|
||||
function getHash(): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
return window.location.hash.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get the current hash from the URL.
|
||||
* @see https://github.com/vercel/next.js/discussions/49465
|
||||
*/
|
||||
export function useHash() {
|
||||
const getCurrentHash = useCallback(
|
||||
() => (typeof window !== 'undefined' ? window.location.hash.replace(/^#!?/, '') : ''),
|
||||
[]
|
||||
);
|
||||
const [hash, setHash] = useState<string>(getCurrentHash());
|
||||
|
||||
useEffect(() => {
|
||||
const handleHashChange = () => {
|
||||
setHash(getCurrentHash());
|
||||
};
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', handleHashChange);
|
||||
};
|
||||
}, [getCurrentHash]);
|
||||
|
||||
const params = useParams();
|
||||
const [hash, setHash] = React.useState<string | null>(getHash);
|
||||
React.useEffect(() => {
|
||||
setHash(getHash());
|
||||
}, [params]);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
'use client';
|
||||
import * as React from 'react';
|
||||
import { useEventCallback } from 'usehooks-ts';
|
||||
import { useScrollListener } from './useScrollListener';
|
||||
|
||||
/**
|
||||
* Hook that returns the active section key of the page.
|
||||
* The active section is the section that is currently visible on the viewport.
|
||||
*
|
||||
* Here's a diagram: https://user-images.githubusercontent.com/8937991/206921462-14e5b8b7-5a19-4c8b-912d-ed1f17710bc4.png
|
||||
*/
|
||||
export function usePageActiveSections(
|
||||
ids: string[],
|
||||
scrollRef: React.RefObject<HTMLElement | Window | null>
|
||||
): string[] {
|
||||
const [activeSections, setActiveSections] = React.useState<string[]>(noActiveSections);
|
||||
|
||||
const update = useEventCallback(() => {
|
||||
const element = scrollRef.current;
|
||||
if (element) {
|
||||
const rect =
|
||||
element instanceof Window
|
||||
? new DOMRect(0, 0, window.innerWidth, window.innerHeight)
|
||||
: element.getBoundingClientRect();
|
||||
const activeSections = findActiveSectionIds({
|
||||
ids,
|
||||
containerRect: rect,
|
||||
});
|
||||
setActiveSections((previous) => {
|
||||
if (activeSections.length !== previous.length) {
|
||||
return activeSections;
|
||||
}
|
||||
if (activeSections.some((key, i) => key !== previous[i])) {
|
||||
return activeSections;
|
||||
}
|
||||
return previous;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useScrollListener(update, scrollRef);
|
||||
|
||||
React.useEffect(() => {
|
||||
update();
|
||||
}, [update]);
|
||||
|
||||
return activeSections;
|
||||
}
|
||||
|
||||
const noActiveSections: string[] = [];
|
||||
|
||||
// We consider anything in [-MARGINpx; +MARGINpx] to be at the boundary
|
||||
const MARGIN = 24;
|
||||
|
||||
/**
|
||||
* findActiveSectionIndex finds the section that is in the window's view
|
||||
* it uses binary-search to efficiently (minimize DOM queries)
|
||||
* find the DOM node whose .top is closest to but below `0` (the window's top)
|
||||
*/
|
||||
function findActiveSectionIndex(ids: string[], scrollTop: number): number {
|
||||
const N = ids.length;
|
||||
// Number of max iterations to find the element (so our search is bounded)
|
||||
// in theory, at most floor(log(n)+1) steps to find the specific element
|
||||
const maxIterations = Math.floor(Math.log2(N) + 1);
|
||||
// Our binary search variables
|
||||
let start = 0;
|
||||
let end = N - 1;
|
||||
let mid = Math.ceil((start + end) / 2);
|
||||
let idx = -1;
|
||||
let y: number | null = null;
|
||||
for (let i = 0; i < maxIterations; i++) {
|
||||
const id = ids[mid];
|
||||
// Double check that we have a section, else exit
|
||||
if (!id) {
|
||||
return -1;
|
||||
}
|
||||
const element = document.getElementById(id);
|
||||
const sy = element ? element.getBoundingClientRect().top - scrollTop : null;
|
||||
if (sy === null || sy > MARGIN) {
|
||||
// Go left
|
||||
end = mid;
|
||||
mid = Math.floor((start + end) / 2);
|
||||
} else if (sy >= -MARGIN && sy <= MARGIN) {
|
||||
// Hit
|
||||
return mid;
|
||||
} else if (sy < MARGIN) {
|
||||
// If bigger than previous (but still smaller than 0)
|
||||
// that becomes our new active index
|
||||
if (y === null || sy > y) {
|
||||
y = sy;
|
||||
idx = mid;
|
||||
}
|
||||
// Go right
|
||||
start = mid;
|
||||
mid = Math.ceil((start + end) / 2);
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the active sections in the viewport.
|
||||
*/
|
||||
function findActiveSectionIds(input: {
|
||||
ids: string[];
|
||||
containerRect: DOMRect;
|
||||
}): string[] {
|
||||
const activeIndex = findActiveSectionIndex(input.ids, input.containerRect.top);
|
||||
|
||||
// Check if the next sections are visible, stop when they are not.
|
||||
const activeSections: string[] = input.ids[activeIndex] ? [input.ids[activeIndex]] : [];
|
||||
|
||||
for (let i = activeIndex + 1; i < input.ids.length; i++) {
|
||||
const id = input.ids[i];
|
||||
const element = document.getElementById(id);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.bottom + MARGIN > input.containerRect.bottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
activeSections.push(id);
|
||||
}
|
||||
return activeSections;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
'use client';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import * as React from 'react';
|
||||
import { useEventCallback } from 'usehooks-ts';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
/**
|
||||
* Returns the value of the previous render.
|
||||
*/
|
||||
export function usePathnameChange(callback: () => void): void {
|
||||
const pathname = usePathname();
|
||||
const previousPathname = usePrevious(pathname);
|
||||
const eventCallback = useEventCallback(callback);
|
||||
React.useEffect(() => {
|
||||
if (previousPathname !== undefined && pathname !== previousPathname) {
|
||||
eventCallback();
|
||||
}
|
||||
}, [pathname, previousPathname, eventCallback]);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
'use client';
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* Returns the value of the previous render.
|
||||
*/
|
||||
export function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = React.useRef<T | undefined>(undefined);
|
||||
React.useLayoutEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import { type RefObject, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
*/
|
||||
export function useScrollListener(
|
||||
listener: (event: Event) => void,
|
||||
elementRef: RefObject<HTMLElement | Window | null> | null
|
||||
elementRef: RefObject<HTMLElement | Window> | null
|
||||
) {
|
||||
const listenerRef = useRef(listener);
|
||||
useLayoutEffect(() => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { usePathname } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
import { useHash } from './useHash';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
/**
|
||||
* Scroll the page to an anchor point or
|
||||
@@ -14,29 +13,28 @@ import { usePrevious } from './usePrevious';
|
||||
export function useScrollPage(props: { scrollMarginTop?: number }) {
|
||||
const hash = useHash();
|
||||
const pathname = usePathname();
|
||||
const prevPathname = usePrevious(pathname);
|
||||
const scrollMarginTopRef = React.useRef(props.scrollMarginTop);
|
||||
React.useLayoutEffect(() => {
|
||||
if (hash) {
|
||||
const element = document.getElementById(hash);
|
||||
if (element) {
|
||||
const originalScrollMarginTop = element.style.scrollMarginTop;
|
||||
if (scrollMarginTopRef.current) {
|
||||
element.style.scrollMarginTop = `${scrollMarginTopRef.current}px`;
|
||||
if (props.scrollMarginTop) {
|
||||
element.style.scrollMarginTop = `${props.scrollMarginTop}px`;
|
||||
}
|
||||
element.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||||
return () => {
|
||||
const element = document.getElementById(hash);
|
||||
if (element) {
|
||||
element.style.scrollMarginTop = originalScrollMarginTop;
|
||||
}
|
||||
};
|
||||
element.scrollIntoView({
|
||||
block: 'start',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevPathname && pathname !== prevPathname) {
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}, [hash, pathname, prevPathname]);
|
||||
return () => {
|
||||
if (hash) {
|
||||
const element = document.getElementById(hash);
|
||||
if (element) {
|
||||
element.style.scrollMarginTop = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [hash, pathname, props.scrollMarginTop]);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Link = React.forwardRef(function Link(
|
||||
|
||||
// When the page is embedded in an iframe, for security reasons other urls cannot be opened.
|
||||
// In this case, we open the link in a new tab.
|
||||
if (window.self !== window.top && isExternalLink(href, window.location.origin)) {
|
||||
if (isExternal && window.self !== window.top) {
|
||||
event.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
@@ -58,9 +58,7 @@ export const Link = React.forwardRef(function Link(
|
||||
domProps.onClick?.(event);
|
||||
};
|
||||
|
||||
// We test if the link is external, without comparing to the origin
|
||||
// as this will be rendered on the server and it could result in a mismatch.
|
||||
if (isExternalLink(href)) {
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a ref={ref} {...domProps} href={href} onClick={onClick}>
|
||||
{children}
|
||||
@@ -74,27 +72,3 @@ export const Link = React.forwardRef(function Link(
|
||||
</NextLink>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a link is external, compared to an origin.
|
||||
*/
|
||||
function isExternalLink(href: string, origin: string | null = null) {
|
||||
if (!URL.canParse) {
|
||||
// If URL.canParse is not available, we quickly check if it looks like a URL
|
||||
return href.startsWith('http');
|
||||
}
|
||||
|
||||
if (!URL.canParse(href)) {
|
||||
// If we can't parse the href, we consider it a relative path
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!origin) {
|
||||
// If origin is not provided, we consider the link external
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the url points to the same origin, we consider it internal
|
||||
const parsed = new URL(href);
|
||||
return parsed.origin !== origin;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import type { ImageResizer } from '@v2/lib/images';
|
||||
import { type ImageResizer, checkIsHttpURL } from '@v2/lib/images';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import { checkIsHttpURL } from '@/lib/urls';
|
||||
import { ZoomImage } from './ZoomImage';
|
||||
import type { PolymorphicComponentProp } from './types';
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ export const getLatestOpenAPISpecVersionContent = cache({
|
||||
* Resolve a URL to the content to render.
|
||||
*/
|
||||
export const getPublishedContentByUrl = cache({
|
||||
name: 'api.getPublishedContentByUrl.v7',
|
||||
name: 'api.getPublishedContentByUrl.v4',
|
||||
tag: (url) => getCacheTagForURL(url),
|
||||
get: async (
|
||||
url: string,
|
||||
@@ -267,10 +267,6 @@ export const getPublishedContentByUrl = cache({
|
||||
|
||||
const parsed = parseCacheResponse(response);
|
||||
|
||||
console.log(
|
||||
`Parsed ttl: ${parsed.ttl} at ${Date.now()}, for ${'apiToken' in response.data ? response.data.apiToken : '<no-token>'}`
|
||||
);
|
||||
|
||||
const data: PublishedContentWithCache = {
|
||||
...response.data,
|
||||
cacheMaxAge: parsed.ttl,
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import { GITBOOK_ASSETS_URL, GITBOOK_URL } from '@v2/lib/env';
|
||||
import { joinPath, joinPathWithBaseURL } from './paths';
|
||||
import { GITBOOK_ASSETS_URL } from '@v2/lib/env';
|
||||
|
||||
/**
|
||||
* Create a public URL for an asset.
|
||||
*/
|
||||
export function getAssetURL(path: string): string {
|
||||
return joinPathWithBaseURL(
|
||||
GITBOOK_ASSETS_URL || GITBOOK_URL,
|
||||
joinPath('~gitbook/static', path)
|
||||
);
|
||||
return `${GITBOOK_ASSETS_URL || ''}/~gitbook/static/${path}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
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,5 +1,4 @@
|
||||
import { parseOpenAPI } from '@gitbook/openapi-parser';
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cache';
|
||||
import type {
|
||||
@@ -7,9 +6,7 @@ import type {
|
||||
OpenAPISchemasBlock,
|
||||
ResolveOpenAPIBlockArgs,
|
||||
} from '@/lib/openapi/types';
|
||||
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
|
||||
import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize';
|
||||
import { GITBOOK_RUNTIME } from '@v2/lib/env';
|
||||
import { memoize } from '@v2/lib/data/memoize';
|
||||
import { assert } from 'ts-essentials';
|
||||
import { resolveContentRef } from '../references';
|
||||
import { isV2 } from '../v2';
|
||||
@@ -69,26 +66,15 @@ const fetchFilesystemV1 = cache({
|
||||
},
|
||||
});
|
||||
|
||||
const fetchFilesystemV2 = withCacheKey(
|
||||
withoutConcurrentExecution(getCloudflareRequestGlobal, async (cacheKey, url: string) => {
|
||||
if (GITBOOK_RUNTIME !== 'cloudflare') {
|
||||
return fetchFilesystemUseCache(url);
|
||||
}
|
||||
|
||||
// FIXME: OpenNext doesn't support 'use cache' yet
|
||||
const uncached = unstable_cache(async () => fetchFilesystemUncached(url), [cacheKey], {
|
||||
revalidate: 60 * 60 * 24,
|
||||
});
|
||||
|
||||
const response = await uncached();
|
||||
return response;
|
||||
})
|
||||
);
|
||||
|
||||
const fetchFilesystemUseCache = async (url: string) => {
|
||||
const fetchFilesystemV2 = memoize(async function fetchFilesystemV2(url: string) {
|
||||
'use cache';
|
||||
return fetchFilesystemUncached(url);
|
||||
};
|
||||
|
||||
// TODO: add cache lifetime once we can use next.js 15 code here
|
||||
|
||||
const response = await fetchFilesystemUncached(url);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
async function fetchFilesystemUncached(
|
||||
url: string,
|
||||
|
||||
@@ -15,8 +15,8 @@ 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';
|
||||
@@ -194,7 +194,7 @@ export async function resolveContentRef(
|
||||
|
||||
if (!targetSpace) {
|
||||
return {
|
||||
href: getGitBookAppHref(`/s/${contentRef.space}`),
|
||||
href: getGitbookAppHref(`/s/${contentRef.space}`),
|
||||
text: 'space',
|
||||
active: false,
|
||||
};
|
||||
@@ -224,7 +224,7 @@ export async function resolveContentRef(
|
||||
|
||||
case 'collection': {
|
||||
return {
|
||||
href: getGitBookAppHref('/home'),
|
||||
href: getGitbookAppHref('/home'),
|
||||
text: 'collection',
|
||||
active: false,
|
||||
};
|
||||
@@ -242,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,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Check if a URL is an HTTP URL.
|
||||
*/
|
||||
export function checkIsHttpURL(input: string | URL): boolean {
|
||||
if (!URL.canParse(input)) {
|
||||
return false;
|
||||
}
|
||||
const parsed = new URL(input);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
||||
}
|
||||
@@ -9,7 +9,6 @@ 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,
|
||||
@@ -32,8 +31,7 @@ import {
|
||||
searchSiteContent,
|
||||
} from './api';
|
||||
import { getDynamicCustomizationSettings } from './customization';
|
||||
import { withLeadingSlash, withTrailingSlash } from './paths';
|
||||
import { assertIsNotV2 } from './v2';
|
||||
import { getBasePath, getHost, getSiteBasePath } from './links';
|
||||
|
||||
/*
|
||||
* Code that will be used until the migration to v2 is complete.
|
||||
@@ -331,41 +329,3 @@ 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') ?? '';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
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';
|
||||
|
||||
@@ -33,7 +32,6 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
|
||||
|
||||
const { site, customization } = context;
|
||||
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
|
||||
|
||||
// If the site has a custom icon, redirect to it
|
||||
if (customIcon) {
|
||||
const iconUrl = options.theme === 'light' ? customIcon.light : customIcon.dark;
|
||||
@@ -46,7 +44,6 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
|
||||
}
|
||||
|
||||
const contentTitle = site.title;
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
tw={tcls(options.theme === 'light' ? 'bg-white' : 'bg-black', size.boxStyle)}
|
||||
@@ -74,14 +71,6 @@ 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(','),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,13 +107,12 @@ export async function serveResizedImage(
|
||||
try {
|
||||
const response = await resizeImage(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to resize image, received status code ${response.status}`);
|
||||
throw new Error('Failed to resize image');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Redirect to the original image if resizing fails
|
||||
console.warn('Error while resizing image, redirecting to original', error);
|
||||
return NextResponse.redirect(url, 302);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ 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 } = {
|
||||
@@ -89,15 +87,12 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
await Promise.all(
|
||||
primaryFontWeights.map((face) => {
|
||||
const { weight, sources } = face;
|
||||
const source = sources[0];
|
||||
|
||||
// Satori doesn't support WOFF2, so we skip it
|
||||
// https://github.com/vercel/satori?tab=readme-ov-file#fonts
|
||||
if (!source || source.format === 'woff2' || source.url.endsWith('.woff2')) {
|
||||
if (sources.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const url = sources[0].url;
|
||||
|
||||
return loadCustomFont({ url: source.url, weight });
|
||||
return loadCustomFont({ url, weight });
|
||||
})
|
||||
)
|
||||
).filter(filterOutNullable);
|
||||
@@ -174,12 +169,8 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
{String.fromCodePoint(Number.parseInt(`0x${customization.favicon.emoji}`))}
|
||||
</span>
|
||||
);
|
||||
const src = await readSelfImage(
|
||||
linker.toAbsoluteURL(
|
||||
linker.toPathInSpace(
|
||||
`~gitbook/icon?size=medium&theme=${customization.themes.default}`
|
||||
)
|
||||
)
|
||||
const src = 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" />;
|
||||
})();
|
||||
@@ -201,11 +192,7 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
/>
|
||||
|
||||
{/* Grid */}
|
||||
<img
|
||||
tw="absolute inset-0 w-[100vw] h-[100vh]"
|
||||
src={await readStaticImage(gridAsset)}
|
||||
alt="Grid"
|
||||
/>
|
||||
<img tw="absolute inset-0 w-[100vw] h-[100vh]" src={gridAsset} alt="Grid" />
|
||||
|
||||
{/* Logo */}
|
||||
{customization.header.logo ? (
|
||||
@@ -241,18 +228,6 @@ 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(','),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -310,59 +285,3 @@ 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(
|
||||
// `getAssetURL` can return a relative URL, so we need to make it absolute
|
||||
// the URL doesn't matter, as we're using the worker-self-reference binding
|
||||
new URL(url, 'https://worker-self-reference/')
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -54,9 +54,6 @@ async function main() {
|
||||
writeDataFile('styles-map', JSON.stringify(onlyStyles, null, 2)),
|
||||
writeDataFile('icons', JSON.stringify(result, null, 2)),
|
||||
]);
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: We want the CLI to log
|
||||
console.log(`Generated ${result.length} icons`);
|
||||
}
|
||||
|
||||
async function writeDataFile(name, content) {
|
||||
@@ -77,7 +74,6 @@ async function writeDataFile(name, content) {
|
||||
]);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`Error generating icons list: ${error}`);
|
||||
main().catch((_error) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -20,6 +20,8 @@ async function main() {
|
||||
// Create the output folder if it doesn't exist
|
||||
await fs.mkdir(outputFolder, { recursive: true });
|
||||
|
||||
const _printOutputFolder = path.relative(process.cwd(), outputFolder);
|
||||
|
||||
// Copy the assets from
|
||||
// source/sprites to outputFolder/sprites
|
||||
// source/svgs to outputFolder/svgs
|
||||
@@ -43,12 +45,8 @@ async function main() {
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: We want the CLI to log
|
||||
console.log(`Copied ${stylesToCopy.length} styles to ${outputFolder}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`Error copying icons: ${error}`);
|
||||
main().catch((_error) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"generate": {
|
||||
"inputs": ["bin/**/*", "package.json"],
|
||||
"outputs": ["src/data/*.json", "dist/data/*.json"]
|
||||
"outputs": ["data/*.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @gitbook/openapi-parser
|
||||
|
||||
## 2.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2b6c593: Remove stable from x-stability
|
||||
|
||||
## 2.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.2",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.10",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist/
|
||||
@@ -0,0 +1,7 @@
|
||||
# @gitbook/proxy
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 53b9f10: First version
|
||||
@@ -0,0 +1,28 @@
|
||||
# `@gitbook/proxy`
|
||||
|
||||
Host a GitBook site on your own domain as a subpath.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { proxyToGitBook } from '@gitbook/proxy';
|
||||
|
||||
const site = proxyToGitBook(event.request, {
|
||||
site: 'mycompany.gitbook.io/site/',
|
||||
basePath: '/docs',
|
||||
});
|
||||
|
||||
export default {
|
||||
async fetch(request) {
|
||||
// If the requst matches the basePath /docs, we serve from GitBook
|
||||
if (site.match(request)) {
|
||||
return site.fetch(request);
|
||||
}
|
||||
|
||||
// Otherwise we do something else.
|
||||
return new Response('Not found', {
|
||||
statusCode: 404,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user