Compare commits

..

27 Commits

Author SHA1 Message Date
Zeno Kapitein 365f60d2ca First try 2025-12-17 15:19:07 +01:00
Zeno Kapitein c716df39ba Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout 2025-12-16 19:49:33 +01:00
Zeno Kapitein 8ee84f130f Update SpaceLayout.tsx 2025-12-12 11:53:46 +01:00
Zeno Kapitein aa86711d5a More spacing 2025-12-11 19:33:38 +01:00
Zeno Kapitein 5347917a47 Spacing 2025-12-11 19:16:24 +01:00
Zeno Kapitein 43e8deca0f Add margin to first document item 2025-12-11 17:56:49 +01:00
Zeno Kapitein 4cdab37eac Rework table of contents to accommodate faded edges 2025-12-11 17:27:49 +01:00
Zeno Kapitein 8a887fd890 Fix tests 2025-12-11 14:39:00 +01:00
Zeno Kapitein 2f6ea5b1f0 Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout 2025-12-11 14:28:55 +01:00
Zeno Kapitein dcd03554bd Update TOC to use normal scrollcontainer 2025-12-11 14:28:26 +01:00
Zeno Kapitein 9281b5e3b7 Update SideSheet.tsx 2025-12-11 14:26:46 +01:00
Zeno Kapitein ec0b6075b0 Update AnnouncementBanner.tsx 2025-12-11 10:24:15 +01:00
Zeno Kapitein e372dae552 Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout 2025-12-11 10:11:28 +01:00
Zeno Kapitein e329b41764 Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout 2025-12-05 14:13:46 +01:00
Zeno Kapitein c8956720d0 Update Header.tsx 2025-12-04 18:32:37 +01:00
Zeno Kapitein 843635225d Make sidesheet work well in docs embed 2025-12-04 18:17:31 +01:00
Zeno Kapitein f78e9ca34e Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout 2025-12-04 17:36:31 +01:00
Zeno Kapitein 72a06718e2 Layout 2025-12-02 19:22:21 +01:00
Zeno Kapitein 0a7979ede2 More layout fixes 2025-12-02 17:49:51 +01:00
Zeno Kapitein fb49a27cc1 Layout tweaks 2025-12-02 16:04:39 +01:00
Zeno Kapitein 555a1da3ab Changeset 2025-12-02 14:10:13 +01:00
Zeno Kapitein b9f9623970 Fix scroll gutter 2025-12-02 14:08:23 +01:00
Zeno Kapitein a7b80c3c4f Small fixes 2025-12-02 13:54:32 +01:00
Zeno Kapitein e05f262f46 Get ready for supporting side sections list
(not enabled for now)
2025-12-02 13:50:38 +01:00
Zeno Kapitein f53e0427e4 Naming and docs 2025-12-02 12:52:02 +01:00
Zeno Kapitein 0d44191666 Fix trademark & other visual bugs 2025-12-02 11:40:28 +01:00
Zeno Kapitein 9e669f162a Add SideSheet component, refactor TOC and AIChat to use it
Also fixes RND-7803
2025-12-02 10:09:24 +01:00
199 changed files with 1407 additions and 5375 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Fix missing properties in allOf/oneOf
@@ -2,4 +2,4 @@
"gitbook": patch
---
Support social links
Add Input component
-6
View File
@@ -1,6 +0,0 @@
---
"gitbook": minor
"@gitbook/react-openapi": patch
---
Support code themes customization
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Enhance discriminator handling in OpenAPISchema
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Set <html lang> based on translated language
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/openapi-parser': patch
'@gitbook/react-openapi': patch
---
Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/embed": patch
---
Fix signed JWT token usage
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-openapi": patch
---
Fix OpenAPI example not showing
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix overflowing section groups
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": minor
---
Serve a markdown version of the page when the "Accept" header contains "text/markdown".
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Align styling of PageLinkItem and PageDocumentItem
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix search results cursor
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/openapi-parser": patch
---
Update scalar dependencies.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Better conditional display of page actions
+6
View File
@@ -0,0 +1,6 @@
---
"@gitbook/embed": minor
"gitbook": patch
---
Improve Docs Embed with separate Assistant and Docs tabs
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-openapi": patch
---
Improve OpenAPI server URL validation
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/icons": minor
---
Update to FontAwesome 7.1.0
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix ordered list item index calculation
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Standardise toggle chevrons across the app
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Add customizable suggested questions
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Fix OpenAPI basic auth placeholder
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix links to other spaces and root page in embeddable view.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Support `greeting` and fix suggested questions if there are no custom ones defined
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Fix OpenAPI oneOf/allOf merge
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Grow InlineActionButton to take available width on line
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/icons": patch
---
Simplify installation by using public fontawesome mirror.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Add a RSS feed to all pages with update blocks.
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Remove hidden site spaces from navigation
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Fix hidden section not found
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-openapi": patch
---
Add OpenAPI 3.1 nullable array support
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Improve OpenAPI circular references
+7 -2
View File
@@ -5,6 +5,8 @@ on:
branches:
- main
- changeset-release/main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
format:
runs-on: ubuntu-latest
@@ -34,10 +36,13 @@ jobs:
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- run: bun unit
build:
build-oss:
# CI to check that the repository builds correctly on a machine without the credentials
runs-on: ubuntu-latest
name: Build
name: Build (Open Source)
timeout-minutes: 6
env:
BUN_NPM_TOKEN: ''
steps:
- name: Checkout
uses: actions/checkout@v4
+2
View File
@@ -4,6 +4,8 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (preview)
+2
View File
@@ -3,6 +3,8 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (production)
+2
View File
@@ -3,6 +3,8 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (staging)
+3
View File
@@ -4,6 +4,9 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
+17 -1
View File
@@ -77,7 +77,23 @@ All pull-requests will be tested against both visual and performances testing to
## Fonts and Icons
GitBook Open uses fontawesome. For self-hosting and local development, for licensing reasons, only the icons from the free version should be used.
GitBook Open uses fontawesome. During development, your local environment will use the free version. However, only the pro version will be accepted by CI. If you see the following error:
```
The GitBook icon is missing. It indicates that the dependencies were installed without the correct font-awesome package. These changes have probably been persisted in the Bun lockfile. Read the README for more information.
```
It means that you've changed the GBO dependencies and bundled in the free version. Only GitBook staff can help with this - if you're not on the GitBook team, please ping us in the PR and we'll help get things moving.
If you are GitBook staff, you'll need our NPM token in your local environment.
```
.env.local
BUN_NPM_TOKEN=xxx
```
and then reinstall dependencies.
## Contributing
Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 KiB

After

Width:  |  Height:  |  Size: 622 KiB

+70 -87
View File
@@ -6,8 +6,8 @@
"name": "gitbook",
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.29.8",
"turbo": "^2.7.6",
"@changesets/cli": "^2.29.7",
"turbo": "^2.6.1",
"vercel": "^39.4.2",
},
},
@@ -48,7 +48,7 @@
},
"packages/embed": {
"name": "@gitbook/embed",
"version": "0.2.0",
"version": "0.1.5",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -56,7 +56,6 @@
},
"devDependencies": {
"@types/react": "catalog:",
"bun-types": "catalog:",
"react": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
@@ -109,7 +108,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.21.3",
"version": "0.20.0",
"dependencies": {
"@cloudflare/workers-types": "^4.20251011.0",
"@gitbook/api": "catalog:",
@@ -136,6 +135,7 @@
"@radix-ui/react-tooltip": "^1.1.8",
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
"@tusbar/cache-control": "^1.0.2",
"ai": "^4.2.2",
"assert-never": "catalog:",
@@ -157,8 +157,7 @@
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"motion": "^12.23.24",
"negotiator": "^1.0.0",
"next": "15.4.11",
"next": "15.4.10",
"next-themes": "^0.4.6",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -176,7 +175,7 @@
"remark-rehype": "^11.1.1",
"rison": "^0.1.1",
"server-only": "^0.0.1",
"shiki": "^3.21.0",
"shiki": "^3.2.0",
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"unified": "^11.0.5",
@@ -197,7 +196,6 @@
"@types/jsontoxml": "^1.0.5",
"@types/jsonwebtoken": "^9.0.6",
"@types/mdast": "^4.0.4",
"@types/negotiator": "^0.6.4",
"@types/node": "^20",
"@types/object-hash": "^3.0.6",
"@types/parse-cache-control": "^1.0.4",
@@ -220,13 +218,13 @@
},
"packages/icons": {
"name": "@gitbook/icons",
"version": "0.4.2",
"version": "0.3.4",
"bin": {
"gitbook-icons": "./bin/gitbook-icons.js",
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.1.0",
"@fortawesome/fontawesome-svg-core": "^7.1.0",
"@gitbook/fontawesome-pro": "1.0.16",
},
"devDependencies": {
"@types/react": "catalog:",
@@ -235,17 +233,20 @@
"tsdown": "catalog:",
"typescript": "catalog:",
},
"optionalDependencies": {
"@gitbook/fontawesome-pro": "1.0.16",
},
"peerDependencies": {
"react": "*",
},
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "3.0.7",
"version": "3.0.5",
"dependencies": {
"@scalar/json-magic": "^0.9.4",
"@scalar/openapi-parser": "^0.24.5",
"@scalar/openapi-types": "^0.5.3",
"@scalar/json-magic": "^0.8.1",
"@scalar/openapi-parser": "^0.23.2",
"@scalar/openapi-types": "^0.5.1",
},
"devDependencies": {
"@tsconfig/node20": "catalog:",
@@ -258,7 +259,7 @@
},
"packages/react-contentkit": {
"name": "@gitbook/react-contentkit",
"version": "0.7.11",
"version": "0.7.8",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:*",
@@ -300,7 +301,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.5.6",
"version": "1.5.3",
"dependencies": {
"@gitbook/expr": "workspace:*",
"@gitbook/openapi-parser": "workspace:*",
@@ -328,7 +329,7 @@
"typescript": "catalog:",
},
"peerDependencies": {
"react": ">=17",
"react": "*",
"react-dom": "*",
},
},
@@ -345,7 +346,7 @@
"react-dom": "catalog:",
},
"catalog": {
"@gitbook/api": "0.159.0",
"@gitbook/api": "0.154.0",
"@scalar/api-client-react": "^1.3.46",
"@tsconfig/node20": "^20.1.6",
"@tsconfig/strictest": "^2.0.6",
@@ -538,21 +539,21 @@
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
"@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.0.14", "", { "dependencies": { "@changesets/config": "^3.1.2", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA=="],
"@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.0.13", "", { "dependencies": { "@changesets/config": "^3.1.1", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg=="],
"@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.9", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ=="],
"@changesets/changelog-git": ["@changesets/changelog-git@0.2.1", "", { "dependencies": { "@changesets/types": "^6.1.0" } }, "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q=="],
"@changesets/cli": ["@changesets/cli@2.29.8", "", { "dependencies": { "@changesets/apply-release-plan": "^7.0.14", "@changesets/assemble-release-plan": "^6.0.9", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.2", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/get-release-plan": "^4.0.14", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.6", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@inquirer/external-editor": "^1.0.2", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "ci-info": "^3.7.0", "enquirer": "^2.4.1", "fs-extra": "^7.0.1", "mri": "^1.2.0", "p-limit": "^2.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA=="],
"@changesets/cli": ["@changesets/cli@2.29.7", "", { "dependencies": { "@changesets/apply-release-plan": "^7.0.13", "@changesets/assemble-release-plan": "^6.0.9", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.1", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/get-release-plan": "^4.0.13", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@inquirer/external-editor": "^1.0.0", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "ci-info": "^3.7.0", "enquirer": "^2.4.1", "fs-extra": "^7.0.1", "mri": "^1.2.0", "p-limit": "^2.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ=="],
"@changesets/config": ["@changesets/config@3.1.2", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/logger": "^0.1.1", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1", "micromatch": "^4.0.8" } }, "sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog=="],
"@changesets/config": ["@changesets/config@3.1.1", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/logger": "^0.1.1", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1", "micromatch": "^4.0.8" } }, "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA=="],
"@changesets/errors": ["@changesets/errors@0.2.0", "", { "dependencies": { "extendable-error": "^0.1.5" } }, "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow=="],
"@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@2.1.3", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "picocolors": "^1.1.0", "semver": "^7.5.3" } }, "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ=="],
"@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.14", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.9", "@changesets/config": "^3.1.2", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.6", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g=="],
"@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.13", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.9", "@changesets/config": "^3.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg=="],
"@changesets/get-version-range-type": ["@changesets/get-version-range-type@0.4.0", "", {}, "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ=="],
@@ -560,11 +561,11 @@
"@changesets/logger": ["@changesets/logger@0.1.1", "", { "dependencies": { "picocolors": "^1.1.0" } }, "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg=="],
"@changesets/parse": ["@changesets/parse@0.4.2", "", { "dependencies": { "@changesets/types": "^6.1.0", "js-yaml": "^4.1.1" } }, "sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA=="],
"@changesets/parse": ["@changesets/parse@0.4.1", "", { "dependencies": { "@changesets/types": "^6.1.0", "js-yaml": "^3.13.1" } }, "sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q=="],
"@changesets/pre": ["@changesets/pre@2.0.2", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1" } }, "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug=="],
"@changesets/read": ["@changesets/read@0.6.6", "", { "dependencies": { "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/parse": "^0.4.2", "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "p-filter": "^2.1.0", "picocolors": "^1.1.0" } }, "sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg=="],
"@changesets/read": ["@changesets/read@0.6.5", "", { "dependencies": { "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/parse": "^0.4.1", "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "p-filter": "^2.1.0", "picocolors": "^1.1.0" } }, "sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg=="],
"@changesets/should-skip-package": ["@changesets/should-skip-package@0.1.2", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw=="],
@@ -722,9 +723,11 @@
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="],
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@7.1.0", "", {}, "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA=="],
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA=="],
"@gitbook/api": ["@gitbook/api@0.159.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-IRZXT2NAcOlKlCcGnzRH4w82AuBXam5I7lw84qfCRbNDDycvFNBwIVMHu3JMOcGUTCkzj7QDElaNKYirsWDwFQ=="],
"@gitbook/api": ["@gitbook/api@0.154.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-huEgWWa2H+H4ofYEAQl9r49u0gVjDHHWVNJKhd6SgGCfefjNPHkQj9zaky/5DBQzlu/+SDmutWiOtYdrykQO8w=="],
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
@@ -870,7 +873,7 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="],
"@next/env": ["@next/env@15.4.11", "", {}, "sha512-mIYp/091eYfPFezKX7ZPTWqrmSXq+ih6+LcUyKvLmeLQGhlPtot33kuEOd4U+xAA7sFfj21+OtCpIZx0g5SpvQ=="],
"@next/env": ["@next/env@15.4.10", "", {}, "sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A=="],
@@ -1284,23 +1287,23 @@
"@scalar/draggable": ["@scalar/draggable@0.2.0", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-UetHRB5Bqo5egVYlS21roWBcICmyk8CKh2htsidO+bFGAjl2e7Te+rY0borhNrMclr0xezHlPuLpEs1dvgLS2g=="],
"@scalar/helpers": ["@scalar/helpers@0.2.9", "", {}, "sha512-Y4ffJF0yELdwZ0BKgonqn3SumIgRn1WKyYCVHD+TDM7qRFChdGRypyt20+efHs26fmJeyBAIIv2laICj5uimiw=="],
"@scalar/helpers": ["@scalar/helpers@0.1.1", "", {}, "sha512-eJjuCI5djqU0adTwrHvpDf0xuwNRpwZinCfJ03QjnmIFBM9Ch0u3tn/0EZOhcVNbdEyJ+3yvSVy1dCmmvgtpvQ=="],
"@scalar/icons": ["@scalar/icons@0.4.7", "", { "dependencies": { "@phosphor-icons/core": "^2.1.1", "@types/node": "^22.9.0", "chalk": "^5.4.1", "vue": "^3.5.17" } }, "sha512-0qXPGRdZ180TMfejWCPYy7ILszBrAraq4KBhPtcM12ghc5qkncFWWpTm5yXI/vrbm10t7wvtTK08CLZ36CnXlQ=="],
"@scalar/import": ["@scalar/import@0.4.31", "", { "dependencies": { "@scalar/helpers": "0.0.12", "@scalar/openapi-parser": "0.22.3", "yaml": "2.8.0" } }, "sha512-3+3dHz+EvS/C4YEQ8eEihZFVxRVZODsMEHsp48tvXTaIs1Qxb4kf3Nkaf9E3k+6rpM2CUrF59K1P8LvhXob48w=="],
"@scalar/json-magic": ["@scalar/json-magic@0.9.4", "", { "dependencies": { "@scalar/helpers": "0.2.9", "yaml": "^2.8.0" } }, "sha512-PyfyWrH4ZkW0TM1ColiiHj4NRF8hUM61H0UzAkHLhRNnKFxi6hI+oqNrwqPnyk93hrpkpTRHC7Fl5T0BRwuzVg=="],
"@scalar/json-magic": ["@scalar/json-magic@0.8.1", "", { "dependencies": { "@scalar/helpers": "0.1.1", "xxhash-wasm": "^1.1.0", "yaml": "2.8.0" } }, "sha512-PtG0gJxw+iE9492pqy9V+ZbnfRnycIxTAxknKQCOGRyFYfDvYSs0/uyFCHoQPl7HEZoNNA7q/fr0LBZXMRzv7g=="],
"@scalar/oas-utils": ["@scalar/oas-utils@0.6.3", "", { "dependencies": { "@hyperjump/browser": "^1.1.0", "@hyperjump/json-schema": "^1.9.6", "@scalar/helpers": "0.1.1", "@scalar/json-magic": "0.8.1", "@scalar/object-utils": "1.2.11", "@scalar/openapi-types": "0.5.1", "@scalar/themes": "0.13.23", "@scalar/types": "0.4.0", "@scalar/workspace-store": "0.20.0", "@types/har-format": "^1.2.15", "flatted": "^3.3.3", "js-base64": "^3.7.8", "microdiff": "^1.5.0", "nanoid": "5.1.5", "type-fest": "5.0.0", "yaml": "2.8.0", "zod": "4.1.11" } }, "sha512-+hEMZSLuTeXlrPqpajusZn2R8qs/OrH2Xstllr3GOCnJz7ATwniD69DT7Il49L1j2HJEtX4EpleRX9OMhdoWHg=="],
"@scalar/object-utils": ["@scalar/object-utils@1.2.11", "", { "dependencies": { "@scalar/helpers": "0.1.1", "flatted": "^3.3.3", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1", "type-fest": "5.0.0" } }, "sha512-V44CbfjNuYhoVmVZ36DDlTfyBNF8dMYzLTcDz5zvoSsx+SViXE8ScMd+kgUMHCtSJZV6izV2x3UzIbGQY/pWsQ=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.24.5", "", { "dependencies": { "@scalar/json-magic": "0.9.4", "@scalar/openapi-types": "0.5.3", "@scalar/openapi-upgrader": "0.1.8", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.8.0" } }, "sha512-pTeKnmhVdSIfG3vysgDm6jsKc7Do1vXdy/4aqp7j8AEzXllf8RZjSgRSUhtvFYFQCr27fDZ117V3WPQUYtgmCw=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.23.2", "", { "dependencies": { "@scalar/json-magic": "0.8.1", "@scalar/openapi-types": "0.5.1", "@scalar/openapi-upgrader": "0.1.4", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "2.8.0" } }, "sha512-NzMOWm6sae+viN8luEUqplsc0rO9XdStUM/TY1+o+5gz8KPrDc8/Wh+ksFyfGi0lnwn8GHwi7NVDrBDL5qkXCA=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.5.3", "", { "dependencies": { "zod": "^4.1.11" } }, "sha512-m4n/Su3K01d15dmdWO1LlqecdSPKuNjuokrJLdiQ485kW/hRHbXW1QP6tJL75myhw/XhX5YhYAR+jrwnGjXiMw=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.5.1", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-8g7s9lPolyDFtijyh3Ob459tpezPuZbkXoFgJwBTHjPZ7ap+TvOJTvLk56CFwxVBVz2BxCzWJqxYyy3FUdeLoA=="],
"@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.8", "", { "dependencies": { "@scalar/openapi-types": "0.5.3" } }, "sha512-2xuYLLs0fBadLIk4I1ObjMiCnOyLPEMPf24A1HtHQvhKGDnGlvT63F2rU2Xw8lxCjgHnzveMPnOJEbwIy64RCg=="],
"@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.4", "", { "dependencies": { "@scalar/openapi-types": "0.5.1" } }, "sha512-OKSjey1U99BTg1ZTiNL1xxOEOrP9U4aRTH7Pf6JFXpqFH8kGdhrDAIA0uogYYzNq65BaQwK+h31fSrIf/yCLCg=="],
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.3.40", "", { "dependencies": { "@scalar/helpers": "0.0.12", "@scalar/oas-utils": "0.5.2", "@scalar/openapi-types": "0.5.0" } }, "sha512-FP1p2/mb0Y5GM9hc+TI9ldDM44VV9GHwdhJQ8xGpArtlt8nxtyKmncOXcgayBD7qk3ohV6W1Eftsr258Eq7gGQ=="],
@@ -1320,17 +1323,17 @@
"@scalar/workspace-store": ["@scalar/workspace-store@0.20.0", "", { "dependencies": { "@scalar/code-highlight": "0.2.0", "@scalar/helpers": "0.1.1", "@scalar/json-magic": "0.8.1", "@scalar/openapi-upgrader": "0.1.4", "@scalar/snippetz": "0.5.2", "@scalar/themes": "0.13.23", "@scalar/typebox": "0.1.1", "@scalar/types": "0.4.0", "github-slugger": "^2.0.0", "type-fest": "5.0.0", "vue": "^3.5.21", "yaml": "2.8.0" } }, "sha512-RklQSKjz5V/69rKjxQV70q4u34qzmUZVtWGUJLVvHxPsPxCtxPPRLYY+gdQDkGjTQYol/dR9xyvi2QGk3OWTKA=="],
"@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="],
"@shikijs/core": ["@shikijs/core@3.2.0", "", { "dependencies": { "@shikijs/types": "3.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-+5dPz8q6HgNqfQ28ycm/vA8dIVd2lNFOUqVRFCQLbs0KZ6emYI+1apLpX+wuL/aDSPLOkMgARwNjkA5UjGKS1Q=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.2.0", "", { "dependencies": { "@shikijs/types": "3.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.1.0" } }, "sha512-1WrYfaz5YT5aTAIMbYQhxlSHc8ArX+hCDNAIdKRqJHzfWQ3xDgh3PTvrAly+RWGuvi5Q4NlvPlTBdlSAXN6Stg=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.2.0", "", { "dependencies": { "@shikijs/types": "3.2.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-3V7ko+YUAP02I4rUbDjCgvyM/H85hUIZBQAS19FjDcJMKL5SbjWTiG7TRKxX1V4ddxLxt2RO64wZinElp/3ngQ=="],
"@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="],
"@shikijs/langs": ["@shikijs/langs@3.2.0", "", { "dependencies": { "@shikijs/types": "3.2.0" } }, "sha512-Qze5YIsp223AmC69VZDQolcrcYPrVa9wV6cW2kVqsDrSWlwhW2EQZEn1Iw2oQU1tGYVg8Hj/xdp8mOv+9zI0vg=="],
"@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="],
"@shikijs/themes": ["@shikijs/themes@3.2.0", "", { "dependencies": { "@shikijs/types": "3.2.0" } }, "sha512-XfzMSTu6iMl2FZIwKykld2OzFKDDlm4KbZrzW6sbKXEeJ1xq61HX4x4bE4+REBFqbbrvAQM8EAH11m/E3cxYDg=="],
"@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="],
"@shikijs/types": ["@shikijs/types@3.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-1uOwfEO0vV+G8n/AO/6Yth7zshNdXvQ1pc4ygTrfE3cyuzVLukrZq72YkFUlsRijam7LvRTvnqL4aT5wx1X2Vw=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
@@ -1476,6 +1479,8 @@
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.16", "", { "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.10.8", "", {}, "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA=="],
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.10.8", "", { "dependencies": { "@tanstack/virtual-core": "3.10.8" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-DB5QA8c/LfqOqIUCpSs3RdOTVroRRdqeHMqBkYrcashSZtOzIv8xbiqHgg7RYxDfkH5F3Y+e0MkuuyGNDVB0BQ=="],
@@ -1530,8 +1535,6 @@
"@types/ms": ["@types/ms@0.7.34", "", {}, "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="],
"@types/negotiator": ["@types/negotiator@0.6.4", "", {}, "sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A=="],
"@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="],
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
@@ -1898,6 +1901,8 @@
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="],
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
@@ -2240,7 +2245,7 @@
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
@@ -2318,6 +2323,8 @@
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"lodash.castarray": ["lodash.castarray@4.4.0", "", {}, "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="],
"lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="],
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
@@ -2334,6 +2341,8 @@
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
@@ -2508,7 +2517,7 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next": ["next@15.4.11", "", { "dependencies": { "@next/env": "15.4.11", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "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-IJRyXal45mIsshZI5XJne/intjusslUP1F+FHVBIyMGEqbYtIq1Irdx5vdWBBg58smviPDycmDeV6txsfkv1RQ=="],
"next": ["next@15.4.10", "", { "dependencies": { "@next/env": "15.4.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "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-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ=="],
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
@@ -2550,9 +2559,9 @@
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
"oniguruma-parser": ["oniguruma-parser@0.5.4", "", {}, "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA=="],
"oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
"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=="],
@@ -2772,7 +2781,7 @@
"shell-quote": ["shell-quote@1.8.1", "", {}, "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA=="],
"shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="],
"shiki": ["shiki@3.2.0", "", { "dependencies": { "@shikijs/core": "3.2.0", "@shikijs/engine-javascript": "3.2.0", "@shikijs/engine-oniguruma": "3.2.0", "@shikijs/langs": "3.2.0", "@shikijs/themes": "3.2.0", "@shikijs/types": "3.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lOF6wkvZCRVQrdfGilyXclTKIjCWKujPAjD6fddLwtQ6eSmgj43pFDbjUmxivtElDRlsGO8G2dLeeRpwNY4wWg=="],
"side-channel": ["side-channel@1.0.6", "", { "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" } }, "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA=="],
@@ -2906,19 +2915,19 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"turbo": ["turbo@2.7.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.6", "turbo-darwin-arm64": "2.7.6", "turbo-linux-64": "2.7.6", "turbo-linux-arm64": "2.7.6", "turbo-windows-64": "2.7.6", "turbo-windows-arm64": "2.7.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-PO9AvJLEsNLO+EYhF4zB+v10hOjsJe5kJW+S6tTbRv+TW7gf1Qer4mfjP9h3/y9h8ZiPvOrenxnEgDtFgaM5zw=="],
"turbo": ["turbo@2.6.1", "", { "optionalDependencies": { "turbo-darwin-64": "2.6.1", "turbo-darwin-arm64": "2.6.1", "turbo-linux-64": "2.6.1", "turbo-linux-arm64": "2.6.1", "turbo-windows-64": "2.6.1", "turbo-windows-arm64": "2.6.1" }, "bin": { "turbo": "bin/turbo" } }, "sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA=="],
"turbo-darwin-64": ["turbo-darwin-64@2.7.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-bYu0qnWju2Ha3EbIkPCk1SMLT3sltKh1P/Jy5FER6BmH++H5z+T5MHh3W1Xoers9rk4N1VdKvog9FO1pxQyjhw=="],
"turbo-darwin-64": ["turbo-darwin-64@2.6.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.7.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-KCxTf3Y1hgNLYIWRLw8bwH8Zie9RyCGoxAlXYsCBI/YNqBSR+ZZK9KYzFxAqDaVaNvTwLFv3rJRGsXOFWg4+Uw=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.6.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw=="],
"turbo-linux-64": ["turbo-linux-64@2.7.6", "", { "os": "linux", "cpu": "x64" }, "sha512-vjoU8zIfNgvJR3cMitgw7inEoi6bmuVuFawDl5yKtxjAEhDktFdRBpGS3WojD4l3BklBbIK689ssXcGf21LxRA=="],
"turbo-linux-64": ["turbo-linux-64@2.6.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.7.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-TcMpBvTqZf+1DptrVYLbZls7WY1UVNDTGaf0bo7/GCgWYv5eZHCVo4Td7kCJeDU4glbXg67REX0md0S0V6ghMg=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.6.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA=="],
"turbo-windows-64": ["turbo-windows-64@2.7.6", "", { "os": "win32", "cpu": "x64" }, "sha512-1/MhkYldiihjneY8QnnDMbAkHXn/udTWSVYS94EMlkE9AShozsLTTOT1gDOpX06EfEW5njP09suhMvxbvwuwpQ=="],
"turbo-windows-64": ["turbo-windows-64@2.6.1", "", { "os": "win32", "cpu": "x64" }, "sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.7.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-0wDVnUJLFAWm4ZzOQFDkbyyUqaszorTGf3Rdc22IRIyJTTLd6ajqdb+cWD89UZ1RKr953+PZR1gqgWQY4PDuhA=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.6.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q=="],
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
@@ -3054,7 +3063,7 @@
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
"yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
@@ -3600,6 +3609,8 @@
"@changesets/logger/picocolors": ["picocolors@1.1.0", "", {}, "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="],
"@changesets/parse/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"@codemirror/lang-html/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="],
"@codemirror/lang-html/@codemirror/lang-css": ["@codemirror/lang-css@6.3.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA=="],
@@ -3708,6 +3719,8 @@
"@opennextjs/aws/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"@opennextjs/aws/yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
@@ -3884,8 +3897,6 @@
"@scalar/api-client/@scalar/workspace-store": ["@scalar/workspace-store@0.17.1", "", { "dependencies": { "@scalar/code-highlight": "0.2.0", "@scalar/helpers": "0.0.12", "@scalar/json-magic": "0.6.1", "@scalar/openapi-upgrader": "0.1.3", "@scalar/snippetz": "0.5.1", "@scalar/typebox": "0.1.1", "@scalar/types": "0.3.2", "github-slugger": "^2.0.0", "type-fest": "5.0.0", "vue": "^3.5.17", "yaml": "2.8.0" } }, "sha512-IWWudWionjVT2JNl+xin9zuoR/I5+f84myt8uzCCKj2PACEjitZvXYwwnqhnLMPPWYI8FQ5dncGKg0zUgRL5zQ=="],
"@scalar/api-client/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@scalar/api-client/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/api-client-react/@scalar/types": ["@scalar/types@0.3.2", "", { "dependencies": { "@scalar/openapi-types": "0.5.0", "nanoid": "5.1.5", "type-fest": "5.0.0", "zod": "4.1.11" } }, "sha512-+X10CCvG57nAqYbTGteiSzRFQcMYm7DLfCRMeEfiWQ9Bq2ladat17XsMSvkvwcfpOSlsoepWf3P5dErERUSOQQ=="],
@@ -3904,20 +3915,8 @@
"@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.22.3", "", { "dependencies": { "@scalar/json-magic": "0.6.1", "@scalar/openapi-types": "0.5.0", "@scalar/openapi-upgrader": "0.1.3", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "2.8.0" } }, "sha512-5Znbx9HVJb7EV9EJXJrUj+cs116QIBwX/hxkyaiLaaDL2w5S+z1rjtV+d0Jv7382FCtzAtfv/9llVuxZYPVqXA=="],
"@scalar/import/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@scalar/oas-utils/@scalar/helpers": ["@scalar/helpers@0.1.1", "", {}, "sha512-eJjuCI5djqU0adTwrHvpDf0xuwNRpwZinCfJ03QjnmIFBM9Ch0u3tn/0EZOhcVNbdEyJ+3yvSVy1dCmmvgtpvQ=="],
"@scalar/oas-utils/@scalar/json-magic": ["@scalar/json-magic@0.8.1", "", { "dependencies": { "@scalar/helpers": "0.1.1", "xxhash-wasm": "^1.1.0", "yaml": "2.8.0" } }, "sha512-PtG0gJxw+iE9492pqy9V+ZbnfRnycIxTAxknKQCOGRyFYfDvYSs0/uyFCHoQPl7HEZoNNA7q/fr0LBZXMRzv7g=="],
"@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.5.1", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-8g7s9lPolyDFtijyh3Ob459tpezPuZbkXoFgJwBTHjPZ7ap+TvOJTvLk56CFwxVBVz2BxCzWJqxYyy3FUdeLoA=="],
"@scalar/oas-utils/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@scalar/oas-utils/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/object-utils/@scalar/helpers": ["@scalar/helpers@0.1.1", "", {}, "sha512-eJjuCI5djqU0adTwrHvpDf0xuwNRpwZinCfJ03QjnmIFBM9Ch0u3tn/0EZOhcVNbdEyJ+3yvSVy1dCmmvgtpvQ=="],
"@scalar/openapi-types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/postman-to-openapi/@scalar/helpers": ["@scalar/helpers@0.0.12", "", {}, "sha512-4NDmHShyi1hrVRsJCdRZT/FIpy+/5PFbVbQLRYX/pjpu5cYqHBj9s6n5RI6gGDXEBHAIFi63g9FC6Isgr66l1Q=="],
@@ -3928,24 +3927,14 @@
"@scalar/snippetz/@scalar/types": ["@scalar/types@0.3.2", "", { "dependencies": { "@scalar/openapi-types": "0.5.0", "nanoid": "5.1.5", "type-fest": "5.0.0", "zod": "4.1.11" } }, "sha512-+X10CCvG57nAqYbTGteiSzRFQcMYm7DLfCRMeEfiWQ9Bq2ladat17XsMSvkvwcfpOSlsoepWf3P5dErERUSOQQ=="],
"@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.1", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-8g7s9lPolyDFtijyh3Ob459tpezPuZbkXoFgJwBTHjPZ7ap+TvOJTvLk56CFwxVBVz2BxCzWJqxYyy3FUdeLoA=="],
"@scalar/types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/use-hooks/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
"@scalar/use-toasts/vue": ["vue@3.5.12", "", { "dependencies": { "@vue/compiler-dom": "3.5.12", "@vue/compiler-sfc": "3.5.12", "@vue/runtime-dom": "3.5.12", "@vue/server-renderer": "3.5.12", "@vue/shared": "3.5.12" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg=="],
"@scalar/workspace-store/@scalar/helpers": ["@scalar/helpers@0.1.1", "", {}, "sha512-eJjuCI5djqU0adTwrHvpDf0xuwNRpwZinCfJ03QjnmIFBM9Ch0u3tn/0EZOhcVNbdEyJ+3yvSVy1dCmmvgtpvQ=="],
"@scalar/workspace-store/@scalar/json-magic": ["@scalar/json-magic@0.8.1", "", { "dependencies": { "@scalar/helpers": "0.1.1", "xxhash-wasm": "^1.1.0", "yaml": "2.8.0" } }, "sha512-PtG0gJxw+iE9492pqy9V+ZbnfRnycIxTAxknKQCOGRyFYfDvYSs0/uyFCHoQPl7HEZoNNA7q/fr0LBZXMRzv7g=="],
"@scalar/workspace-store/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.4", "", { "dependencies": { "@scalar/openapi-types": "0.5.1" } }, "sha512-OKSjey1U99BTg1ZTiNL1xxOEOrP9U4aRTH7Pf6JFXpqFH8kGdhrDAIA0uogYYzNq65BaQwK+h31fSrIf/yCLCg=="],
"@scalar/workspace-store/@scalar/snippetz": ["@scalar/snippetz@0.5.2", "", { "dependencies": { "@scalar/types": "0.4.0", "js-base64": "^3.7.8", "stringify-object": "^5.0.0" } }, "sha512-DwwowT9si0vrszKbjm8SXL5O6nE6isHbG7Vos85ohd6T4g4h5AYY4+SUyjyRAjzbEISd6A0+VJXaWz/g4PpbZg=="],
"@scalar/workspace-store/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
"@smithy/chunked-blob-reader-native/@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="],
@@ -4032,6 +4021,8 @@
"@tailwindcss/postcss/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
"@ts-morph/common/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"@ts-morph/common/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
@@ -4120,8 +4111,6 @@
"convict/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
"cosmiconfig/js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"dunder-proto/gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
@@ -4778,6 +4767,8 @@
"@babel/highlight/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"@changesets/parse/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"@codemirror/lang-json/@codemirror/language/@codemirror/view": ["@codemirror/view@6.34.1", "", { "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ=="],
"@codemirror/lang-json/@codemirror/language/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
@@ -4918,8 +4909,6 @@
"@scalar/components/@scalar/oas-utils/@scalar/workspace-store": ["@scalar/workspace-store@0.17.1", "", { "dependencies": { "@scalar/code-highlight": "0.2.0", "@scalar/helpers": "0.0.12", "@scalar/json-magic": "0.6.1", "@scalar/openapi-upgrader": "0.1.3", "@scalar/snippetz": "0.5.1", "@scalar/typebox": "0.1.1", "@scalar/types": "0.3.2", "github-slugger": "^2.0.0", "type-fest": "5.0.0", "vue": "^3.5.17", "yaml": "2.8.0" } }, "sha512-IWWudWionjVT2JNl+xin9zuoR/I5+f84myt8uzCCKj2PACEjitZvXYwwnqhnLMPPWYI8FQ5dncGKg0zUgRL5zQ=="],
"@scalar/components/@scalar/oas-utils/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@scalar/components/@scalar/oas-utils/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/components/@scalar/themes/@scalar/types": ["@scalar/types@0.3.2", "", { "dependencies": { "@scalar/openapi-types": "0.5.0", "nanoid": "5.1.5", "type-fest": "5.0.0", "zod": "4.1.11" } }, "sha512-+X10CCvG57nAqYbTGteiSzRFQcMYm7DLfCRMeEfiWQ9Bq2ladat17XsMSvkvwcfpOSlsoepWf3P5dErERUSOQQ=="],
@@ -4950,8 +4939,6 @@
"@scalar/postman-to-openapi/@scalar/oas-utils/@scalar/workspace-store": ["@scalar/workspace-store@0.17.1", "", { "dependencies": { "@scalar/code-highlight": "0.2.0", "@scalar/helpers": "0.0.12", "@scalar/json-magic": "0.6.1", "@scalar/openapi-upgrader": "0.1.3", "@scalar/snippetz": "0.5.1", "@scalar/typebox": "0.1.1", "@scalar/types": "0.3.2", "github-slugger": "^2.0.0", "type-fest": "5.0.0", "vue": "^3.5.17", "yaml": "2.8.0" } }, "sha512-IWWudWionjVT2JNl+xin9zuoR/I5+f84myt8uzCCKj2PACEjitZvXYwwnqhnLMPPWYI8FQ5dncGKg0zUgRL5zQ=="],
"@scalar/postman-to-openapi/@scalar/oas-utils/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"@scalar/postman-to-openapi/@scalar/oas-utils/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/postman-to-openapi/@scalar/openapi-types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -4970,8 +4957,6 @@
"@scalar/use-toasts/vue/@vue/shared": ["@vue/shared@3.5.12", "", {}, "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="],
"@scalar/workspace-store/@scalar/openapi-upgrader/@scalar/openapi-types": ["@scalar/openapi-types@0.5.1", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-8g7s9lPolyDFtijyh3Ob459tpezPuZbkXoFgJwBTHjPZ7ap+TvOJTvLk56CFwxVBVz2BxCzWJqxYyy3FUdeLoA=="],
"@shikijs/core/hast-util-to-html/property-information": ["property-information@7.0.0", "", {}, "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg=="],
"@smithy/chunked-blob-reader-native/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="],
@@ -5346,8 +5331,6 @@
"@scalar/use-toasts/vue/@vue/compiler-sfc/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"@scalar/workspace-store/@scalar/openapi-upgrader/@scalar/openapi-types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="],
"@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="],
+2
View File
@@ -0,0 +1,2 @@
[install.scopes]
"gitbook" = { token = "$BUN_NPM_TOKEN", url = "https://registry.npmjs.org" }
+4 -4
View File
@@ -6,11 +6,11 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.29.8",
"turbo": "^2.7.6",
"@changesets/cli": "^2.29.7",
"turbo": "^2.6.1",
"vercel": "^39.4.2"
},
"packageManager": "bun@1.3.7",
"packageManager": "bun@1.3.2",
"overrides": {
"@codemirror/state": "6.4.1",
"@types/react": "catalog:",
@@ -41,7 +41,7 @@
"catalog": {
"@tsconfig/strictest": "^2.0.6",
"@tsconfig/node20": "^20.1.6",
"@gitbook/api": "0.159.0",
"@gitbook/api": "0.154.0",
"@scalar/api-client-react": "^1.3.46",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
-11
View File
@@ -1,16 +1,5 @@
# @gitbook/embed
## 0.2.0
### Minor Changes
- 1430cee: Improve Docs Embed with separate Assistant and Docs tabs
### Patch Changes
- Updated dependencies [62438f4]
- @gitbook/icons@0.4.0
## 0.1.5
### Patch Changes
+1 -3
View File
@@ -13,7 +13,7 @@
}
},
"sideEffects": false,
"version": "0.2.0",
"version": "0.1.5",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -23,7 +23,6 @@
"react": "*"
},
"devDependencies": {
"bun-types": "catalog:",
"@types/react": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
@@ -34,7 +33,6 @@
"build-lib": "tsdown",
"build-standalone": "bun build src/standalone/index.ts --bundle --minify --outdir=standalone",
"clean": "rm -rf ./dist",
"unit": "bun test",
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
@@ -1,38 +0,0 @@
import { describe, expect, it } from 'bun:test';
import { createGitBook } from './createGitBook';
describe('createGitBook.getFrameURL', () => {
it('builds the embed URL when the site URL has no trailing slash', () => {
const client = createGitBook({ siteURL: 'https://example.com/docs' });
const url = new URL(client.getFrameURL({}));
expect(url.origin).toBe('https://example.com');
expect(url.pathname).toBe('/docs/~gitbook/embed');
expect(url.searchParams.toString()).toBe('');
});
it('handles a trailing slash and adds visitor parameters', () => {
const client = createGitBook({ siteURL: 'https://example.com/docs/' });
const url = new URL(
client.getFrameURL({
visitor: {
token: 'signed-token',
unsignedClaims: {
role: 'editor',
count: 3,
enabled: false,
},
},
})
);
expect(url.pathname).toBe('/docs/~gitbook/embed');
expect(url.searchParams.get('jwt_token')).toBe('signed-token');
expect(url.searchParams.get('visitor.role')).toBe('editor');
expect(url.searchParams.get('visitor.count')).toBe('3');
expect(url.searchParams.get('visitor.enabled')).toBe('false');
});
});
+1 -1
View File
@@ -43,7 +43,7 @@ export function createGitBook(options: CreateGitBookOptions) {
url.pathname = `${url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/`}~gitbook/embed`;
if (frameOptions.visitor?.token) {
url.searchParams.set('jwt_token', frameOptions.visitor.token);
url.searchParams.set('token', frameOptions.visitor.token);
}
if (frameOptions.visitor?.unsignedClaims) {
-1
View File
@@ -1,2 +1 @@
export * from './client';
export * from './standalone';
+1 -3
View File
@@ -15,9 +15,7 @@
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"types": [
"bun-types" // add Bun global
]
"types": []
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
-68
View File
@@ -1,73 +1,5 @@
# gitbook
## 0.21.3
### Patch Changes
- Updated dependencies [bf63045]
- @gitbook/react-openapi@1.5.6
## 0.21.2
### Patch Changes
- ca68236: Fix 404 when accessing rss.xml for the root page.
- 21851d3: Round page cover corners on extrawide screens
- 695e99f: Update button sizing
- 7517e98: Clean up button and input across the app
- 99d788e: Update page cover styling
- ebf3b2b: Fix an issue where content refs to spaces outside of site was resolving with share links
- 35efc9d: Fix TOCPageIcons backgrounds on gradient and bold themes
- a7f58ab: Fix embed tab button size regression
- 360b525: Switch icon masking method to fix visual cutoffs in FA7
- Updated dependencies [985c1b4]
- Updated dependencies [360b525]
- @gitbook/openapi-parser@3.0.7
- @gitbook/icons@0.4.2
- @gitbook/react-openapi@1.5.5
- @gitbook/react-contentkit@0.7.11
## 0.21.1
### Patch Changes
- Updated dependencies [e7fb771]
- @gitbook/icons@0.4.1
- @gitbook/react-contentkit@0.7.10
## 0.21.0
### Minor Changes
- 3553db0: Add a RSS feed to all pages with update blocks.
### Patch Changes
- f478ddc: Add Input component
- 3817289: Fix overflowing section groups
- 86e7aa6: Fix search results cursor
- 1430cee: Improve Docs Embed with separate Assistant and Docs tabs
- ed9e8fe: Add support for button actions
- 9c69cc4: Add customizable suggested questions
- c51076e: Fix links to other spaces and root page in embeddable view.
- 6278a18: Support `greeting` and fix suggested questions if there are no custom ones defined
- 04a4175: Remove hidden site spaces from navigation
- 74f8931: Fix hidden section not found
- 344842f: Improve OpenAPI circular references
- Updated dependencies [4766092]
- Updated dependencies [8761cee]
- Updated dependencies [461e15f]
- Updated dependencies [1430cee]
- Updated dependencies [62438f4]
- Updated dependencies [3e40b4d]
- Updated dependencies [87d68ea]
- Updated dependencies [344842f]
- @gitbook/react-openapi@1.5.4
- @gitbook/openapi-parser@3.0.6
- @gitbook/embed@0.2.0
- @gitbook/icons@0.4.0
- @gitbook/react-contentkit@0.7.9
## 0.20.0
### Minor Changes
+17 -12
View File
@@ -10,18 +10,18 @@ const testCases: TestsCase[] = [
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
],
},
{
name: 'Nexthink',
contentBaseURL: 'https://docs.nexthink.com',
tests: [
{
name: 'Home',
url: '/',
screenshot: { waitForTOCScrolling: false },
run: waitForCookiesDialog,
},
],
},
// {
// name: 'Nexthink',
// contentBaseURL: 'https://docs.nexthink.com',
// tests: [
// {
// name: 'Home',
// url: '/',
// screenshot: { waitForTOCScrolling: false },
// run: waitForCookiesDialog,
// },
// ],
// },
{
name: 'asiksupport-stg.dto.kemkes.go.id',
contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id',
@@ -37,6 +37,11 @@ const testCases: TestsCase[] = [
contentBaseURL: 'https://faq.deltaemulator.com',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.dify.ai',
contentBaseURL: 'https://docs.dify.ai',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'seeddao.gitbook.io',
contentBaseURL: 'https://seeddao.gitbook.io',
+71 -407
View File
@@ -8,9 +8,7 @@ import {
CustomizationIconsStyle,
CustomizationSidebarListStyle,
CustomizationThemeMode,
SiteSocialAccountPlatform,
} from '@gitbook/api';
import type { GitBookStandalone } from '@gitbook/embed';
import { expect } from '@playwright/test';
import jwt from 'jsonwebtoken';
@@ -42,7 +40,7 @@ import {
const AI_PROMPT = `You're being invoked by the GitBook CI/CD pipeline. To make screenshot testing of the GitBook Assistant visually consistent, look up the title of the first page you find and respond with only EXACTLY its title. To find the page title, invoke the search tool with the query "GitBook". Before invoking the search tool, respond with the exact text: "I'm going to look up 'GitBook' and then respond with only the page title.". Do not execute any other tools or output any other text.`;
const overrideAIInitialState = () => {
const greeting = document.querySelector('[data-testid="ai-chat-greeting-title"]');
const greeting = document.querySelector('[data-testid="ai-chat-time-greeting"]');
if (greeting) {
greeting.textContent = 'Good morning';
}
@@ -274,10 +272,10 @@ const testCases: TestsCase[] = [
await expect(navigationLink).not.toBeVisible();
// Find and click the chevron element that is next to "Editor" in the TOC
// It is a button inside the link
// It is a span inside the link
const editorChevron = page
.getByRole('link', { name: 'Editor' })
.locator('button');
.locator('span');
await editorChevron.click();
// Verify "Navigation" link becomes visible after expansion
@@ -296,10 +294,10 @@ const testCases: TestsCase[] = [
await expect(navigationLink).not.toBeVisible();
// Find and click the chevron element that is next to "Editor" in the TOC
// It is a button inside the link
// It is a span inside the link
const editorChevron = page
.getByRole('link', { name: 'Editor' })
.locator('button');
.locator('span');
await editorChevron.click();
// At this stage the link should still not be visible
@@ -308,7 +306,7 @@ const testCases: TestsCase[] = [
// Then we click 'Content Structure' chevron to expand further
const contentStructureChevron = page
.getByRole('link', { name: 'Content Structure' })
.locator('button');
.locator('span');
await contentStructureChevron.click();
// Verify "Spaces" link becomes visible after expansion
@@ -678,6 +676,70 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'Markdown page',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'Text page',
url: 'text-page.md',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: 'llms.txt',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'llms.txt',
url: 'llms.txt',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: 'llms-full.txt',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'llms-full.txt',
url: 'llms-full.txt',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: '[page].md',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'blocks.md',
url: 'blocks.md',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: 'Site subdirectory (proxy)',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
@@ -1124,16 +1186,6 @@ const testCases: TestsCase[] = [
},
]),
},
{
name: 'Reusable contents',
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/reusable-contents/',
tests: [
{
name: 'All cases',
url: '',
},
],
},
{
name: 'Page actions',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
@@ -1150,36 +1202,6 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'Social links',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'With social links',
url: getCustomizationURL({
socialAccounts: [
{
platform: SiteSocialAccountPlatform.Github,
display: { footer: true },
handle: 'GitbookIO',
},
{
platform: SiteSocialAccountPlatform.Linkedin,
display: { footer: true },
handle: 'gitbook',
},
{
platform: SiteSocialAccountPlatform.Twitter,
display: { footer: false },
handle: 'GitBookIO',
},
],
}),
fullPage: true,
run: waitForCookiesDialog,
},
],
},
{
name: 'Ads',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
@@ -1269,7 +1291,7 @@ const testCases: TestsCase[] = [
name: 'Redirect to Quickstart page',
url: 'sections-2/redirect-test',
run: async (page) => {
await expect(page.locator('h1')).toContainText('Quickstart');
await expect(page.locator('h1')).toHaveText('Quickstart');
},
screenshot: false,
},
@@ -1295,46 +1317,6 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'Navigation of links on a share links site',
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/api-multi-versions-share-links/',
tests: [
{
name: 'Link in same site has the share link token preserved',
url: '8tNo6MeXg7CkFMzSSz81/link-in-same-site',
run: async (page) => {
const linkToDifferentPage = page.getByRole('link', { name: 'Other Page' });
await linkToDifferentPage.click();
await page.waitForURL((url) =>
url.pathname.includes(
'/api-multi-versions-share-links/8tNo6MeXg7CkFMzSSz81/3.0/other-page'
)
);
await expect(
page.getByRole('heading', { level: 1, name: 'Other Page' })
).toBeVisible();
},
screenshot: false,
},
{
name: 'Link to different site should not have the share link token preserved',
url: '8tNo6MeXg7CkFMzSSz81/link-in-different-site',
run: async (page) => {
const linkToDifferentSite = page.getByRole('link', { name: 'Basics' });
await linkToDifferentSite.click();
await page.waitForURL(
(url) =>
url.toString() ===
'https://gitbook-open-e2e-sites.gitbook.io/sections/sections-4/basics/editor'
);
await expect(
page.getByRole('heading', { level: 1, name: 'Editor' })
).toBeVisible();
},
screenshot: false,
},
],
},
{
name: 'Visitor Auth - Space',
contentBaseURL: 'https://gitbook.gitbook.io/gbo-va-space/',
@@ -1964,324 +1946,6 @@ const testCases: TestsCase[] = [
]),
],
},
{
name: 'Docs Embed - Basic',
contentBaseURL: 'https://gitbook.com/docs/~gitbook/embed/demo/',
tests: [
{
name: 'Standalone UX',
url: '',
run: async (page) => {
const button = page.locator('#gitbook-widget-button');
await expect(button).toBeVisible();
await expect(button.locator('#gitbook-widget-button-label')).toHaveText('Ask');
await expect(button.locator('#gitbook-widget-button-icon')).toHaveAttribute(
'data-icon',
'assistant'
);
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
await button.click(); // Toggle the window off for the screenshot
await expect(page.locator('#gitbook-widget-window')).not.toBeVisible();
},
},
{
name: 'Change standalone button label and icon',
url: '',
run: async (page) => {
await page.evaluate(() => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('configure', {
button: {
label: 'Docs',
icon: 'book',
},
});
});
const button = page.locator('#gitbook-widget-button');
await expect(button).toBeVisible();
button.click();
await expect(page.locator('#gitbook-widget-window')).not.toBeVisible();
await expect(page.locator('#gitbook-widget-button-label')).toHaveText('Docs');
await expect(page.locator('#gitbook-widget-button-icon')).toHaveAttribute(
'data-icon',
'book'
);
},
},
],
},
{
name: 'Docs Embed - Assistant + Docs',
contentBaseURL: 'https://gitbook.com/docs/~gitbook/embed/demo/',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
tests: [
{
name: 'Switch between tabs',
url: '',
run: async (page) => {
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
const iframe = page.frameLocator('#gitbook-widget-iframe');
await iframe.getByTestId('embed-tab-docs').click(); // Switch to docs tab
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible({
timeout: 20000,
});
await iframe.getByTestId('embed-tab-assistant').click(); // Switch to assistant tab
await expect(iframe.getByTestId('ai-chat')).toBeVisible();
await iframe.owner().evaluate(overrideAIInitialState);
},
},
{
name: 'API - navigateToPage',
url: '',
run: async (page) => {
await page.evaluate(() => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('navigateToPage', '/getting-started/quickstart');
});
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible({
timeout: 20000,
});
await expect(iframe.owner()).toHaveAttribute(
'src',
expect.stringContaining('getting-started/quickstart')
);
},
},
{
name: 'API - postUserMessage',
url: '',
run: async (page) => {
await page.evaluate((aiPrompt) => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('postUserMessage', aiPrompt);
}, AI_PROMPT);
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('ai-chat')).toBeVisible();
await expect(iframe.getByTestId('ai-chat-message-user').first()).toHaveText(
AI_PROMPT
);
await iframe.owner().evaluate(overrideAIResponse);
},
},
{
name: 'Configuration - Suggested questions',
url: '',
run: async (page) => {
await page.evaluate(() => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('configure', {
suggestions: [
'What is GitBook?',
'How do I get started?',
'What can you do?',
],
});
});
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(
iframe.getByTestId('ai-chat-suggested-question').nth(0)
).toHaveText('What is GitBook?');
await expect(
iframe.getByTestId('ai-chat-suggested-question').nth(1)
).toHaveText('How do I get started?');
await expect(
iframe.getByTestId('ai-chat-suggested-question').nth(2)
).toHaveText('What can you do?');
await iframe.owner().evaluate(overrideAIInitialState);
},
},
{
name: 'Configuration - Custom action buttons',
url: '',
run: async (page) => {
await page.evaluate((aiPrompt) => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('configure', {
actions: [
{
label: 'Open internal link',
icon: 'bolt',
onClick: () => {
const GitBook =
window.GitBook as unknown as GitBookStandalone;
GitBook('navigateToPage', '/getting-started/quickstart');
},
},
{
label: 'Open external link',
icon: 'sparkle',
onClick: () => {
window.open('https://gitbook.com', '_blank');
},
},
{
label: 'Post message',
icon: 'message',
onClick: () => {
const GitBook =
window.GitBook as unknown as GitBookStandalone;
GitBook('postUserMessage', aiPrompt);
GitBook('navigateToAssistant');
},
},
{
label: 'Close',
icon: 'xmark',
onClick: () => {
const GitBook =
window.GitBook as unknown as GitBookStandalone;
GitBook('close');
},
},
],
});
}, AI_PROMPT);
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('embed-action')).toHaveCount(4);
const actions = iframe.getByTestId('embed-action');
await expect(actions.nth(0)).toHaveAccessibleName('Open internal link');
await actions.nth(0).click();
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible();
await expect(iframe.owner()).toHaveAttribute(
'src',
expect.stringContaining('getting-started/quickstart')
);
await expect(actions.nth(1)).toHaveAccessibleName('Open external link');
// Intercept the new page event without navigating
const [newPage] = await Promise.all([
page.context().waitForEvent('page', { timeout: 5000 }),
actions.nth(1).click(),
]);
// Verify the new page would have opened with the expected URL
expect(newPage.url()).toContain('gitbook.com');
// Close it immediately to avoid navigation
await newPage.close();
await expect(actions.nth(2)).toHaveAccessibleName('Post message');
await actions.nth(2).click();
await expect(iframe.getByTestId('ai-chat')).toBeVisible();
await expect(iframe.getByTestId('ai-chat-message-user').first()).toHaveText(
AI_PROMPT
);
await expect(actions.nth(3)).toHaveAccessibleName('Close');
await actions.nth(3).click();
await expect(page.locator('#gitbook-widget-window')).not.toBeVisible();
await page.locator('#gitbook-widget-button').click();
await iframe.owner().evaluate(overrideAIResponse);
},
},
{
name: 'Configuration - Custom tools',
url: '',
run: async (page) => {
await page.evaluate(() => {
const GitBook = window.GitBook as unknown as GitBookStandalone;
GitBook('configure', {
tools: [
{
name: 'contact_support',
description: 'Contact support on behalf of the user',
execute: async () => {
return {
output: { message: 'Support message' },
summary: {
text: 'Contacted support',
icon: 'circle-question',
},
};
},
confirmation: {
icon: 'circle-question',
label: 'Contact support',
},
},
],
});
GitBook(
'postUserMessage',
'I want to contact support. Call the tool directly without a preamble. Do not respond with anything else.'
);
});
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('ai-chat-message-user').first()).toHaveText(
'I want to contact support. Call the tool directly without a preamble. Do not respond with anything else.'
);
const toolConfirmation = iframe
.getByTestId('ai-chat-tool-confirmation')
.first();
await expect(toolConfirmation).toBeVisible({
timeout: 30000,
});
await iframe.owner().evaluate(overrideAIResponse);
},
},
],
},
{
name: 'Docs Embed - Docs Only',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/~gitbook/embed/demo/',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
tests: [
{
name: 'Docs only',
url: '',
run: async (page) => {
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible({
timeout: 20000,
});
},
},
{
name: 'Table of contents',
url: '',
run: async (page) => {
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible({
timeout: 20000,
});
const tocButton = iframe.getByTestId('toc-button');
await expect(tocButton).toBeVisible();
await tocButton.click();
await expect(iframe.getByTestId('table-of-contents')).toBeVisible();
},
},
{
name: 'Open in new tab',
url: '',
run: async (page) => {
await expect(page.locator('#gitbook-widget-window')).toBeVisible();
const iframe = page.frameLocator('#gitbook-widget-iframe');
await expect(iframe.getByTestId('embed-docs-page')).toBeVisible({
timeout: 20000,
});
const openInNewTabButton = iframe.getByTestId(
'embed-docs-page-open-in-new-tab'
);
await expect(openInNewTabButton).toBeVisible();
// Intercept the new page event without navigating
const [newPage] = await Promise.all([
page.context().waitForEvent('page', { timeout: 5000 }),
openInNewTabButton.click(),
]);
// Verify the new page would have opened with the expected URL
expect(newPage.url()).toContain('gitbook.gitbook.io');
// Close it immediately to avoid navigation
await newPage.close();
},
},
],
},
];
runTestCases(testCases);
+6 -20
View File
@@ -2,7 +2,6 @@ import { argosScreenshot } from '@argos-ci/playwright';
import {
CustomizationAIMode,
CustomizationBackground,
CustomizationCodeTheme,
CustomizationCorners,
CustomizationDefaultFont,
CustomizationDefaultMonospaceFont,
@@ -299,16 +298,6 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
background: CustomizationBackground.Plain,
icons: CustomizationIconsStyle.Regular,
links: CustomizationLinksStyle.Default,
codeTheme: {
default: {
light: CustomizationCodeTheme.DefaultLight,
dark: CustomizationCodeTheme.DefaultDark,
},
openapi: {
light: CustomizationCodeTheme.DefaultLight,
dark: CustomizationCodeTheme.DefaultDark,
},
},
sidebar: {
background: CustomizationSidebarBackgroundStyle.Default,
list: CustomizationSidebarListStyle.Default,
@@ -366,7 +355,6 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
url: 'https://www.gitbook.com/privacy',
},
socialPreview: {},
socialAccounts: [],
};
const encoded = rison.encode_object(deepMerge(DEFAULT_CUSTOMIZATION, partial));
@@ -429,15 +417,14 @@ export async function waitForIcons(page: Page) {
return true;
}
const maskImage = icon.querySelector('[data-testid="mask-image"]');
if (!maskImage) {
throw new Error('No mask-image element');
}
// 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 = maskImage.getAttribute('href');
// If URL is invalid we throw an error.
if (!url) {
throw new Error('No mask-image url');
throw new Error('No mask-image');
}
// If the URL is already queued for loading, we return the state.
@@ -467,8 +454,7 @@ export async function waitForIcons(page: Page) {
*/
async function waitForTOCScrolling(page: Page) {
const viewport = await page.viewportSize();
if (viewport && viewport.width >= 1024 && !page.url().includes('~gitbook/embed/demo')) {
// The embed demo is an iframe, which means the viewport is only a fraction of the main document. So there is no open TOC to scroll to.
if (viewport && viewport.width >= 1024) {
const toc = page.getByTestId('table-of-contents');
await expect(toc).toBeVisible();
await page.evaluate(() => {
+5 -6
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.21.3",
"version": "0.20.0",
"private": true,
"dependencies": {
"@cloudflare/workers-types": "^4.20251011.0",
@@ -28,6 +28,7 @@
"@radix-ui/react-tooltip": "^1.1.8",
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
"@tusbar/cache-control": "^1.0.2",
"ai": "^4.2.2",
"assert-never": "catalog:",
@@ -49,7 +50,7 @@
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"motion": "^12.23.24",
"next": "15.4.11",
"next": "15.4.10",
"next-themes": "^0.4.6",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -67,7 +68,7 @@
"remark-rehype": "^11.1.1",
"rison": "^0.1.1",
"server-only": "^0.0.1",
"shiki": "^3.21.0",
"shiki": "^3.2.0",
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"unified": "^11.0.5",
@@ -77,8 +78,7 @@
"usehooks-ts": "catalog:",
"warn-once": "^0.1.1",
"zod": "^3",
"zustand": "^5.0.3",
"negotiator": "^1.0.0"
"zustand": "^5.0.3"
},
"devDependencies": {
"@argos-ci/playwright": "^5.0.9",
@@ -95,7 +95,6 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/rison": "^0.0.9",
"@types/negotiator": "^0.6.4",
"bun-types": "catalog:",
"deepmerge": "^4.3.1",
"env-cmd": "^10.1.0",
-5
View File
@@ -83,11 +83,6 @@ export function getPagePathFromParams(params: RouteParams) {
// If decoding the param fails, return a 404 instead of crashing
try {
const decoded = decodeURIComponent(params.pagePath);
// For the root page, we encode '/' to avoid an empty param being passed.
if (decoded === '/') {
return '';
}
return decoded;
} catch (error) {
console.error(
@@ -39,10 +39,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont
const { toolCall, context } = props;
return (
<div
data-testid="ai-chat-tool-summary"
className="mt-2 flex origin-top-left animate-blur-in-slow items-start gap-2 text-sm text-tint-subtle"
>
<div className="mt-2 flex origin-top-left animate-blur-in-slow items-start gap-2 text-sm text-tint-subtle">
<Icon
icon={getIconForToolCall(toolCall)}
className="mt-1 size-3 shrink-0 text-tint-subtle/8"
+3 -3
View File
@@ -10,7 +10,7 @@ import type { GitBookAssistant } from '@gitbook/browser-types';
import { useAIChatController, useAIChatState } from '.';
import { AIChatIcon, AISearchIcon, getAIChatName } from '../AIChat';
import { useIntegrationAssistants } from '../Integrations';
import { useSetSearchState } from '../Search/useSearch';
import { useSearch } from '../Search/useSearch';
// Unify assistants configuration context with the assistants hook in one place
export type AIConfig = {
@@ -84,7 +84,7 @@ export function useAI(): AIContext {
const chat = useAIChatState();
const chatController = useAIChatController();
const language = useLanguage();
const setSearchState = useSetSearchState();
const [, setSearchState] = useSearch();
const assistants: Assistant[] = [];
@@ -96,7 +96,7 @@ export function useAI(): AIContext {
<AIChatIcon
state={chat.loading ? 'thinking' : 'default'}
trademark={config.trademark}
className="size-text-lg"
className="size-4"
/>
),
open: (query?: string) => {
@@ -13,7 +13,7 @@ import type { IconName } from '@gitbook/icons';
import * as React from 'react';
import { useTrackEvent } from '../Insights';
import { integrationsAssistantTools } from '../Integrations';
import { useSetSearchState } from '../Search';
import { useSearch } from '../Search';
import { type RenderAIMessageOptions, streamAIChatResponse } from './server-actions';
import { useAIMessageContextRef } from './useAIMessageContext';
@@ -163,7 +163,7 @@ export function AIChatProvider(props: {
const messageContextRef = useAIMessageContextRef();
const trackEvent = useTrackEvent();
const setSearchState = useSetSearchState();
const [, setSearchState] = useSearch();
const language = useLanguage();
// Event listeners storage
@@ -80,13 +80,14 @@ export function AIChat() {
chatController.close();
}
}}
withOverlay={true}
data-testid="ai-chat"
withScrim={true}
className={tcls(
'ai-chat mx-auto ml-8 not-hydrated:hidden w-96 transition-[width] duration-300 ease-quint lg:max-xl:w-80'
)}
>
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base">
<EmbeddableFrameMain data-testid="ai-chat">
<EmbeddableFrameMain>
<EmbeddableFrameHeader>
<AIChatDynamicIcon trademark={config.trademark} />
<EmbeddableFrameHeaderMain>
@@ -103,6 +104,7 @@ export function AIChat() {
icon="close"
label={tString(language, 'close')}
variant="blank"
size="default"
/>
</EmbeddableFrameButtons>
</EmbeddableFrameHeader>
@@ -246,14 +248,13 @@ export function AIChatBody(props: {
<h5
className="animate-blur-in-slow font-bold text-lg text-tint-strong leading-tight [@container(min-height:400px)]:text-center"
style={{ animationDelay: '.5s' }}
data-testid="ai-chat-greeting-title"
data-testid="ai-chat-time-greeting"
>
{greeting?.title || timeGreeting}
</h5>
<p
className="animate-blur-in-slow text-tint leading-tight [@container(min-height:400px)]:text-center"
style={{ animationDelay: '.6s' }}
data-testid="ai-chat-greeting-subtitle"
>
{greeting?.subtitle ||
t(language, 'ai_chat_assistant_description')}
@@ -2,12 +2,9 @@
import { useLanguage } from '@/intl/client';
import { t } from '@/intl/translate';
import type { Assistant } from '../AI';
import { useIsMobile } from '../hooks/useIsMobile';
import { Button } from '../primitives';
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
const MOBILE_BREAKPOINT = 672; // 42rem, equal to Tailwind's @max-2xl container breakpoint
/**
* Button to open/close the AI chat.
*/
@@ -18,15 +15,15 @@ export function AIChatButton(props: {
}) {
const { assistant, showLabel = true, withShortcut = true } = props;
const language = useLanguage();
const isMobile = useIsMobile(MOBILE_BREAKPOINT, '#header-content');
return (
<Button
icon={assistant.icon}
data-testid="ai-chat-button"
iconOnly={!showLabel || isMobile}
iconOnly={!showLabel}
size="medium"
variant="header"
className="h-9 px-2.5 @max-2xl:[&_.button-content]:hidden"
label={
<div className="flex items-center gap-2">
{t(language, 'ai_chat_ask', assistant.label)}
@@ -22,6 +22,7 @@ export function AIChatControlButton() {
icon="trash-can"
label={t(language, 'ai_chat_clear_conversation')}
variant="blank"
size="default"
className="animate-blur-in-slow"
/>
) : null;
@@ -55,7 +55,6 @@ export function AIChatInput(props: {
placeholder={tString(language, 'ai_chat_input_placeholder')}
onSubmit={(val) => onSubmit(val as string)}
submitButton={{
size: 'small',
label: tString(language, 'send'),
}}
className="animate-blur-in-slow bg-tint-base/9 backdrop-blur-lg contrast-more:bg-tint-base"
@@ -19,17 +19,13 @@ export default function AIChatSuggestedQuestions(props: {
];
return (
<div
className="flex flex-col items-start gap-2 self-start"
data-testid="ai-chat-suggested-questions"
>
<div className="flex flex-col items-start gap-2 self-start">
{suggestions.map((question, index) => (
<Button
data-testid="ai-chat-suggested-question"
key={question}
variant="blank"
size="small"
className="max-w-full animate-blur-in-slow border-none bg-primary-solid/1 hover:bg-primary-hover"
size="medium"
className="max-w-full animate-blur-in-slow border-none bg-primary-solid/1 px-3 py-1.5 hover:bg-primary-hover"
style={{
animationDelay: `${1000 + index * 100}ms`,
}}
@@ -37,14 +37,13 @@ export function AIChatToolConfirmations(props: {
style={{ animationDelay: `${0.5 + index * 0.1}s` }}
>
<Button
data-testid="ai-chat-tool-confirmation"
onClick={() => {
tool.confirm();
}}
tabIndex={index}
label={tool.label}
className="w-full justify-center"
size={index === 0 ? 'medium' : 'small'}
size={index === 0 ? 'default' : 'medium'}
variant={index === 0 ? 'primary' : 'secondary'}
icon={tool.icon}
/>
@@ -36,7 +36,7 @@ export function AIResponseFeedback(props: {
'animate-fade-in overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== 1 && 'px-0 text-[0rem] opacity-0'
)}
size="small"
size="medium"
style={{ animationDelay: '.5s', animationDuration: '.5s' }}
onClick={() => handleRating(1)}
disabled={rating !== null}
@@ -52,7 +52,7 @@ export function AIResponseFeedback(props: {
'animate-fade-in overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== -1 && 'px-0 text-[0rem] opacity-0'
)}
size="small"
size="medium"
style={{ animationDelay: '.7s', animationDuration: '.5s' }}
onClick={() => handleRating(-1)}
disabled={rating !== null}
@@ -28,8 +28,8 @@ export function AIChatFollowupSuggestions(props: {
chatController.postMessage({ message: suggestion });
}}
label={suggestion}
className="starting:h-0 max-w-full origin-left animate-blur-in-slow whitespace-normal border-none bg-primary-11/1 starting:py-0 text-left transition-all transition-discrete duration-500 *:whitespace-normal hover:bg-primary-hover"
size="small"
className="starting:h-0 max-w-full origin-left animate-blur-in-slow whitespace-normal border-none bg-primary-11/1 px-3 py-1.5 starting:py-0 text-left transition-all transition-discrete duration-500 *:whitespace-normal hover:bg-primary-hover"
size="medium"
variant="blank"
style={{
animationDelay: `${250 + Math.min(index * 50, 150)}ms`,
@@ -35,66 +35,68 @@ export function AnnouncementBanner(props: {
data-nosnippet=""
>
<div className="transition-all duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<div className={tcls('relative', CONTAINER_STYLE)}>
<Tag
href={contentRef?.href ?? ''}
className={tcls(
'flex w-full items-start justify-center overflow-hidden circular-corners:rounded-xl rounded-md straight-corners:rounded-none px-4 py-3 text-neutral-strong text-sm theme-bold:ring-1 theme-gradient:ring-1 ring-inset transition-colors',
style.container,
closeable && 'pr-12',
hasLink && style.hover
)}
insights={
announcement.link
? {
type: 'link_click',
link: {
target: announcement.link.to,
position: SiteInsightsLinkPosition.Announcement,
},
}
: undefined
}
>
<Icon
icon={style.icon as IconName}
className={`mt-0.5 mr-3 size-4 shrink-0 ${style.iconColor}`}
/>
<div>
{announcement.message}
{hasLink ? (
<div className={tcls(LinkStyles, style.link, 'ml-1 inline')}>
{contentRef?.icon ? (
<span className="mr-1 ml-2 *:inline">
{contentRef?.icon}
</span>
) : null}
{announcement.link?.title && (
<span className="mr-1">{announcement.link?.title}</span>
)}
<Icon
icon={
announcement.link?.to.kind === 'url'
? 'arrow-up-right'
: 'chevron-right'
}
className={tcls('mb-0.5 inline size-3')}
/>
</div>
) : null}
</div>
</Tag>
{closeable ? (
<Button
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
size="small"
onClick={dismissAnnouncement}
className={`absolute top-0 right-4 mt-1.5 mr-1.5 circular-corners:rounded-lg rounded-sm straight-corners:rounded-none p-1.5 transition-all hover:ring-1 sm:right-6 md:right-8 ${style.close}`}
/>
) : null}
<div className={tcls(CONTAINER_STYLE)}>
<div className="relative">
<Tag
href={contentRef?.href ?? ''}
className={tcls(
'flex w-full items-start justify-center overflow-hidden circular-corners:rounded-xl rounded-md straight-corners:rounded-none px-4 py-3 text-neutral-strong text-sm theme-bold:ring-1 theme-gradient:ring-1 ring-inset transition-colors',
style.container,
closeable && 'pr-12',
hasLink && style.hover
)}
insights={
announcement.link
? {
type: 'link_click',
link: {
target: announcement.link.to,
position: SiteInsightsLinkPosition.Announcement,
},
}
: undefined
}
>
<Icon
icon={style.icon as IconName}
className={`mt-0.5 mr-3 size-4 shrink-0 ${style.iconColor}`}
/>
<div>
{announcement.message}
{hasLink ? (
<div className={tcls(LinkStyles, style.link, 'ml-1 inline')}>
{contentRef?.icon ? (
<span className="mr-1 ml-2 *:inline">
{contentRef?.icon}
</span>
) : null}
{announcement.link?.title && (
<span className="mr-1">{announcement.link?.title}</span>
)}
<Icon
icon={
announcement.link?.to.kind === 'url'
? 'arrow-up-right'
: 'chevron-right'
}
className={tcls('mb-0.5 inline size-3')}
/>
</div>
) : null}
</div>
</Tag>
{closeable ? (
<Button
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
size="default"
onClick={dismissAnnouncement}
className={`absolute top-0 right-0 mt-2 mr-2 circular-corners:rounded-lg rounded-sm straight-corners:rounded-none p-1.5 transition-all hover:ring-1 ${style.close}`}
/>
) : null}
</div>
</div>
</div>
</div>
@@ -61,11 +61,10 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'lg:chat-open:mr-80',
'xl:chat-open:mr-100',
'transition-all',
'duration-300',
'text-sm'
'duration-300'
)}
>
<p id={describedById}>
<p id={describedById} className={tcls('text-sm')}>
{t(
language,
'cookies_prompt',
@@ -77,13 +76,12 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
<Button
iconOnly
icon="close"
size="small"
label={tString(language, 'close')}
variant="blank"
onClick={() => setShow(false)}
className={tcls('absolute', 'top-2', 'right-2', 'hover:bg-tint-hover')}
/>
<div className="mt-3 flex flex-row gap-2">
<div className={tcls('mt-3', 'flex', 'flex-row', 'gap-2')}>
<Button
variant="primary"
size="small"
@@ -7,18 +7,18 @@ import {
import { Card, type CardProps } from '@/components/primitives';
import {
type ResolvedContentRef,
resolveContentRef,
resolveContentRefFallback,
resolveContentRefInDocument,
} from '@/lib/references';
import type { BlockProps } from './Block';
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>) {
const { document, block, context, style } = props;
const { block, context, style } = props;
const resolved = context.contentContext
? await resolveContentRefInDocument(document, block.data.ref, context.contentContext, {
? await resolveContentRef(block.data.ref, context.contentContext, {
resolveAnchorText: true,
iconStyle: ['text-xl', 'text-tint'],
})
@@ -84,11 +84,6 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
ancestorBlocks: props.ancestorBlocks,
});
// Determine if this block should expand to full width or use readable width
// Text blocks (paragraphs, headings, lists) use a readable max-width
// Visual blocks (code, tables, images, etc.) expand to full width
const isFullWidthBlock = FULL_WIDTH_BLOCKS.includes(node.type);
return (
<Block
key={node.key || `${node.type}-${index}`}
@@ -99,12 +94,7 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
? 'max-w-screen-xl'
: 'max-w-3xl',
!LIST_BLOCKS.includes(node.type) && 'print:break-inside-avoid',
isFullWidthBlock && 'page-width-wide:max-w-full',
// In OpenAPI mode, all blocks expand to full width
// In full-width mode, content is capped at 64rem (1024px) and centered
'layout-openapi:max-w-full',
'layout-full-width:max-w-5xl',
'layout-full-width:mx-auto',
FULL_WIDTH_BLOCKS.includes(node.type) && 'page-width-wide:max-w-full',
blockStyle,
]}
isEstimatedOffscreen={isOffscreen}
@@ -1,26 +1,25 @@
'use client';
import type { CustomizationThemedCodeTheme, DocumentBlockCode } from '@gitbook/api';
import type { DocumentBlockCode } from '@gitbook/api';
import { useEffect, useId, useMemo, useRef, useState } from 'react';
import { useAdaptiveVisitor } from '@/components/Adaptive';
import { useInViewportListener } from '@/components/hooks/useInViewportListener';
import { useScrollListener } from '@/components/hooks/useScrollListener';
import { Button, ToggleChevron } from '@/components/primitives';
import { Button } from '@/components/primitives';
import { t, useLanguage } from '@/intl/client';
import { type ClassValue, tcls } from '@/lib/tailwind';
import { tcls } from '@/lib/tailwind';
import { useDebounceCallback } from 'usehooks-ts';
import type { BlockProps } from '../Block';
import { type InlineExpressionVariables, useEvaluateInlineExpression } from '../InlineExpression';
import { CodeBlockRenderer } from './CodeBlockRenderer';
import type { HighlightTheme, RenderedInline } from './highlight';
import type { HighlightLine, RenderedInline } from './highlight';
import { plainHighlight } from './plain-highlight';
type ClientBlockProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
inlines: RenderedInline[];
inlineExprVariables: InlineExpressionVariables;
mode: BlockProps<DocumentBlockCode>['context']['mode'];
themes?: CustomizationThemedCodeTheme;
};
export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
@@ -30,7 +29,7 @@ export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
* It allows us to defer some load to avoid blocking the rendering of the whole page with block highlighting.
*/
export function ClientCodeBlock(props: ClientBlockProps) {
const { block, mode, style, inlines, inlineExprVariables, themes } = props;
const { block, mode, style, inlines, inlineExprVariables } = props;
const blockRef = useRef<HTMLDivElement>(null);
const isInViewportRef = useRef(false);
const [isInViewport, setIsInViewport] = useState(false);
@@ -41,17 +40,17 @@ export function ClientCodeBlock(props: ClientBlockProps) {
visitorClaims,
variables: inlineExprVariables,
});
const plainTheme = useMemo(
() => plainHighlight(block, inlines, { evaluateInlineExpression, themes }),
[block, inlines, evaluateInlineExpression, themes]
const plainLines = useMemo(
() => plainHighlight(block, inlines, { evaluateInlineExpression }),
[block, inlines, evaluateInlineExpression]
);
const [theme, setTheme] = useState<null | HighlightTheme>(null);
const [lines, setLines] = useState<null | HighlightLine[]>(null);
const [highlighting, setHighlighting] = useState(false);
// Preload the highlighter when the block is mounted.
useEffect(() => {
import('./highlight').then(({ preloadHighlight }) => preloadHighlight(block, themes));
}, [block, themes]);
import('./highlight').then(({ preloadHighlight }) => preloadHighlight(block));
}, [block]);
// When user scrolls, we need to wait for the scroll to finish before running the highlight
const isScrollingRef = useRef(false);
@@ -100,16 +99,14 @@ export function ClientCodeBlock(props: ClientBlockProps) {
if (typeof window !== 'undefined') {
setHighlighting(true);
import('./highlight').then(({ highlight }) => {
highlight(block, inlines, { evaluateInlineExpression, themes }).then(
(theme) => {
if (cancelled) {
return;
}
setTheme(theme);
setHighlighting(false);
highlight(block, inlines, { evaluateInlineExpression }).then((lines) => {
if (cancelled) {
return;
}
);
setLines(lines);
setHighlighting(false);
});
});
}
@@ -119,12 +116,12 @@ export function ClientCodeBlock(props: ClientBlockProps) {
}
// Otherwise if the block is not in viewport, we reset to the plain lines
setTheme(null);
}, [isInViewport, block, inlines, evaluateInlineExpression, themes]);
setLines(null);
}, [isInViewport, block, inlines, evaluateInlineExpression]);
const expandable = block.data.expandable;
const numberOfLinesOfCode = theme?.lines.length ?? plainTheme.lines.length;
const numberOfLinesOfCode = lines?.length ?? plainLines.length;
const collapsedLineCount =
block.data.collapsedLineCount || CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT;
const isExpandable = Boolean(
@@ -139,17 +136,16 @@ export function ClientCodeBlock(props: ClientBlockProps) {
aria-busy={highlighting}
block={block}
style={style}
theme={theme ?? plainTheme}
lines={lines ?? plainLines}
id={codeBlockBodyId}
/>
);
return isExpandable ? (
<CodeBlockExpandable
theme={theme ?? plainTheme}
lines={lines ?? plainLines}
controls={codeBlockBodyId}
collapsedLineCount={collapsedLineCount}
style={style}
>
{renderer}
</CodeBlockExpandable>
@@ -160,25 +156,21 @@ export function ClientCodeBlock(props: ClientBlockProps) {
function CodeBlockExpandable(props: {
children: React.ReactNode;
theme: HighlightTheme;
lines: HighlightLine[];
collapsedLineCount: number;
controls?: string;
style?: ClassValue;
}) {
const { children, controls, theme, collapsedLineCount, style } = props;
const { children, controls, lines = [], collapsedLineCount } = props;
const [isExpanded, setIsExpanded] = useState(false);
const language = useLanguage();
return (
<div
className={tcls('group/codeblock-expandable relative', style)}
data-follow-color-scheme="true"
aria-expanded={isExpanded}
>
<div className="group/codeblock-expandable relative">
<div
className={tcls(
!isExpanded
? '[&_pre]:h-[calc(2rem+var(--line-count)*var(--line-height))] [&_pre]:overflow-y-hidden'
: ''
isExpanded
? '[&_pre]:after:opacity-0'
: '[&_pre]:h-[calc(2rem+var(--line-count)*var(--line-height))] [&_pre]:overflow-y-hidden [&_pre]:after:opacity-100',
'[&_pre]:after:pointer-events-none [&_pre]:after:absolute [&_pre]:after:inset-0 [&_pre]:after:z-1 [&_pre]:after:bg-gradient-to-t [&_pre]:after:from-0% [&_pre]:after:from-tint-2 [&_pre]:after:to-70% [&_pre]:after:to-transparent [&_pre]:after:content-[""]'
)}
style={
{
@@ -189,9 +181,9 @@ function CodeBlockExpandable(props: {
>
{children}
</div>
<div className="pointer-events-none absolute bottom-2 flex w-full justify-center">
<div className="pointer-events-none absolute bottom-0 flex w-full justify-center">
<Button
icon={<ToggleChevron open={isExpanded} />}
icon={isExpanded ? 'chevron-up' : 'chevron-down'}
size="xsmall"
variant="blank"
type="button"
@@ -202,7 +194,7 @@ function CodeBlockExpandable(props: {
>
{isExpanded
? t(language, 'code_block_expanded')
: t(language, 'code_block_collapsed', theme.lines.length)}
: t(language, 'code_block_collapsed', lines.length)}
</Button>
</div>
</div>
@@ -1,10 +1,6 @@
import * as React from 'react';
import type {
CustomizationThemedCodeTheme,
DocumentBlockCode,
SiteCustomizationSettings,
} from '@gitbook/api';
import type { DocumentBlockCode } from '@gitbook/api';
import { getNodeFragmentByType } from '@/lib/document';
@@ -17,21 +13,8 @@ import { type RenderedInline, getInlines, highlight } from './highlight';
/**
* Render a code block, can be client-side or server-side.
*/
export async function CodeBlock(
props: BlockProps<DocumentBlockCode> & {
themeKey?: keyof SiteCustomizationSettings['styling']['codeTheme'];
themes?: CustomizationThemedCodeTheme;
}
) {
const {
block,
document,
style,
isEstimatedOffscreen,
context,
themeKey = 'default',
themes: providedThemes,
} = props;
export async function CodeBlock(props: BlockProps<DocumentBlockCode>) {
const { block, document, style, isEstimatedOffscreen, context } = props;
const inlines = getInlines(block);
let hasInlineExpression = false;
@@ -67,19 +50,10 @@ export async function CodeBlock(
return { inline, body };
});
// Get code themes from customization
const themes =
providedThemes ??
(context.contentContext && 'customization' in context.contentContext
? context.contentContext.customization.styling.codeTheme[themeKey]
: undefined);
if (!isEstimatedOffscreen && !hasInlineExpression && !block.data.expandable) {
// In v2, we render the code block server-side
const theme = await highlight(block, richInlines, {
themes: themes,
});
return <CodeBlockRenderer block={block} style={style} theme={theme} />;
const lines = await highlight(block, richInlines);
return <CodeBlockRenderer block={block} style={style} lines={lines} />;
}
const variables = context.contentContext
@@ -100,7 +74,6 @@ export async function CodeBlock(
inlines={richInlines}
inlineExprVariables={variables}
mode={context.mode}
themes={themes}
/>
</React.Suspense>
);
@@ -7,10 +7,10 @@ import { tcls } from '@/lib/tailwind';
import { AnnotationPopover } from '../Annotation/AnnotationPopover';
import type { BlockProps } from '../Block';
import { CopyCodeButton } from './CopyCodeButton';
import type { HighlightLine, HighlightTheme, HighlightToken } from './highlight';
import type { HighlightLine, HighlightToken } from './highlight';
type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
theme: HighlightTheme;
lines: HighlightLine[];
'aria-busy'?: boolean;
id?: string;
};
@@ -22,7 +22,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
props: CodeBlockRendererProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
const { block, style, theme, 'aria-busy': ariaBusy } = props;
const { block, style, lines, 'aria-busy': ariaBusy } = props;
const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1;
const withWrap = block.data.overflow === 'wrap';
@@ -30,35 +30,15 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
const id = useId();
const codeId = props.id || id;
/* Shiki returns a color + CSS variables in a single string, which isn't supported in React.
So we parse the foreground & background into objects that can be passed into React's `style` prop. */
const bg = parseShikiColorString(theme.bg);
const fg = parseShikiColorString(theme.fg);
return (
<div
ref={ref}
aria-busy={ariaBusy}
className={tcls(
'group/codeblock shiki grid shrink grid-flow-col overflow-hidden',
style
)}
/* Sets the code theme's mode (light or dark) for the site's theme mode (light or dark).
* Used to style UI elements (scrollbars, form controls) correctly and apply the right default to "plain" code blocks. */
data-color-scheme={`${theme.themes.light.type} ${theme.themes.dark.type}`}
className={tcls('group/codeblock grid shrink grid-flow-col overflow-hidden', style)}
>
<div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]">
{title ? (
<div
className="relative top-px z-20 inline-flex items-center justify-center circular-corners:rounded-t-xl rounded-corners:rounded-t-lg straight-corners:rounded-t-xs border border-tint-subtle border-b-0 bg-tint-subtle theme-muted:bg-tint-base px-3 py-2 text-tint text-xs leading-none tracking-wide contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base"
style={{
backgroundColor: bg?.color,
...bg?.vars,
color: fg?.color,
...fg?.vars,
}}
>
<div className="relative top-px z-20 inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s border border-tint-subtle border-b-0 bg-tint-subtle theme-muted:bg-tint-base px-3 py-2 text-tint text-xs leading-none tracking-wide contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base">
{title}
</div>
) : null}
@@ -69,32 +49,23 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
/>
<pre
className={tcls(
'relative overflow-auto border border-tint-subtle bg-tint-subtle theme-muted:bg-tint-base p-2 text-tint-strong [grid-area:2/1] contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base',
'circular-corners:rounded-2xl rounded-corners:rounded-xl straight-corners:rounded-xs shadow-xs',
title && 'rounded-ss-none!'
'relative overflow-auto border border-tint-subtle bg-tint-subtle theme-muted:bg-tint-base p-2 [grid-area:2/1] contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base',
'rounded-md straight-corners:rounded-xs shadow-xs',
title && 'rounded-ss-none'
)}
style={{
backgroundColor: bg?.color,
...bg?.vars,
color: fg?.color,
...fg?.vars,
}}
>
<code
id={codeId}
className={tcls(
'inline-grid max-h-full min-w-full grid-cols-[auto_1fr] [count-reset:line] print:whitespace-pre-wrap',
withWrap && 'whitespace-pre-wrap',
'[[aria-expanded=false]_&]:mask-b-from-50%'
'inline-grid min-w-full grid-cols-[auto_1fr] [count-reset:line] print:whitespace-pre-wrap',
withWrap && 'whitespace-pre-wrap'
)}
>
{theme.lines.map((line, index) => (
{lines.map((line, index) => (
<CodeHighlightLine
bg={bg}
fg={fg}
key={index}
line={line}
isLast={index === theme.lines.length - 1}
isLast={index === lines.length - 1}
withLineNumbers={withLineNumbers}
/>
))}
@@ -106,35 +77,13 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
function CodeHighlightLine(props: {
line: HighlightLine;
bg?: ShikiColorDefinition;
fg?: ShikiColorDefinition;
isLast: boolean;
withLineNumbers: boolean;
}) {
const { line, isLast, withLineNumbers, bg, fg } = props;
const { line, isLast, withLineNumbers } = props;
return (
<span
className={tcls('highlight-line', line.highlighted && 'highlighted')}
style={
line.highlighted
? {
backgroundColor: bg?.color,
...bg?.vars,
}
: undefined
}
>
{withLineNumbers && (
<span
className="highlight-line-number"
style={{
color: fg?.color,
...fg?.vars,
backgroundColor: bg?.color,
...bg?.vars,
}}
/>
)}
<span className={tcls('highlight-line', line.highlighted && 'highlighted')}>
{withLineNumbers && <span className="highlight-line-number" />}
<span className="highlight-line-content">
<CodeHighlightTokens tokens={line.tokens} />
{!isLast && '\n'}
@@ -163,53 +112,13 @@ function CodeHighlightToken(props: { token: HighlightToken }) {
return token.content;
}
case 'shiki': {
if (!token.token.htmlStyle) {
if (!token.token.color) {
return token.token.content;
}
return <span style={token.token.htmlStyle}>{token.token.content}</span>;
return <span style={{ color: token.token.color }}>{token.token.content}</span>;
}
default:
assertNever(token);
}
}
type ShikiColorDefinition = {
color: string | undefined;
vars: Record<string, string> | undefined;
};
/**
* Parse Shiki color string format into separate color and CSS variables.
*
* Shiki returns colors in a format that combines a default color with CSS variables:
* "defaultColor;--shiki-light:value1;--shiki-dark:value2"
*
* React does not parse these strings as valid CSS because they contain values & properties in one.
* We split these so React can apply them separately via the style prop.
*/
function parseShikiColorString(
originalColor: string | undefined
): ShikiColorDefinition | undefined {
if (!originalColor) {
return undefined;
}
const parts = originalColor.split(';');
const color = parts[0];
const vars = parts.slice(1).reduce(
(acc, item) => {
const [key, value] = item.split(':');
if (key && value) {
acc[key] = value;
}
return acc;
},
{} as Record<string, string>
);
return {
color,
vars: Object.keys(vars).length > 0 ? vars : undefined,
};
}
@@ -1,8 +1,4 @@
import type {
CustomizationThemedCodeTheme,
JSONDocument,
SiteCustomizationSettings,
} from '@gitbook/api';
import type { JSONDocument } from '@gitbook/api';
import { useId } from 'react';
import { CodeBlock } from './CodeBlock';
@@ -12,13 +8,8 @@ import { convertCodeStringToBlock } from './utils';
* Plain code block with syntax highlighting.
* For simplicity, this is just a wrapper around the CodeBlock component, emulating a document.
*/
export function PlainCodeBlock(props: {
code: string;
syntax: string;
themeKey?: keyof SiteCustomizationSettings['styling']['codeTheme'];
themes?: CustomizationThemedCodeTheme;
}) {
const { code, syntax, themeKey, themes } = props;
export function PlainCodeBlock(props: { code: string; syntax: string }) {
const { code, syntax } = props;
const id = useId();
const block = convertCodeStringToBlock({ key: id, code, syntax });
@@ -35,8 +26,6 @@ export function PlainCodeBlock(props: {
context={{
mode: 'default',
}}
themeKey={themeKey}
themes={themes}
block={block}
ancestorBlocks={[]}
// We optimize perf by default
@@ -1,832 +0,0 @@
import type { ThemeRegistration } from 'shiki/core';
export const customThemes: Record<string, ThemeRegistration> = {
'default-light': createTheme({
name: 'default-light',
type: 'light',
bg: 'rgb(var(--tint-2))',
fg: 'rgb(var(--tint-12))',
colors: {
basic: {
comment: 'rgb(var(--neutral-9))',
string: 'rgb(var(--success-10))',
constant: 'rgb(var(--warning-10))',
escape: 'rgb(var(--success-10))',
},
keyword: {
default: 'rgb(var(--danger-10))',
operator: 'rgb(var(--tint-11))',
regexpOperator: 'rgb(var(--primary-10))',
regexpQuantifier: 'rgb(var(--warning-10))',
},
variable: {
default: 'rgb(var(--tint-12))',
language: 'rgb(var(--danger-10))',
parameter: 'rgb(var(--warning-10))',
},
function: {
default: 'rgb(var(--primary-10))',
support: 'rgb(var(--primary-10))',
},
type: {
custom: 'rgb(var(--primary-10))',
primitive: 'rgb(var(--warning-10))',
other: 'rgb(var(--tint-12))',
classMeta: 'rgb(var(--tint-12))',
},
entity: {
tag: 'rgb(var(--warning-10))',
attributeId: 'rgb(var(--primary-10))',
label: 'rgb(var(--danger-10))',
},
markup: {
default: 'rgb(var(--primary-10))',
code: 'rgb(var(--success-10))',
link: 'rgb(var(--primary-10))',
linkUrl: 'rgb(var(--warning-10))',
separator: 'rgb(var(--tint-7))',
},
diff: {
inserted: 'rgb(var(--success-10))',
deleted: 'rgb(var(--danger-10))',
changed: 'rgb(var(--tint-12))',
},
other: {
punctuation: 'rgb(var(--tint-11))',
preprocessor: 'rgb(var(--danger-10))',
default: 'rgb(var(--tint-11))',
invalid: 'rgb(var(--danger-10))',
},
},
}),
'default-dark': createTheme({
name: 'default-dark',
type: 'dark',
bg: 'rgb(var(--tint-2))',
fg: 'rgb(var(--tint-12))',
colors: {
basic: {
comment: 'rgb(var(--neutral-9))',
string: 'rgb(var(--success-11))',
constant: 'rgb(var(--warning-11))',
escape: 'rgb(var(--success-11))',
},
keyword: {
default: 'rgb(var(--danger-11))',
operator: 'rgb(var(--tint-11))',
regexpOperator: 'rgb(var(--primary-11))',
regexpQuantifier: 'rgb(var(--warning-11))',
},
variable: {
default: 'rgb(var(--tint-12))',
language: 'rgb(var(--danger-11))',
parameter: 'rgb(var(--warning-11))',
},
function: {
default: 'rgb(var(--primary-11))',
support: 'rgb(var(--primary-11))',
},
type: {
custom: 'rgb(var(--primary-11))',
primitive: 'rgb(var(--warning-11))',
other: 'rgb(var(--tint-12))',
classMeta: 'rgb(var(--tint-12))',
},
entity: {
tag: 'rgb(var(--warning-11))',
attributeId: 'rgb(var(--primary-11))',
label: 'rgb(var(--danger-11))',
},
markup: {
default: 'rgb(var(--primary-11))',
code: 'rgb(var(--success-11))',
link: 'rgb(var(--primary-11))',
linkUrl: 'rgb(var(--warning-11))',
separator: 'rgb(var(--tint-7))',
},
diff: {
inserted: 'rgb(var(--success-11))',
deleted: 'rgb(var(--danger-11))',
changed: 'rgb(var(--tint-12))',
},
other: {
punctuation: 'rgb(var(--tint-12))',
preprocessor: 'rgb(var(--danger-11))',
default: 'rgb(var(--tint-12))',
invalid: 'rgb(var(--danger-11))',
},
},
}),
'monochrome-light': createTheme({
name: 'monochrome-light',
type: 'light',
bg: 'rgb(var(--tint-2))',
fg: 'rgb(var(--tint-12))',
colors: {
basic: {
comment: 'rgb(var(--tint-9))',
string: 'rgb(var(--primary-12))',
constant: 'rgb(var(--primary-11))',
escape: 'rgb(var(--tint-12))',
},
keyword: {
default: 'rgb(var(--primary-10))',
operator: 'rgb(var(--primary-11))',
regexpOperator: 'rgb(var(--primary-10))',
regexpQuantifier: 'rgb(var(--primary-11))',
},
variable: {
default: 'rgb(var(--tint-12))',
language: 'rgb(var(--primary-10))',
parameter: 'rgb(var(--tint-11))',
},
function: {
default: 'rgb(var(--primary-9))',
support: 'rgb(var(--primary-9))',
},
type: {
custom: 'rgb(var(--primary-10))',
primitive: 'rgb(var(--primary-11))',
other: 'rgb(var(--primary-10))',
classMeta: 'rgb(var(--tint-12))',
},
entity: {
tag: 'rgb(var(--primary-11))',
attributeId: 'rgb(var(--primary-9))',
label: 'rgb(var(--primary-10))',
},
markup: {
default: 'rgb(var(--primary-10))',
code: 'rgb(var(--tint-12))',
link: 'rgb(var(--primary-10))',
linkUrl: 'rgb(var(--primary-11))',
separator: 'rgb(var(--tint-7))',
},
diff: {
inserted: 'rgb(var(--success-11))',
deleted: 'rgb(var(--danger-11))',
changed: 'rgb(var(--tint-12))',
},
other: {
punctuation: 'rgb(var(--tint-10))',
preprocessor: 'rgb(var(--primary-10))',
default: 'rgb(var(--primary-11))',
invalid: 'rgb(var(--danger-11))',
},
},
}),
'monochrome-dark': createTheme({
name: 'monochrome-dark',
type: 'dark',
bg: 'rgb(var(--tint-2))',
fg: 'rgb(var(--tint-12))',
colors: {
basic: {
comment: 'rgb(var(--tint-9))',
string: 'rgb(var(--primary-12))',
constant: 'rgb(var(--primary-11))',
escape: 'rgb(var(--tint-12))',
},
keyword: {
default: 'rgb(var(--primary-11))',
operator: 'rgb(var(--primary-11))',
regexpOperator: 'rgb(var(--primary-10))',
regexpQuantifier: 'rgb(var(--primary-11))',
},
variable: {
default: 'rgb(var(--tint-12))',
language: 'rgb(var(--primary-10))',
parameter: 'rgb(var(--tint-9))',
},
function: {
default: 'rgb(var(--primary-9))',
support: 'rgb(var(--primary-9))',
},
type: {
custom: 'rgb(var(--primary-10))',
primitive: 'rgb(var(--primary-11))',
other: 'rgb(var(--primary-10))',
classMeta: 'rgb(var(--tint-12))',
},
entity: {
tag: 'rgb(var(--primary-11))',
attributeId: 'rgb(var(--primary-9))',
label: 'rgb(var(--primary-10))',
},
markup: {
default: 'rgb(var(--primary-10))',
code: 'rgb(var(--tint-12))',
link: 'rgb(var(--primary-10))',
linkUrl: 'rgb(var(--primary-11))',
separator: 'rgb(var(--tint-7))',
},
diff: {
inserted: 'rgb(var(--success-11))',
deleted: 'rgb(var(--danger-11))',
changed: 'rgb(var(--tint-12))',
},
other: {
punctuation: 'rgb(var(--tint-10))',
preprocessor: 'rgb(var(--primary-10))',
default: 'rgb(var(--primary-11))',
invalid: 'rgb(var(--danger-11))',
},
},
}),
};
/**
* Color category mappings for syntax highlighting.
* Organized into logical groups for better readability.
*/
export type ColorCategories = {
/** Basic syntax elements */
basic: {
/** Comments (//, /*, #, etc.) */
comment: string;
/** String literals ("...", '...', `...`) */
string: string;
/** Constants: numeric, language constants (true, false, null), character constants */
constant: string;
/** Escape sequences in strings (\n, \t, etc.) */
escape: string;
};
/** Keywords and operators */
keyword: {
/** Control flow (if, else, for, while, return), storage (const, let, var, function, class) */
default: string;
/** Operators (+, -, *, /, =, ==, etc.) */
operator: string;
/** Regexp operators (|, ^, $) */
regexpOperator: string;
/** Regexp quantifiers (*, +, ?, {n}) */
regexpQuantifier: string;
};
/** Variables and parameters */
variable: {
/** Regular variables */
default: string;
/** Language variables (this, self, super) */
language: string;
/** Function/method parameters */
parameter: string;
};
/** Functions */
function: {
/** User-defined function/method invocations */
default: string;
/** Built-in/library support functions */
support: string;
};
/** Types and classes */
type: {
/** Custom type names (DocumentBlockCode, HighlightLine, etc.) */
custom: string;
/** Primitive types (number, boolean, string, etc.) */
primitive: string;
/** Other types (support.type, support.class, namespaces) */
other: string;
/** Class meta contexts (meta.class) */
classMeta: string;
};
/** Entities (tags, attributes, labels) */
entity: {
/** HTML/XML tags and attributes */
tag: string;
/** Attribute IDs (entity.other.attribute-name.id) */
attributeId: string;
/** Labels (entity.name.label) */
label: string;
};
/** Markup elements */
markup: {
/** Markup elements (bold, italic, headings, lists, quotes, etc.) */
default: string;
/** Inline code in markup */
code: string;
/** Links in markup */
link: string;
/** Link URLs in markup */
linkUrl: string;
/** Separators (meta.separator) */
separator: string;
};
/** Diff highlighting */
diff: {
/** Inserted lines */
inserted: string;
/** Deleted lines */
deleted: string;
/** Changed lines */
changed: string;
};
/** Other syntax elements */
other: {
/** Punctuation (., ,, ;, :, {}, [], (), etc.) */
punctuation: string;
/** Preprocessor directives (#include, #define, etc.) */
preprocessor: string;
/** Other/miscellaneous elements */
default: string;
/** Invalid syntax/errors */
invalid: string;
};
};
/**
* Creates a Shiki theme registration from semantic color categories.
*
* Maps our semantic color categories (comment, keyword, function, etc.) to Shiki's TextMate scopes.
* TextMate scopes are hierarchical selectors (e.g., "keyword.control", "variable.parameter") that
* Shiki uses to identify different syntax elements. This abstraction allows us to define themes
* using semantic categories rather than low-level scope patterns.
*/
function createTheme(options: {
name: string;
type: 'light' | 'dark';
bg: string;
fg: string;
colors: ColorCategories;
}): ThemeRegistration {
const { name, type, bg, fg, colors } = options;
return {
name,
type,
bg,
fg,
settings: [
// Comments
{
scope: ['comment'],
settings: {
foreground: colors.basic.comment,
},
},
// Constants: language constants (true, false, null, etc.)
{
scope: ['constant.language'],
settings: {
foreground: colors.basic.constant,
},
},
// Constants: numeric
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
'keyword.other.unit',
'constant.sha.git-rebase',
],
settings: {
foreground: colors.basic.constant,
},
},
// Constants: regexp
{
scope: ['constant.regexp'],
settings: {
foreground: colors.basic.constant,
},
},
// Constants: character
{
scope: ['constant.character', 'constant.other.option'],
settings: {
foreground: colors.basic.constant,
},
},
// Constants: escape sequences
{
scope: ['constant.character.escape'],
settings: {
foreground: colors.basic.escape,
},
},
// Constants: other
{
scope: [
'constant.other',
'constant.other.color',
'constant.other.symbol',
'constant.other.placeholder',
'constant.other.character-class.regexp',
'constant.character.set.regexp',
'constant.character.character-class.regexp',
'constant.other.character-class.set.regexp',
],
settings: {
foreground: colors.basic.constant,
},
},
// Constants: variable constants
{
scope: ['variable.other.constant'],
settings: {
foreground: colors.basic.constant,
},
},
// Strings
{
scope: [
'string',
'meta.embedded.assembly',
'string.tag',
'string.value',
'string.regexp',
'meta.preprocessor.string',
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
'punctuation.definition.group.regexp',
'punctuation.definition.group.assertion.regexp',
'punctuation.definition.character-class.regexp',
'punctuation.character.set.begin.regexp',
'punctuation.character.set.end.regexp',
'keyword.operator.negation.regexp',
'support.other.parenthesis.regexp',
],
settings: {
foreground: colors.basic.string,
},
},
// String expressions (template literals, interpolated)
// Only color the punctuation, not the expression content
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: colors.variable.parameter,
},
},
// Keywords: control flow
{
scope: [
'keyword.control',
'source.cpp keyword.operator.new',
'keyword.operator.delete',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
'entity.name.operator',
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: colors.keyword.default,
},
},
// Keywords: storage (const, let, var, function, class, etc.)
{
scope: [
'storage',
'storage.type',
'storage.modifier',
'keyword.operator.noexcept',
'storage.type.numeric.go',
'storage.type.byte.go',
'storage.type.boolean.go',
'storage.type.string.go',
'storage.type.uintptr.go',
'storage.type.error.go',
'storage.type.rune.go',
'storage.type.cs',
'storage.type.generic.cs',
'storage.type.modifier.cs',
'storage.type.variable.cs',
'storage.type.annotation.java',
'storage.type.generic.java',
'storage.type.java',
'storage.type.object.array.java',
'storage.type.primitive.array.java',
'storage.type.primitive.java',
'storage.type.token.java',
'storage.type.groovy',
'storage.type.annotation.groovy',
'storage.type.parameters.groovy',
'storage.type.generic.groovy',
'storage.type.object.array.groovy',
'storage.type.primitive.array.groovy',
'storage.type.primitive.groovy',
],
settings: {
foreground: colors.keyword.default,
},
},
// Keywords: general
{
scope: ['keyword'],
settings: {
foreground: colors.keyword.default,
},
},
// Keywords: operators (default)
{
scope: ['keyword.operator'],
settings: {
foreground: colors.keyword.operator,
},
},
// Keywords: regexp operators
{
scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'],
settings: {
foreground: colors.keyword.regexpOperator,
},
},
// Keywords: regexp quantifiers
{
scope: ['keyword.operator.quantifier.regexp'],
settings: {
foreground: colors.keyword.regexpQuantifier,
},
},
// Variable language (this, self, super, etc.)
{
scope: ['variable.language'],
settings: {
foreground: colors.variable.language,
},
},
// Parameters
{
scope: ['variable.parameter'],
settings: {
foreground: colors.variable.parameter,
},
},
// Variables (regular variables, not parameters)
{
scope: [
'variable',
'meta.definition.variable.name',
'support.variable',
'entity.name.variable',
'meta.object-literal.key',
],
settings: {
foreground: colors.variable.default,
},
},
// Functions: invocations (calls) - user-defined functions
{
scope: [
'meta.function-call',
'meta.method-call',
'entity.name.function',
'entity.name.method',
'support.constant.handlebars',
'source.powershell variable.other.member',
'entity.name.operator.custom-literal',
'keyword.other.special-method',
],
settings: {
foreground: colors.function.default,
},
},
// Support functions: built-in/library functions
{
scope: ['support.function', 'support.function.git-rebase'],
settings: {
foreground: colors.function.support,
},
},
// Types: custom type names (DocumentBlockCode, HighlightLine, etc.) - must come first
{
scope: ['entity.name.type', 'entity.name.class', 'entity.other.inherited-class'],
settings: {
foreground: colors.type.custom,
},
},
// Types: primitive (number, boolean, string, etc.) - must come before support.type
{
scope: [
'support.type.primitive',
'storage.type.primitive',
'support.type.builtin',
'entity.name.type.primitive',
'support.type',
],
settings: {
foreground: colors.type.primitive,
},
},
// Types: other (support.class, etc.)
{
scope: [
'support.class',
'entity.name.namespace',
'entity.other.attribute',
'entity.name.scope-resolution',
'meta.type.cast.expr',
'meta.type.new.expr',
'support.constant.math',
'support.constant.json',
'punctuation.separator.namespace.ruby',
],
settings: {
foreground: colors.type.other,
},
},
// Class meta contexts
{
scope: ['meta.class'],
settings: {
foreground: colors.type.classMeta,
},
},
// DOM constants and namespace references (ReactDOM, window.document, etc.)
{
scope: [
'support.constant.dom',
'variable.other.object',
'variable.other.object.property',
'entity.name.class.jsx',
'support.class.builtin',
],
settings: {
foreground: colors.variable.parameter,
},
},
// Entity names: tags, attributes
{
scope: [
'entity.name.tag',
'entity.other.attribute-name',
'support.type.vendored.property-name',
'support.type.property-name',
'source.css variable',
'source.coffee.embedded',
'meta.structure.dictionary.key.python',
],
settings: {
foreground: colors.entity.tag,
},
},
// Attribute IDs (typically styled like functions)
{
scope: ['entity.other.attribute-name.id', 'punctuation.definition.entity'],
settings: {
foreground: colors.entity.attributeId,
},
},
// Punctuation
{
scope: [
'punctuation',
'punctuation.definition.tag',
'punctuation.definition.quote.begin.markdown',
'punctuation.definition.list.begin.markdown',
'punctuation.section.embedded.begin.php',
'punctuation.section.embedded.end.php',
],
settings: {
foreground: colors.other.punctuation,
},
},
// Links: link text
{
scope: [
'markup.underline.link',
'string.other.link',
'punctuation.definition.string.end.markdown',
'punctuation.definition.string.begin.markdown',
],
settings: {
foreground: colors.markup.link,
},
},
// Links: link URLs
{
scope: ['meta.link'],
settings: {
foreground: colors.markup.linkUrl,
},
},
// Preprocessor
{
scope: [
'meta.preprocessor',
'entity.name.function.preprocessor',
'meta.preprocessor.numeric',
],
settings: {
foreground: colors.other.preprocessor,
},
},
// Diff: inserted
{
scope: ['markup.inserted', 'markup.inserted.diff'],
settings: {
foreground: colors.diff.inserted,
},
},
// Diff: deleted
{
scope: ['markup.deleted', 'markup.deleted.diff'],
settings: {
foreground: colors.diff.deleted,
},
},
// Diff: changed
{
scope: ['markup.changed', 'markup.changed.diff', 'meta.diff.header'],
settings: {
foreground: colors.diff.changed,
},
},
// Markup: bold (with bold font style)
{
scope: ['markup.bold', 'punctuation.definition.bold'],
settings: {
foreground: colors.markup.default,
fontStyle: 'bold',
},
},
// Markup: italic (with italic font style)
{
scope: ['markup.italic', 'punctuation.definition.italic'],
settings: {
foreground: colors.markup.default,
fontStyle: 'italic',
},
},
// Markup: inline code
{
scope: ['markup.raw.inline'],
settings: {
foreground: colors.markup.code,
},
},
// Markup: headings, lists, quotes, underline, strikethrough
{
scope: [
'markup.heading',
'markup.heading punctuation.definition.heading',
'entity.name.section',
'header',
'markup.list',
'markup.quote',
'markup.underline',
'markup.strikethrough',
],
settings: {
foreground: colors.markup.default,
},
},
// Separator
{
scope: ['meta.separator'],
settings: {
background: colors.markup.separator,
foreground: fg,
},
},
// Labels
{
scope: ['entity.name.label'],
settings: {
foreground: colors.entity.label,
},
},
// Other
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
'storage.modifier.import.java',
'variable.language.wildcard.java',
'storage.modifier.package.java',
],
settings: {
foreground: colors.other.default,
},
},
// Invalid
{
scope: ['invalid'],
settings: {
foreground: colors.other.invalid,
},
},
],
};
}
@@ -8,8 +8,7 @@ async function highlightWithInlines(block: DocumentBlockCode) {
inline,
body: null,
}));
const result = await highlight(block, inlines);
return result.lines;
return highlight(block, inlines);
}
it('should parse plain code', async () => {
@@ -225,19 +224,7 @@ it('should parse code with an inline on a single line', async () => {
{
type: 'shiki',
token: {
content: '"',
},
},
{
type: 'shiki',
token: {
content: 'Hello World',
},
},
{
type: 'shiki',
token: {
content: '"',
content: '"Hello World"',
},
},
{
@@ -388,19 +375,7 @@ it('should parse code with an inline on a multiple line', async () => {
{
type: 'shiki',
token: {
content: '"',
},
},
{
type: 'shiki',
token: {
content: 'Hello World',
},
},
{
type: 'shiki',
token: {
content: '"',
content: '"Hello World"',
},
},
{
@@ -656,13 +631,7 @@ it('should support multiple code tokens in an annotation', async () => {
{
type: 'shiki',
token: {
content: '.',
},
},
{
type: 'shiki',
token: {
content: 'world',
content: '.world',
},
},
{
@@ -676,13 +645,7 @@ it('should support multiple code tokens in an annotation', async () => {
{
type: 'shiki',
token: {
content: ')',
},
},
{
type: 'shiki',
token: {
content: ';',
content: ');',
},
},
],
@@ -736,13 +699,7 @@ it('should handle \\r', async () => {
{
type: 'shiki',
token: {
content: '.',
},
},
{
type: 'shiki',
token: {
content: 'log',
content: '.log',
},
},
{
@@ -754,19 +711,7 @@ it('should handle \\r', async () => {
{
type: 'shiki',
token: {
content: '"',
},
},
{
type: 'shiki',
token: {
content: 'Hello',
},
},
{
type: 'shiki',
token: {
content: '"',
content: '"Hello"',
},
},
{
@@ -789,13 +734,7 @@ it('should handle \\r', async () => {
{
type: 'shiki',
token: {
content: '.',
},
},
{
type: 'shiki',
token: {
content: 'log',
content: '.log',
},
},
{
@@ -807,19 +746,7 @@ it('should handle \\r', async () => {
{
type: 'shiki',
token: {
content: '"',
},
},
{
type: 'shiki',
token: {
content: 'World',
},
},
{
type: 'shiki',
token: {
content: '"',
content: '"World"',
},
},
{
@@ -1,39 +1,20 @@
import {
CustomizationCodeTheme,
type CustomizationThemedCodeTheme,
type DocumentBlockCode,
type DocumentBlockCodeLine,
type DocumentInlineAnnotation,
import type {
DocumentBlockCode,
DocumentBlockCodeLine,
DocumentInlineAnnotation,
} from '@gitbook/api';
import {
type ThemeRegistrationAny,
type ThemedToken,
createCssVariablesTheme,
createSingletonShorthands,
createdBundledHighlighter,
} from 'shiki/core';
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
import { type BundledLanguage, bundledLanguages } from 'shiki/langs';
import { bundledThemes } from 'shiki/themes';
import { nullIfNever } from '@/lib/typescript';
import { customThemes } from './customThemes';
import { plainHighlight } from './plain-highlight';
export const DEFAULT_THEMES: CustomizationThemedCodeTheme = {
light: CustomizationCodeTheme.DefaultLight,
dark: CustomizationCodeTheme.DefaultDark,
};
export type HighlightTheme = {
bg?: string;
fg?: string;
themes: {
light: ThemeRegistrationAny;
dark: ThemeRegistrationAny;
};
lines: HighlightLine[];
};
export type HighlightLine = {
highlighted: boolean;
tokens: HighlightToken[];
@@ -55,12 +36,12 @@ export type RenderedInline = {
const isSafari =
typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const theme = createCssVariablesTheme();
// Merge bundled Shiki themes with our custom themes so both are available to the highlighter
const { getSingletonHighlighter } = createSingletonShorthands(
createdBundledHighlighter<any, any>({
langs: bundledLanguages,
themes: { ...bundledThemes, ...customThemes },
themes: {},
engine: () => createJavaScriptRegexEngine({ forgiving: true, target: 'ES2018' }),
})
);
@@ -68,15 +49,12 @@ const { getSingletonHighlighter } = createSingletonShorthands(
/**
* Preload the highlighter for a code block.
*/
export async function preloadHighlight(
block: DocumentBlockCode,
themes: CustomizationThemedCodeTheme = DEFAULT_THEMES
) {
export async function preloadHighlight(block: DocumentBlockCode) {
const langName = getBlockLang(block);
if (langName) {
await getSingletonHighlighter({
langs: [langName],
themes: [themes.light, themes.dark],
themes: [theme],
});
}
}
@@ -89,9 +67,8 @@ export async function highlight(
inlines: RenderedInline[],
options?: {
evaluateInlineExpression?: (expr: string) => string;
themes?: CustomizationThemedCodeTheme;
}
): Promise<HighlightTheme> {
): Promise<HighlightLine[]> {
const langName = getBlockLang(block);
if (!langName || (isSafari && ['powershell', 'cpp'].includes(langName))) {
@@ -103,20 +80,13 @@ export async function highlight(
return plainHighlight(block, inlines, options);
}
const themes = options?.themes ?? DEFAULT_THEMES;
const code = getPlainCodeBlock(block, undefined, options);
const highlighter = await getSingletonHighlighter({
langs: [langName],
themes: [themes.light, themes.dark],
themes: [theme],
});
const resolvedThemes = {
light: highlighter.getTheme(themes.light),
dark: highlighter.getTheme(themes.dark),
};
let tokenizeMaxLineLength = 400;
// In some cases, people will use unindented code blocks with a single line.
// In this case, we can safely increase the max line length to avoid not highlighting the code.
@@ -124,47 +94,38 @@ export async function highlight(
tokenizeMaxLineLength = 5000;
}
const result = highlighter.codeToTokens(code, {
const lines = highlighter.codeToTokensBase(code, {
lang: langName,
themes: resolvedThemes,
// Shiki's light-dark() CSS function provides different colors for light/dark modes based on the resolved themes
defaultColor: 'light-dark()',
theme,
tokenizeMaxLineLength,
});
const lines = result.tokens;
let currentIndex = 0;
return {
bg: result.bg,
fg: result.fg,
themes: resolvedThemes,
lines: lines.map((tokens, index) => {
const lineBlock = block.nodes[index];
const result: HighlightToken[] = [];
return lines.map((tokens, index) => {
const lineBlock = block.nodes[index];
const result: HighlightToken[] = [];
const eatToken = (): PositionedToken | null => {
const token = tokens.shift();
if (token) {
currentIndex += token.content.length;
}
return token
? { ...token, start: currentIndex - token.content.length, end: currentIndex }
: null;
};
while (tokens.length > 0) {
result.push(...matchTokenAndInlines(eatToken, inlines));
const eatToken = (): PositionedToken | null => {
const token = tokens.shift();
if (token) {
currentIndex += token.content.length;
}
return token
? { ...token, start: currentIndex - token.content.length, end: currentIndex }
: null;
};
currentIndex += 1; // for the \n
while (tokens.length > 0) {
result.push(...matchTokenAndInlines(eatToken, inlines));
}
return {
highlighted: Boolean(lineBlock?.data.highlighted),
tokens: result,
};
}),
};
currentIndex += 1; // for the \n
return {
highlighted: Boolean(lineBlock?.data.highlighted),
tokens: result,
};
});
}
/**
@@ -1,9 +1,8 @@
import type { CustomizationThemedCodeTheme, DocumentBlockCode } from '@gitbook/api';
import type { DocumentBlockCode } from '@gitbook/api';
import { getNodeText } from '@/lib/document';
import { bundledThemesInfo } from 'shiki/themes';
import { customThemes } from './customThemes';
import type { HighlightTheme, HighlightToken, RenderedInline } from './highlight';
import type { HighlightLine, HighlightToken, RenderedInline } from './highlight';
/**
* Parse a code block without highlighting it.
@@ -13,59 +12,42 @@ export function plainHighlight(
inlines: RenderedInline[],
options?: {
evaluateInlineExpression?: (expr: string) => string;
themes?: CustomizationThemedCodeTheme;
}
): HighlightTheme {
): HighlightLine[] {
const inlinesCopy = Array.from(inlines);
// Resolve theme objects: first check bundled Shiki themes, then custom themes, finally fallback to type-only
// The type-only fallback ensures HighlightTheme always has valid theme objects even when theme names don't match
const themes = {
light: bundledThemesInfo.find((theme) => theme.id === options?.themes?.light) ??
Object.values(customThemes).find((theme) => theme.name === options?.themes?.light) ?? {
type: 'light',
},
dark: bundledThemesInfo.find((theme) => theme.id === options?.themes?.dark) ??
Object.values(customThemes).find((theme) => theme.name === options?.themes?.dark) ?? {
type: 'dark',
},
};
return block.nodes.map((lineBlock) => {
const tokens: HighlightToken[] = lineBlock.nodes.map((node) => {
if (node.object === 'text') {
return {
type: 'plain',
content: getNodeText(node),
};
}
return {
themes: themes,
lines: block.nodes.map((lineBlock) => {
const tokens: HighlightToken[] = lineBlock.nodes.map((node) => {
if (node.object === 'text') {
return {
if (node.type === 'expression') {
return {
type: 'plain',
content: options?.evaluateInlineExpression?.(node.data.expression) ?? '',
};
}
const inline = inlinesCopy.shift();
return {
type: 'annotation',
body: inline?.body ?? null,
children: [
{
type: 'plain',
content: getNodeText(node),
};
}
if (node.type === 'expression') {
return {
type: 'plain',
content: options?.evaluateInlineExpression?.(node.data.expression) ?? '',
};
}
const inline = inlinesCopy.shift();
return {
type: 'annotation',
body: inline?.body ?? null,
children: [
{
type: 'plain',
content: getNodeText(node),
},
],
};
});
return {
highlighted: Boolean(lineBlock.data.highlighted),
tokens,
},
],
};
}),
};
});
return {
highlighted: Boolean(lineBlock.data.highlighted),
tokens,
};
});
}
@@ -71,8 +71,6 @@ export function DocumentView(
style,
// Preserve adjacent whitespace and new lines.
'whitespace-pre-wrap',
// In OpenAPI mode, add left padding for sidebar offset
'layout-openapi:pl-12',
]}
context={context}
isOffscreen={isOffscreen}
@@ -87,7 +85,7 @@ export function DocumentViewSkeleton(props: { document: JSONDocument; blockStyle
const { document, blockStyle } = props;
return (
<div className="flex flex-col gap-4 layout-openapi:pl-12">
<div className="flex flex-col gap-4">
{document.nodes.map((block) => (
<BlockSkeleton
key={block.key!}
@@ -97,10 +95,6 @@ export function DocumentViewSkeleton(props: { document: JSONDocument; blockStyle
block.data && 'fullWidth' in block.data && block.data.fullWidth
? 'max-w-screen-xl'
: 'max-w-3xl',
// Expand in OpenAPI mode, cap at 64rem in full-width mode
'layout-openapi:max-w-full',
'layout-full-width:max-w-5xl',
'layout-full-width:mx-auto',
blockStyle,
]}
/>
@@ -1,6 +1,6 @@
import type { DocumentBlockDrawing } from '@gitbook/api';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import { Image } from '../utils';
import type { BlockProps } from './Block';
@@ -8,11 +8,11 @@ import { Caption } from './Caption';
import { imageBlockSizes } from './Images';
export async function Drawing(props: BlockProps<DocumentBlockDrawing>) {
const { document, block, context } = props;
const { block, context } = props;
const resolved =
block.data.ref && context.contentContext
? await resolveContentRefInDocument(document, block.data.ref, context.contentContext)
? await resolveContentRef(block.data.ref, context.contentContext)
: null;
if (!resolved) {
return null;
@@ -4,7 +4,6 @@ import { Icon } from '@gitbook/icons';
import { getNodeFragmentByType } from '@/lib/document';
import { tcls } from '@/lib/tailwind';
import { ToggleChevron } from '@/components/primitives';
import type { BlockProps } from '../Block';
import { Blocks } from '../Blocks';
import { Inlines } from '../Inlines';
@@ -47,14 +46,14 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
'[&::-webkit-details-marker]:hidden'
)}
>
<ToggleChevron
orientation="right-to-down"
open={context.mode === 'print'}
<Icon
icon="chevron-right"
className={tcls(
'inline-block',
'size-3',
'mr-3',
'mb-1',
'transition-transform',
'shrink-0',
'group-open/expandable:rotate-90'
)}
@@ -2,7 +2,7 @@ import { type DocumentBlockFile, SiteInsightsLinkPosition } from '@gitbook/api';
import { t } from '@/intl/translate';
import { getSimplifiedContentType } from '@/lib/files';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import { getSpaceLanguage } from '@/intl/server';
import { Button, Link } from '../primitives';
@@ -12,17 +12,13 @@ import { Caption } from './Caption';
import { FileIcon } from './FileIcon';
export async function File(props: BlockProps<DocumentBlockFile>) {
const { document, block, context } = props;
const { block, context } = props;
if (!context.contentContext) {
return null;
}
const contentRef = await resolveContentRefInDocument(
document,
block.data.ref,
context.contentContext
);
const contentRef = await resolveContentRef(block.data.ref, context.contentContext);
const file = contentRef?.file;
if (!file) {
@@ -1,7 +1,7 @@
import type { DocumentBlockImage, DocumentBlockImages, JSONDocument, Length } from '@gitbook/api';
import { Image, type ImageResponsiveSize } from '@/components/utils';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import { type ClassValue, tcls } from '@/lib/tailwind';
import type { BlockProps } from './Block';
@@ -81,14 +81,12 @@ async function ImageBlock(props: {
isEstimatedOffscreen: boolean;
withFrame?: boolean;
}) {
const { document, block, context, isEstimatedOffscreen, withFrame } = props;
const { block, context, isEstimatedOffscreen, withFrame } = props;
const [src, darkSrc] = await Promise.all([
context.contentContext
? resolveContentRefInDocument(document, block.data.ref, context.contentContext)
: null,
context.contentContext ? resolveContentRef(block.data.ref, context.contentContext) : null,
block.data.refDark && context.contentContext
? resolveContentRefInDocument(document, block.data.refDark, context.contentContext)
? resolveContentRef(block.data.refDark, context.contentContext)
: null,
]);
@@ -98,6 +96,36 @@ async function ImageBlock(props: {
return (
<div className={tcls('relative', 'overflow-hidden')}>
{/* Frame grid */}
{withFrame && (
<div
className={tcls(
'absolute',
'-top-0.5',
'-left-0.5',
'right-px',
'bottom-px',
'opacity-40',
'dark:opacity-[0.1]',
'bg-[length:24px_24px,24px_24px]',
'bg-[linear-gradient(to_right,_rgb(234,235,238)_1px,_transparent_1px),linear-gradient(to_bottom,_rgb(234,235,238)_1px,_transparent_1px)]',
'dark:bg-[linear-gradient(to_right,_rgb(122,128,139)_1px,_transparent_1px),linear-gradient(to_bottom,_rgb(122,128,139)_1px,_transparent_1px)]',
'bg-repeat'
)}
/>
)}
{/* Shadow overlay */}
{withFrame && (
<div
className={tcls(
'pointer-events-none absolute inset-0 rounded-2xl',
'shadow-[inset_0_0_10px_10px_rgba(255,255,255,0.9)]',
'dark:shadow-[inset_0_0_10px_10px_rgb(29,29,29)]'
)}
/>
)}
<Caption {...props} fit>
<Image
alt={block.data.alt ?? ''}
@@ -1,75 +0,0 @@
'use client';
import { tString, useLanguage } from '@/intl/client';
import { useAI, useAIChatController, useAIChatState } from '../AI';
import { useSetSearchState } from '../Search';
import { Button, type ButtonProps, Input } from '../primitives';
export function InlineActionButton(
props: { action: 'ask' | 'search'; query?: string } & { buttonProps: ButtonProps } // TODO: Type this properly: Pick<api.DocumentInlineButton, 'action' | 'query'> & { buttonProps: ButtonProps }
) {
const { action, query, buttonProps } = props;
const { assistants } = useAI();
const chatController = useAIChatController();
const chatState = useAIChatState();
const setSearchState = useSetSearchState();
const language = useLanguage();
const handleSubmit = (value: string) => {
if (action === 'ask') {
chatController.open();
if (value ?? query) {
chatController.postMessage({ message: value ?? query });
}
} else if (action === 'search') {
setSearchState((prev) => ({
...prev,
ask: null,
scope: 'default',
query: value ?? query,
open: true,
}));
}
};
const icon =
action === 'ask' && buttonProps.icon === 'gitbook-assistant' && assistants.length > 0
? assistants[0]?.icon
: buttonProps.icon;
if (!query) {
return (
<Input
inline
label={buttonProps.label as string}
sizing="medium"
className="inline-flex max-w-full grow"
submitButton={{
label: tString(language, action === 'ask' ? 'send' : 'search'),
}}
clearButton={{
className: 'text-[1em]',
}}
maxLength={action === 'ask' ? 2048 : 512}
disabled={action === 'ask' && chatState.loading}
aria-busy={action === 'ask' && chatState.loading}
leading={icon}
keyboardShortcut={false}
onSubmit={(value) => handleSubmit(value as string)}
containerStyle={{
width: `${buttonProps.label ? buttonProps.label.toString().length + 10 : 20}ch`,
}}
/>
);
}
const label = action === 'ask' ? `Ask "${query}"` : `Search for "${query}"`;
const button = (
<Button {...buttonProps} onClick={() => handleSubmit(query)} label={label}>
{label !== buttonProps.label ? buttonProps.label : null}
</Button>
);
return button;
}
@@ -1,81 +1,44 @@
import { resolveContentRefFallback, resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef, resolveContentRefFallback } from '@/lib/references';
import * as api from '@gitbook/api';
import type { IconName } from '@gitbook/icons';
import { Button, type ButtonProps } from '../primitives';
import { Button } from '../primitives';
import type { InlineProps } from './Inline';
import { InlineActionButton } from './InlineActionButton';
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
export function InlineButton(props: InlineProps<api.DocumentInlineButton>) {
const { inline } = props;
export async function InlineButton(props: InlineProps<api.DocumentInlineButton>) {
const { inline, context } = props;
const buttonProps: ButtonProps = {
label: inline.data.label,
variant: inline.data.kind,
icon: inline.data.icon as IconName | undefined,
size: 'medium',
};
const resolved = context.contentContext
? await resolveContentRef(inline.data.ref, context.contentContext)
: null;
const ButtonImplementation = () => {
if ('action' in inline.data && 'query' in inline.data.action) {
return (
<InlineActionButton
action={inline.data.action.action}
query={inline.data.action.query ?? ''}
buttonProps={buttonProps}
/>
);
}
if ('ref' in inline.data) {
return <InlineLinkButton {...props} buttonProps={buttonProps} />;
}
return <Button {...buttonProps} disabled />;
};
const href = resolved?.href ?? resolveContentRefFallback(inline.data.ref)?.href;
const inlineElement = (
// Set the leading to have some vertical space between adjacent buttons
<ButtonImplementation />
<span className="inline-button leading-12 [&:has(+.inline-button)]:mr-2">
<Button
href={href}
label={inline.data.label}
// TODO: use a variant specifically for user-defined buttons.
variant={inline.data.kind}
className="leading-normal"
disabled={href === undefined}
icon={inline.data.icon as IconName | undefined}
insights={{
type: 'link_click',
link: {
target: inline.data.ref,
position: api.SiteInsightsLinkPosition.Content,
},
}}
/>
</span>
);
if (!resolved) {
return <NotFoundRefHoverCard context={context}>{inlineElement}</NotFoundRefHoverCard>;
}
return inlineElement;
}
export async function InlineLinkButton(
props: InlineProps<api.DocumentInlineButton> & { buttonProps: ButtonProps }
) {
const { document, inline, context, buttonProps } = props;
if (!('ref' in inline.data)) return;
const resolved =
context.contentContext && inline.data.ref
? await resolveContentRefInDocument(document, inline.data.ref, context.contentContext)
: null;
const href =
resolved?.href ??
(inline.data.ref ? resolveContentRefFallback(inline.data.ref)?.href : undefined);
const button = (
<Button
{...buttonProps}
insights={{
type: 'link_click',
link: {
target: inline.data.ref,
position: api.SiteInsightsLinkPosition.Content,
},
}}
href={href}
disabled={href === undefined}
/>
);
if (inline.data.ref && !resolved) {
return <NotFoundRefHoverCard context={context}>{button}</NotFoundRefHoverCard>;
}
return button;
}
@@ -2,22 +2,20 @@ import type { GitBookBaseContext } from '@/lib/context';
import type { DocumentInlineImage } from '@gitbook/api';
import assertNever from 'assert-never';
import { type ResolvedContentRef, resolveContentRefInDocument } from '@/lib/references';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { Image } from '../utils';
import type { InlineProps } from './Inline';
export async function InlineImage(props: InlineProps<DocumentInlineImage>) {
const { document, inline, context, ancestorInlines } = props;
const { inline, context, ancestorInlines } = props;
const { size = 'original' } = inline.data;
const [src, darkSrc] = await Promise.all([
context.contentContext
? resolveContentRefInDocument(document, inline.data.ref, context.contentContext)
: null,
context.contentContext ? resolveContentRef(inline.data.ref, context.contentContext) : null,
inline.data.refDark && context.contentContext
? resolveContentRefInDocument(document, inline.data.refDark, context.contentContext)
? resolveContentRef(inline.data.refDark, context.contentContext)
: null,
]);
@@ -4,8 +4,8 @@ import { getSpaceLanguage, tString } from '@/intl/server';
import { type TranslationLanguage, languages } from '@/intl/translations';
import {
type ResolvedContentRef,
resolveContentRef,
resolveContentRefFallback,
resolveContentRefInDocument,
} from '@/lib/references';
import { Icon } from '@gitbook/icons';
import { StyledLink } from '../../primitives';
@@ -15,10 +15,10 @@ import { NotFoundRefHoverCard } from '../NotFoundRefHoverCard';
import { InlineLinkTooltip } from './InlineLinkTooltip';
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
const { document, inline, context, ancestorInlines } = props;
const { inline, document, context, ancestorInlines } = props;
const resolved = context.contentContext
? await resolveContentRefInDocument(document, inline.data.ref, context.contentContext, {
? await resolveContentRef(inline.data.ref, context.contentContext, {
// We don't want to resolve the anchor text here, as it can be very expensive and will block rendering if there is a lot of anchors link.
resolveAnchorText: false,
})
@@ -182,16 +182,8 @@ function getOrderedListItemPrefixContent(input: {
}): string {
const { parent, block } = input;
const start = parent.data.start ?? 1;
let index: number;
// If the block has a key, use it to find the index. Otherwise, use the object reference.
if (block.key) {
index = parent.nodes.findIndex((node) => node.key === block.key);
} else {
index = parent.nodes.findIndex((node) => node === block);
}
const value = index >= 0 ? index + start : Math.max(start, 1);
const index = parent.nodes.findIndex((node) => node.key === block.key) ?? 0;
const value = index + start;
switch (input.depth % 3) {
// Use numbers
case 0: {
@@ -1,15 +1,15 @@
import { type DocumentInlineMention, SiteInsightsLinkPosition } from '@gitbook/api';
import { StyledLink } from '@/components/primitives';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import type { InlineProps } from './Inline';
export async function Mention(props: InlineProps<DocumentInlineMention>) {
const { document, inline, context } = props;
const { inline, context } = props;
const resolved = context.contentContext
? await resolveContentRefInDocument(document, inline.data.ref, context.contentContext, {
? await resolveContentRef(inline.data.ref, context.contentContext, {
resolveAnchorText: true,
})
: null;
@@ -3,8 +3,11 @@ import { OpenAPIOperation as BaseOpenAPIOperation } from '@gitbook/react-openapi
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
import { tcls } from '@/lib/tailwind';
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
import { getOpenAPIContext } from './context';
/**
@@ -2,8 +2,11 @@ import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasB
import { tcls } from '@/lib/tailwind';
import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
import { getOpenAPIContext } from './context';
/**
@@ -3,8 +3,11 @@ import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
import { tcls } from '@/lib/tailwind';
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
import { getOpenAPIContext } from './context';
/**
@@ -9,6 +9,7 @@ import { PlainCodeBlock } from '../CodeBlock';
import { DocumentView } from '../DocumentView';
import { Heading } from '../Heading';
import './scalar.css';
import './style.css';
import { DEFAULT_LOCALE, getSpaceLocale } from '@/intl/server';
import type { GitBookAnyContext } from '@/lib/context';
@@ -42,17 +43,7 @@ export function getOpenAPIContext(args: {
check: <Icon icon="check" />,
lock: <Icon icon="lock" />,
},
renderCodeBlock: (codeProps) => (
<PlainCodeBlock
{...codeProps}
themeKey="openapi"
themes={
context && 'customization' in context
? context.customization.styling.codeTheme.openapi
: undefined
}
/>
),
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
renderDocument: (documentProps) => (
<DocumentView
document={documentProps.document as JSONDocument}
@@ -1,5 +1,4 @@
@reference "../../RootLayout/globals.css";
@import './scalar.css';
/* Layout Components */
.openapi-operation,
@@ -82,7 +81,7 @@
/* Markdown Base */
.openapi-markdown {
@apply prose [&_p]:my-2! text-tint-strong dark:prose-invert max-w-none whitespace-normal;
@apply prose text-tint-strong prose-p:my-2! dark:prose-invert max-w-none whitespace-normal;
}
.openapi-markdown > *:first-child {
@@ -161,7 +160,7 @@
}
.openapi-schema-root-description.openapi-markdown {
@apply prose-sm text-balance mt-1.5 text-[0.813rem]! text-tint overflow-hidden font-normal! select-text [&_strong]:font-semibold [&_strong]:text-inherit;
@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 {
@@ -280,7 +279,7 @@
/* Schema Description */
.openapi-schema-description.openapi-markdown {
@apply prose-sm text-tint overflow-hidden text-pretty font-normal! select-text [&_strong]:font-semibold [&_strong]:text-inherit;
@apply prose-sm text-tint overflow-hidden text-pretty font-normal! select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-schema-description.openapi-markdown pre:has(code) {
@@ -323,7 +322,7 @@
.openapi-securities-oauth-content,
.openapi-securities-scopes {
@apply prose *:prose-sm! *:text-tint;
@apply prose *:!prose-sm *:text-tint;
}
.openapi-securities-oauth-content {
@@ -347,7 +346,7 @@
}
.openapi-securities-description.openapi-markdown {
@apply prose-sm text-tint font-normal! select-text text-pretty;
@apply prose-sm text-tint font-normal! select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-securities-label {
@@ -378,7 +377,7 @@
}
.openapi-requestbody-description.openapi-markdown {
@apply prose-sm text-tint font-normal! text-pretty select-text [&_strong]:font-semibold [&_strong]:text-inherit;
@apply prose-sm text-tint font-normal! text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
}
/* Responses */
@@ -396,7 +395,7 @@
}
.openapi-response-description.openapi-markdown {
@apply text-left prose-sm leading-tight text-tint select-text [&_strong]:font-semibold [&_strong]:text-inherit;
@apply text-left prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-disclosure-group-trigger[aria-expanded="false"] .openapi-response-description.openapi-markdown {
@@ -527,7 +526,7 @@
.openapi-panel,
.openapi-codesample,
.openapi-response-examples {
@apply border shrink min-h-32 overflow-hidden rounded-lg straight-corners:rounded-none circular-corners:rounded-xl bg-tint-subtle border-tint-subtle shadow-sm;
@apply border shrink min-h-40 overflow-hidden rounded-lg straight-corners:rounded-none circular-corners:rounded-xl bg-tint-subtle border-tint-subtle shadow-sm;
}
.openapi-response-examples-panel {
@@ -541,9 +540,7 @@
.openapi-panel pre,
.openapi-codesample pre,
.openapi-response-examples pre {
@apply border-none rounded-none shrink shadow-none;
/* Override the code block's background to transparent so it matches with the header and footer */
@apply bg-transparent!
@apply bg-transparent border-none rounded-none shrink shadow-none;
}
.openapi-panel-heading {
@@ -9,12 +9,8 @@ import { getTextAlignment } from './utils';
export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
const { block, style, ...contextProps } = props;
// InlineActionButtons use flex-grow to take the available width. This requires the parent to be a flex container.
const inlineButtonStyle =
'has-[.button,input]:flex has-[.button,input]:flex-wrap has-[.button,input]:gap-2 has-[.button,input]:items-center';
return (
<p className={tcls(inlineButtonStyle, style, getTextAlignment(block.data?.align))}>
<p className={tcls(style, getTextAlignment(block.data?.align))}>
<Inlines {...contextProps} nodes={block.nodes} ancestorInlines={[]} />
</p>
);
@@ -1,13 +1,12 @@
import type { DocumentBlockReusableContent } from '@gitbook/api';
import { getDataOrNull } from '@/lib/data';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import type { BlockProps } from './Block';
import { UnwrappedBlocks } from './Blocks';
// TODO-DEREF: Remove this once we have rolled out the new reusable content deref in the API.
export async function ReusableContent(props: BlockProps<DocumentBlockReusableContent>) {
const { document, block, context, ancestorBlocks } = props;
const { block, context, ancestorBlocks } = props;
if (!context.contentContext) {
throw new Error('Expected a content context to render a reusable content block');
@@ -17,7 +16,7 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
? context.contentContext.dataFetcher.withToken({ apiToken: block.meta.token })
: context.contentContext.dataFetcher;
const resolved = await resolveContentRefInDocument(document, block.data.ref, {
const resolved = await resolveContentRef(block.data.ref, {
...context.contentContext,
dataFetcher,
});
@@ -31,7 +30,7 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
return null;
}
const reusableContentDocument = await getDataOrNull(
const document = await getDataOrNull(
dataFetcher.getRevisionReusableContentDocument({
spaceId: reusableContent.context.space.id,
revisionId: reusableContent.context.revisionId,
@@ -39,14 +38,14 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
})
);
if (!reusableContentDocument) {
if (!document) {
return null;
}
return (
<UnwrappedBlocks
nodes={reusableContentDocument.nodes}
document={reusableContentDocument}
nodes={document.nodes}
document={document}
ancestorBlocks={[...ancestorBlocks, block]}
context={{
...context,
@@ -32,16 +32,7 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
})();
return (
<div
className={tcls(
'mx-auto flex w-full max-w-3xl flex-row gap-4 md:gap-8',
// Expand in OpenAPI mode, cap at 64rem in full-width mode
'layout-openapi:max-w-full',
'layout-full-width:max-w-5xl',
'layout-full-width:mx-auto',
style
)}
>
<div className={tcls('mx-auto flex w-full max-w-3xl flex-row gap-4 md:gap-8', style)}>
<div className="relative select-none">
<div
className={tcls(
@@ -1,6 +1,6 @@
import { LinkBox, LinkOverlay } from '@/components/primitives';
import { Image } from '@/components/utils';
import { type ResolvedContentRef, resolveContentRefInDocument } from '@/lib/references';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import {
CardsImageObjectFit,
@@ -18,7 +18,7 @@ export async function RecordCard(
record: TableRecordKV;
}
) {
const { view, record, context, block, isOffscreen, document } = props;
const { view, record, context, block, isOffscreen } = props;
const { dark, light } = getRecordCardCovers(record[1], view);
const targetRef = view.targetDefinition
@@ -27,13 +27,13 @@ export async function RecordCard(
const [lightCover, darkCover, target] = await Promise.all([
light.contentRef && context.contentContext
? resolveContentRefInDocument(document, light.contentRef, context.contentContext)
? resolveContentRef(light.contentRef, context.contentContext)
: null,
dark.contentRef && context.contentContext
? resolveContentRefInDocument(document, dark.contentRef, context.contentContext)
? resolveContentRef(dark.contentRef, context.contentContext)
: null,
targetRef && context.contentContext
? resolveContentRefInDocument(document, targetRef, context.contentContext)
? resolveContentRef(targetRef, context.contentContext)
: null,
]);
@@ -12,7 +12,7 @@ import { StyledLink } from '@/components/primitives';
import { Image } from '@/components/utils';
import { getNodeFragmentByName } from '@/lib/document';
import { getSimplifiedContentType } from '@/lib/files';
import { resolveContentRefInDocument } from '@/lib/references';
import { resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { filterOutNullable } from '@/lib/typescript';
import type { BlockProps } from '../Block';
@@ -176,8 +176,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
const files = await Promise.all(
value.map((fileId) =>
context.contentContext
? resolveContentRefInDocument(
document,
? resolveContentRef(
{
kind: 'file',
file: fileId,
@@ -251,7 +250,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
}
const resolved =
value && context.contentContext
? await resolveContentRefInDocument(document, value, context.contentContext, {
? await resolveContentRef(value, context.contentContext, {
resolveAnchorText: true,
iconStyle: ['mr-2', 'text-tint-subtle'],
})
@@ -294,11 +293,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
user: userId,
};
const resolved = context.contentContext
? await resolveContentRefInDocument(
document,
contentRef,
context.contentContext
)
? await resolveContentRef(contentRef, context.contentContext)
: null;
if (!resolved) {
return null;
@@ -371,8 +366,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
}
const image = context.contentContext
? await resolveContentRefInDocument(
document,
? await resolveContentRef(
'ref' in value ? value.ref : value,
context.contentContext
)
@@ -224,7 +224,7 @@ const TabPanel = memo(function TabPanel(props: {
aria-labelledby={getTabButtonId(tab.id)}
className="scroll-mt-[calc(var(--content-scroll-margin)+var(--spacing)*20)]"
>
<div className={tcls('p-4')} hidden={!isActive}>
<div className="p-4" hidden={!isActive}>
{tab.body}
</div>
</div>
@@ -60,7 +60,8 @@ export function Update(props: BlockProps<DocumentBlockUpdate>) {
{...contextProps}
nodes={block.nodes}
ancestorBlocks={[...ancestorBlocks, block]}
style="[&>*:first-child]:!pt-0 flex flex-1 flex-col [&>*+*]:mt-5"
// Remove padding-top from headings when they're the first child (similar to column-first-of-type pattern)
style="[&>*:first-child]:!pt-0 flex-1 space-y-4"
/>
</div>
);
@@ -9,11 +9,11 @@ export function getTextAlignment(textAlignment: TextAlignment | undefined): Clas
switch (textAlignment) {
case undefined:
case TextAlignment.Start:
return ['text-start', 'self-start', 'justify-start'];
return ['text-start', 'justify-self-start'];
case TextAlignment.Center:
return ['text-center', 'self-center', 'justify-center'];
return ['text-center', 'justify-self-center'];
case TextAlignment.End:
return ['text-end', 'self-end', 'justify-end'];
return ['text-end', 'justify-self-end'];
default:
return nullIfNever(textAlignment);
}

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