Compare commits

..

65 Commits

Author SHA1 Message Date
Tal Gluck 50cafe9ec6 linting 2025-12-03 14:24:35 +01:00
Tal Gluck 2f05549c72 update snippet generator 2025-12-03 12:19:41 +01:00
Tal Gluck 2da0ccf4ba update code sample generator for python 2025-12-03 12:03:29 +01:00
Zeno Kapitein 9e062b10d3 Make semantic text colors more vibrant (#3838) 2025-12-03 10:39:41 +01:00
Nolann B. 74f89316d9 Fix hidden section not found (#3836) 2025-12-02 11:37:55 +01:00
Zeno Kapitein 9e1d7b296b Update AIChat context card open delay (#3834) 2025-12-02 10:24:18 +01:00
Nolann B. b49caafa9c Support for hidden sections (#3831) 2025-12-01 14:48:48 +01:00
Zeno Kapitein f372eec393 Make annotations visible on hover, fix accessibility and outro animations (#3822) 2025-11-28 17:15:29 +01:00
Zeno Kapitein 65ae941687 Replace AI Chat generated content in e2e (#3833) 2025-11-28 16:46:04 +01:00
Viktor Renkema 75b8efa155 Remove sticky prop on Updates block (#3832) 2025-11-28 10:08:21 +01:00
Utku Ufuk 34b0c67c91 Stop filtering by language when site space language is unspecified (#3825) 2025-11-27 17:57:57 +03:00
Zeno Kapitein e0ace99707 Fix flaky tests in AI Chat greeting (#3828) 2025-11-27 09:50:03 +01:00
Viktor Renkema 3553db0989 Add RSS feed for pages with update blocks (#3824)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-11-26 20:34:25 +01:00
Zeno Kapitein 5c14948cf3 Prevent flaky tests in AI Chat followup suggestions (#3827) 2025-11-26 18:39:51 +01:00
conico974 8e4e2a2333 Update wranglerVersion to 4.43.0 (#3826) 2025-11-26 18:35:18 +01:00
Zeno Kapitein 0f4c53cfbd Update Assistant e2e tests (#3814) 2025-11-26 17:09:48 +00:00
Zeno Kapitein c156e946b0 Update Assistant styling (#3797) 2025-11-24 12:41:00 +01:00
Nolann B. 461e15fd00 Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme (#3820) 2025-11-22 10:44:05 +01:00
conico974 2989f79708 Add 'use server' directive to markdown renderer (#3815) 2025-11-20 09:28:00 +01:00
Nolann B. 344842f5ce Improve OpenAPI circular references (#3821) 2025-11-19 22:44:32 +01:00
Samy Pessé c51076efc4 Fix links to other spaces in embeddable view (#3819) 2025-11-19 10:38:53 +00:00
Zeno Kapitein 38e34dcce3 Optimise page outline (#3813) 2025-11-19 11:26:16 +01:00
Nolann B. 04a4175678 Remove hidden site spaces from navigation (#3795) 2025-11-18 22:49:41 +01:00
Nolann B. 3e40b4db50 Fix OpenAPI basic auth placeholder (#3816) 2025-11-18 20:18:59 +01:00
Greg Bergé 854e612e5d Fix handling of broken links in buttons (#3812) 2025-11-18 11:12:27 +01:00
Samy Pessé 27a461be9d Fix NPM publishing once and for all (#3811) 2025-11-16 02:16:20 +01:00
Samy Pessé 3d83dadb12 New attempt at fixing NPM publishing with trusted publisher (#3810) 2025-11-16 01:34:57 +01:00
Samy Pessé 8dc5c18b9f Pass proper GITHUB_TOKEN to the publish script 2025-11-16 00:35:16 +01:00
Samy Pessé e4756c23a7 Use dedicated step to publish to NPM 2025-11-16 00:28:37 +01:00
Samy Pessé 79ad7bcb59 Fix publishing to NPM (#3809) 2025-11-16 00:25:26 +01:00
Samy Pessé 5b0917a13a Remove --dry-run in publish script 2025-11-15 23:36:35 +01:00
Samy Pessé 6d6f4ef6dd Fix the script to publish npm packages and make it more robust 2025-11-15 23:31:14 +01:00
Samy Pessé fa69a64a2b Merge branch 'main' of https://github.com/GitbookIO/gitbook 2025-11-15 19:47:52 +01:00
Samy Pessé d1a85d1f91 Try fixing the publishing workflow 2025-11-15 19:47:36 +01:00
Samy Pessé 05bf9d7a59 Version Packages (#3733)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-15 19:34:29 +01:00
Samy Pessé 2e0b3370c8 Fix permissions for publish workflow 2025-11-15 19:32:41 +01:00
Samy Pessé 10995e09ca Use trusted publishing for publishing NPM packages (#3808) 2025-11-15 19:26:26 +01:00
Samy Pessé e7cc3ff61f Revert "Use NPM dependency for eval-estree-expression"
This reverts commit 59d259074b.
2025-11-15 18:13:10 +01:00
Samy Pessé 59d259074b Use NPM dependency for eval-estree-expression 2025-11-15 17:58:54 +01:00
Zeno Kapitein 96c4ae4f2d Fix Heading in hint blocks are huge in published content (#3806) 2025-11-14 15:26:46 +01:00
Taran Vohra 64f3596df2 Output generic alternates for page metadata (#3804) 2025-11-14 16:32:19 +05:30
Taran Vohra 27d6c3af39 Use an available alternate when switching variants in a page (#3796) 2025-11-14 15:19:40 +05:30
Nolann B. 1de4eb7973 Fix @modelcontextprotocol/sdk version (#3805) 2025-11-13 15:38:33 +01:00
Nolann B. df1966df25 Bump Scalar (#3803) 2025-11-13 15:29:25 +01:00
conico974 1001aaf94e Refactor DynamicTabs to use NavigationStatusContext (#3800) 2025-11-13 14:37:09 +01:00
Zeno Kapitein 155e448c8f Support primary link in logo (#3802) 2025-11-13 12:59:17 +01:00
Viktor Renkema ca2a94ee1a Support sticky date + new third date format (#3801) 2025-11-13 12:09:53 +01:00
Nolann B. a512c90bda Re-arrange OpenAPI Scopes for OAuth2 (#3799) 2025-11-12 19:40:21 +01:00
conico974 9cbefa89c7 Filter invalid events sent to the API (#3792) 2025-11-12 11:54:13 +01:00
Taran Vohra c8bba7bfe1 Resolve page meta links for metadata (#3793) 2025-11-12 16:12:27 +05:30
Viktor Renkema 4e04db8a1b Support rendering upcoming Update blocks (#3794) 2025-11-12 10:16:02 +01:00
Nolann B. b45feafeff Disable OpenAPI "Try it" when no servers are defined (#3791) 2025-11-10 17:25:22 +01:00
Zeno Kapitein 3876f7081e Edgeless Assistant window on depth: flat sites (#3788) 2025-11-10 14:37:28 +01:00
Nolann B. b4a021a192 Add heredoc support for cURL JSON body (#3790) 2025-11-10 11:20:53 +01:00
Zeno Kapitein 1d92dc1350 Update site layout and blocks with container queries (#3780) 2025-11-10 10:55:03 +01:00
Nolann B. e4ba35fdae Rework OpenAPI Authorizations + scopes (#3786) 2025-11-06 21:20:09 +01:00
Viktor Renkema 35dcaca971 Support custom cover heights (#3779) 2025-11-05 14:21:47 +01:00
Zeno Kapitein 2aa4f2e0b8 Update DropdownMenu styling (#3787) 2025-11-05 08:40:53 +01:00
Zeno Kapitein 6d56df7c10 Treat pages without visible TOC items as no-toc (#3783) 2025-11-05 08:40:15 +01:00
Zeno Kapitein 4534ea83c3 Implement unique title tags for sections & variants (#3768) 2025-11-04 11:36:28 +01:00
Zeno Kapitein 9022d87ec7 Add page breadcrumbs to search results (#3784) 2025-11-04 00:31:32 +01:00
Brett Jephson a49781e200 Expand code block when printing (#3782) 2025-11-03 15:26:18 +00:00
Steven H 065a5e6e55 Disable Workers Observability (#3781) 2025-11-03 11:17:33 +00:00
Zeno Kapitein e27986b17a Reword "Everywhere" search scope to "All docs" (#3776) 2025-11-03 09:57:21 +01:00
Zeno Kapitein 8606f222c9 Add padding to TOC page group items (#3777) 2025-11-03 09:56:39 +01:00
209 changed files with 3378 additions and 1539 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix webframe height issue
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
SearchInput fixes — Translate Clear button, Bold theme color, "Esc" keyboard shortcut
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix search container shadow
+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": patch
---
Revert contentref anchor title
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Move the "Ask <query>" to the bottom of search results if query is not a question
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Improve AI Search branding
-5
View File
@@ -1,5 +0,0 @@
---
'gitbook': patch
---
Improve ancestor label in references
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Tweak language dropdown styling
-5
View File
@@ -1,5 +0,0 @@
---
'gitbook': patch
---
Fix ToggeableLinkItem display
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Fix OpenAPI basic auth placeholder
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix icon resolution for page group
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Support translations and generic variants together
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Add confirmation to "Copy MCP URL" page action
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Update search scope UI
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix links to other spaces and root page in embeddable view.
-5
View File
@@ -1,5 +0,0 @@
---
'gitbook': patch
---
Fix OpenAPI path display
-6
View File
@@ -1,6 +0,0 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Add OpenAPI servers selection
-6
View File
@@ -1,6 +0,0 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Improve OAuth2 scopes handling in OpenAPI
+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": patch
---
Fix opacity of ScrollContainer buttons on bold themes
@@ -3,4 +3,4 @@
'gitbook': patch
---
Add alt text support to card covers
Improve OpenAPI circular references
@@ -30,7 +30,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
@@ -49,7 +49,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
@@ -61,7 +61,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
@@ -71,7 +71,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
@@ -70,7 +70,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: deploy --config ./packages/gitbook/openNext/customWorkers/doWrangler.jsonc
@@ -81,7 +81,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
@@ -104,7 +104,7 @@ runs:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
wranglerVersion: '4.43.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
+16 -6
View File
@@ -6,7 +6,7 @@ on:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -14,28 +14,38 @@ jobs:
publish:
name: Publish
runs-on: ubuntu-latest
permissions:
id-token: write
pull-requests: write
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v3
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Create Release Pull Request or Publish to npm
- name: Create Release Pull Request
id: changesets
uses: changesets/action@v1
with:
publish: npm run release
version: npm run changeset-version
publish: bun run publish-all-packages
version: bun run changeset-version
env:
# Using a PAT instead of GITHUB_TOKEN because we need to run workflows when releases are created
# https://github.com/orgs/community/discussions/26875#discussioncomment-3253761
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+3
View File
@@ -26,3 +26,6 @@ yarn-error.log*
# TypeScript
*.tsbuildinfo
.npmrc
# Bun pack artifacts
packages/*/*.tgz
+79 -94
View File
@@ -1,18 +1,19 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "gitbook",
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.29.7",
"turbo": "^2.5.8",
"turbo": "^2.6.1",
"vercel": "^39.4.2",
},
},
"packages/browser-types": {
"name": "@gitbook/browser-types",
"version": "0.1.2",
"version": "0.1.3",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -25,7 +26,7 @@
},
"packages/cache-tags": {
"name": "@gitbook/cache-tags",
"version": "0.3.3",
"version": "0.3.4",
"dependencies": {
"@gitbook/api": "catalog:",
"assert-never": "catalog:",
@@ -38,7 +39,7 @@
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.4.2",
"version": "0.4.3",
"devDependencies": {
"bun-types": "catalog:",
"tsdown": "catalog:",
@@ -47,7 +48,7 @@
},
"packages/embed": {
"name": "@gitbook/embed",
"version": "0.1.4",
"version": "0.1.5",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -72,14 +73,14 @@
},
"packages/expr": {
"name": "@gitbook/expr",
"version": "1.2.3",
"version": "1.2.4",
"dependencies": {
"acorn": "^8.14.0",
"acorn-loose": "8.4.0",
"acorn-walk": "^8.3.4",
"assert-never": "catalog:",
"escodegen": "^2.1.0",
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#9cf28d2",
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#fb0246a",
},
"devDependencies": {
"@tsconfig/node20": "catalog:",
@@ -94,7 +95,7 @@
},
"packages/fonts": {
"name": "@gitbook/fonts",
"version": "0.1.2",
"version": "0.1.3",
"dependencies": {
"@gitbook/api": "catalog:",
},
@@ -107,7 +108,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.19.2",
"version": "0.20.0",
"dependencies": {
"@cloudflare/workers-types": "^4.20251011.0",
"@gitbook/api": "catalog:",
@@ -123,7 +124,7 @@
"@gitbook/react-contentkit": "workspace:*",
"@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*",
"@modelcontextprotocol/sdk": "^1.17.5",
"@modelcontextprotocol/sdk": "1.17.5",
"@opennextjs/aws": "^3.8.5",
"@opennextjs/cloudflare": "^1.11.0",
"@radix-ui/react-checkbox": "^1.0.4",
@@ -142,6 +143,7 @@
"classnames": "catalog:",
"direction": "^2.0.1",
"event-iterator": "^2.0.0",
"feed": "^5.1.0",
"image-size": "^2.0.2",
"js-cookie": "^3.0.5",
"jsontoxml": "^1.0.1",
@@ -205,6 +207,7 @@
"env-cmd": "^10.1.0",
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"rss-parser": "^3.13.0",
"stylelint": "^16.16.0",
"tailwindcss": "^4.1.11",
"ts-essentials": "^10.0.1",
@@ -215,7 +218,7 @@
},
"packages/icons": {
"name": "@gitbook/icons",
"version": "0.3.3",
"version": "0.3.4",
"bin": {
"gitbook-icons": "./bin/gitbook-icons.js",
},
@@ -239,11 +242,11 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "3.0.4",
"version": "3.0.5",
"dependencies": {
"@scalar/json-magic": "^0.3.1",
"@scalar/openapi-parser": "^0.20.1",
"@scalar/openapi-types": "^0.3.7",
"@scalar/json-magic": "^0.8.1",
"@scalar/openapi-parser": "^0.23.2",
"@scalar/openapi-types": "^0.5.1",
},
"devDependencies": {
"@tsconfig/node20": "catalog:",
@@ -256,7 +259,7 @@
},
"packages/react-contentkit": {
"name": "@gitbook/react-contentkit",
"version": "0.7.7",
"version": "0.7.8",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:*",
@@ -275,7 +278,7 @@
},
"packages/react-math": {
"name": "@gitbook/react-math",
"version": "0.6.3",
"version": "0.6.4",
"bin": {
"gitbook-math": "./bin/gitbook-math.js",
},
@@ -298,13 +301,13 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.5.2",
"version": "1.5.3",
"dependencies": {
"@gitbook/expr": "workspace:*",
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "catalog:",
"@scalar/oas-utils": "^0.2.130",
"@scalar/types": "^0.1.9",
"@scalar/oas-utils": "^0.6.3",
"@scalar/types": "^0.4.0",
"classnames": "catalog:",
"flatted": "^3.2.9",
"js-yaml": "^4.1.0",
@@ -343,7 +346,7 @@
"react-dom": "catalog:",
},
"catalog": {
"@gitbook/api": "0.146.0",
"@gitbook/api": "0.151.0",
"@scalar/api-client-react": "^1.3.46",
"@tsconfig/node20": "^20.1.6",
"@tsconfig/strictest": "^2.0.6",
@@ -724,7 +727,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.146.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-gWcSCbN+9Zc/XOEk4t8v70kKyaVJQytHMnnstArr8av1YpHzZWEpVQCeQ20SnJvkvO5y+P7TCVxJCLG2ciT9SQ=="],
"@gitbook/api": ["@gitbook/api@0.151.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-5d9+rZ2u6CKKIiVHO1Toyk+7wHtTOXmP0+sVIE3teRkceX4z5FGIIpa4XsFeKC9XeosncvehdshPaOGQtSDpTQ=="],
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
@@ -862,7 +865,7 @@
"@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@2.0.0-rc.0", "", { "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", "https-proxy-agent": "^7.0.5", "node-fetch": "^2.6.7", "nopt": "^8.0.0", "semver": "^7.5.3", "tar": "^7.4.0" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-nhSMNprz3WmeRvd8iUs5JqkKr0Ncx46JtPxM3AhXes84XpSJfmIwKeWXRpsr53S7kqPkQfPhzrMFUxSNb23qSA=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.18.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-JvKyB6YwS3quM+88JPR0axeRgvdDu3Pv6mdZUy+w4qVkCzGgumb9bXG/TmtDRQv+671yaofVfXSQmFLlWU5qPQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.17.5", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg=="],
"@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=="],
@@ -1280,33 +1283,33 @@
"@scalar/draggable": ["@scalar/draggable@0.2.0", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-UetHRB5Bqo5egVYlS21roWBcICmyk8CKh2htsidO+bFGAjl2e7Te+rY0borhNrMclr0xezHlPuLpEs1dvgLS2g=="],
"@scalar/helpers": ["@scalar/helpers@0.0.8", "", {}, "sha512-9A1CxL3jV7Kl9wGu86/cR/wiJN6J+3tK4WuW3252s2gF+upXsgQRx9WLhFF3xifOP1irIGusitZBiojiKmUSVg=="],
"@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.3.1", "", { "dependencies": { "@scalar/helpers": "0.0.8", "vue": "^3.5.17", "yaml": "2.8.0" } }, "sha512-vQvl4TckiMT8Txo6S82ETJ4OI+K6iSxLZsjPaq4cEqY+9zVfmIMALFGPj/X5BB8tU3FdluV2yEa8LswsMQ68IA=="],
"@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.2.130", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.2.0", "@scalar/themes": "0.9.86", "@scalar/types": "0.1.7", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-sVpdc3+3c/WiNrKEIwzJ+ml2ZQBjarMOTDJCM/IrvYhrJE0nHrdkzxlJgNPi++vJbVl0saYt8LhEItALv7NziA=="],
"@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.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="],
"@scalar/object-utils": ["@scalar/object-utils@1.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.20.1", "", { "dependencies": { "@scalar/json-magic": "0.3.1", "@scalar/openapi-types": "0.3.7", "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-rxtuBQ90YsUEDefWU3GAEmvjYr1CvGO6nkDVzbjmWv2aPB3mtK80bCRBgQGTdIdW5XQEWAAmflSKEhcj2Xo5QA=="],
"@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.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="],
"@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.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@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=="],
"@scalar/snippetz": ["@scalar/snippetz@0.5.1", "", { "dependencies": { "@scalar/types": "0.3.2", "stringify-object": "^5.0.0" } }, "sha512-RuCSsD59qVUyux9g6BXQX35TTeJU7U34bilfPeDA9p8+dvo1WDDoRgooBmAUn4Xaxh2H7hVH0qTSJ0ZlPk4SQw=="],
"@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="],
"@scalar/themes": ["@scalar/themes@0.13.23", "", { "dependencies": { "@scalar/types": "0.4.0", "nanoid": "5.1.5" } }, "sha512-9eQs3h9mg9TtM8tyR8/Q7lyBXssXEelem20ZVvQ2eQyB6+ibHaXKh8lkUipuX1w6KU5JVr2VMYr/Q8G0B/5USw=="],
"@scalar/typebox": ["@scalar/typebox@0.1.1", "", {}, "sha512-Mhhubu4zj1PiXhtgvNbz34zniedtO6PYdD80haMkIjOJwV9aWejxXILr2elHGBMsLfdhH3s9qxux6TL6X8Q6/Q=="],
"@scalar/types": ["@scalar/types@0.1.16", "", { "dependencies": { "@scalar/openapi-types": "0.2.3", "nanoid": "^5.1.5", "zod": "3.24.1" } }, "sha512-v1L96F8Inn27NUw5Xcl0iifw3SOX0R9WBV6GeswR23s8i1SJz7UiLEi8rLOqOB5DiLtC8hDK0kNl4TKn7MNJWQ=="],
"@scalar/types": ["@scalar/types@0.4.0", "", { "dependencies": { "@scalar/openapi-types": "0.5.1", "nanoid": "5.1.5", "type-fest": "5.0.0", "zod": "4.1.11" } }, "sha512-vOD1GZez7kPdVA+UQit05QE9dbALfevhK9kqRTsqcPX7FvvZ9eQWSNl1GKmKtmRiAZGThv2agM5AvHRxkH2JSw=="],
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.12.43", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.15.1", "codemirror": "^6.0.0", "vue": "^3.5.17" } }, "sha512-gtI4jzMS4FEaTxq4FZcUJ3tjhMzi442bMvkLJglwrY68zAwGGLI0jtx9NyiC6i+ikwEjgpdZk+J45AeCHUxd0Q=="],
@@ -1314,7 +1317,7 @@
"@scalar/use-toasts": ["@scalar/use-toasts@0.8.0", "", { "dependencies": { "nanoid": "^5.1.5", "vue": "^3.5.12", "vue-sonner": "^1.0.3" } }, "sha512-u+o77cdTNZ5ePqHPu8ZcFw1BLlISv+cthN0bR1zJHXmqBjvanFTy2kL+Gmv3eW9HxZiHdqycKVETlYd0mWiqJQ=="],
"@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/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.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=="],
@@ -1556,8 +1559,6 @@
"@ungap/structured-clone": ["@ungap/structured-clone@1.2.0", "", {}, "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="],
"@unhead/schema": ["@unhead/schema@1.11.14", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA=="],
"@vercel/build-utils": ["@vercel/build-utils@9.1.0", "", {}, "sha512-ccknvdKH6LDB9ZzZaX8a8cOvFbI441APLHvKrunJE/wezY0skmfuEUK1qnfPApXMs4FMWzZQj2LO9qpzfgBPsQ=="],
"@vercel/error-utils": ["@vercel/error-utils@2.0.3", "", {}, "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ=="],
@@ -1906,7 +1907,7 @@
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
"env-cmd": ["env-cmd@10.1.0", "", { "dependencies": { "commander": "^4.0.0", "cross-spawn": "^7.0.0" }, "bin": { "env-cmd": "bin/env-cmd.js" } }, "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA=="],
@@ -1952,7 +1953,7 @@
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eval-estree-expression": ["eval-estree-expression@github:jonschlinkert/eval-estree-expression#9cf28d2", {}, "jonschlinkert-eval-estree-expression-9cf28d2"],
"eval-estree-expression": ["eval-estree-expression@github:jonschlinkert/eval-estree-expression#fb0246a", {}, "jonschlinkert-eval-estree-expression-fb0246a"],
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
@@ -2004,6 +2005,8 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"feed": ["feed@5.1.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg=="],
"file-entry-cache": ["file-entry-cache@10.0.7", "", { "dependencies": { "flat-cache": "^6.1.7" } }, "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw=="],
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
@@ -2402,7 +2405,7 @@
"micro": ["micro@9.3.5-canary.3", "", { "dependencies": { "arg": "4.1.0", "content-type": "1.0.4", "raw-body": "2.4.1" }, "bin": { "micro": "./bin/micro.js" } }, "sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g=="],
"microdiff": ["microdiff@1.4.0", "", {}, "sha512-OBKBOa1VBznvLPb/3ljeJaENVe0fO0lnWl77lR4vhPlQD71UpjEoRV5P0KdQkcjbFlBu1Oy2mEUBMU3wxcBAGg=="],
"microdiff": ["microdiff@1.5.0", "", {}, "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q=="],
"micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
@@ -2734,12 +2737,16 @@
"router": ["router@2.0.0", "", { "dependencies": { "array-flatten": "3.0.0", "is-promise": "4.0.0", "methods": "~1.1.2", "parseurl": "~1.3.3", "path-to-regexp": "^8.0.0", "setprototypeof": "1.2.0", "utils-merge": "1.0.1" } }, "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ=="],
"rss-parser": ["rss-parser@3.13.0", "", { "dependencies": { "entities": "^2.0.3", "xml2js": "^0.5.0" } }, "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
@@ -2892,23 +2899,23 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"turbo": ["turbo@2.5.8", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.8", "turbo-darwin-arm64": "2.5.8", "turbo-linux-64": "2.5.8", "turbo-linux-arm64": "2.5.8", "turbo-windows-64": "2.5.8", "turbo-windows-arm64": "2.5.8" }, "bin": { "turbo": "bin/turbo" } }, "sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w=="],
"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.5.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh5bCACiHO8rUXZLpKw+m3FiHtAp2CkanSyJre+SInEvEr5kIxjGvCK/8MFX8SFRjQuhjtvpIvYYZJB4AGCxNQ=="],
"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.5.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-f1H/tQC9px7+hmXn6Kx/w8Jd/FneIUnvLlcI/7RGHunxfOkKJKvsoiNzySkoHQ8uq1pJnhJ0xNGTlYM48ZaJOQ=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.6.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw=="],
"turbo-linux-64": ["turbo-linux-64@2.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-hMyvc7w7yadBlZBGl/bnR6O+dJTx3XkTeyTTH4zEjERO6ChEs0SrN8jTFj1lueNXKIHh1SnALmy6VctKMGnWfw=="],
"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.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-LQELGa7bAqV2f+3rTMRPnj5G/OHAe2U+0N9BwsZvfMvHSUbsQ3bBMWdSQaYNicok7wOZcHjz2TkESn1hYK6xIQ=="],
"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.5.8", "", { "os": "win32", "cpu": "x64" }, "sha512-3YdcaW34TrN1AWwqgYL9gUqmZsMT4T7g8Y5Azz+uwwEJW+4sgcJkIi9pYFyU4ZBSjBvkfuPZkGgfStir5BBDJQ=="],
"turbo-windows-64": ["turbo-windows-64@2.6.1", "", { "os": "win32", "cpu": "x64" }, "sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-eFC5XzLmgXJfnAK3UMTmVECCwuBcORrWdewoiXBnUm934DY6QN8YowC/srhNnROMpaKaqNeRpoB5FxCww3eteQ=="],
"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=="],
"type-fest": ["type-fest@4.40.0", "", {}, "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="],
"type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"type-is": ["type-is@2.0.0", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw=="],
@@ -3028,6 +3035,14 @@
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -3050,8 +3065,6 @@
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
@@ -3684,8 +3697,6 @@
"@modelcontextprotocol/sdk/cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"@modelcontextprotocol/sdk/eventsource-parser": ["eventsource-parser@3.0.0", "", {}, "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA=="],
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
"@node-minify/core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
@@ -3864,9 +3875,7 @@
"@scalar/api-client/@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/api-client/microdiff": ["microdiff@1.5.0", "", {}, "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q=="],
"@scalar/api-client/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@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/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -3886,17 +3895,9 @@
"@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/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/oas-utils/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/oas-utils/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
"@scalar/oas-utils/yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"@scalar/openapi-types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
"@scalar/openapi-upgrader/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@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=="],
@@ -3906,23 +3907,13 @@
"@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/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
"@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.3", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-O1GwqLpcRc3GKXTbeBZ5E12fXR2ltpqGWk4RfhoN4ebKZsPVknV5at5425G97E1SwMy12BporRvn90k1Z+MruQ=="],
"@scalar/types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
"@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.0.12", "", {}, "sha512-4NDmHShyi1hrVRsJCdRZT/FIpy+/5PFbVbQLRYX/pjpu5cYqHBj9s6n5RI6gGDXEBHAIFi63g9FC6Isgr66l1Q=="],
"@scalar/workspace-store/@scalar/json-magic": ["@scalar/json-magic@0.6.1", "", { "dependencies": { "@scalar/helpers": "0.0.12", "yaml": "2.8.0" } }, "sha512-HJMPY5dUU3EXVS4EkjAFXo+uCrby/YFu/gljKDQnhYWRy5zQ0sJWrOEDcHS8nLoJRCIRD5tiVpCxnUnSb6OoAQ=="],
"@scalar/workspace-store/@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/workspace-store/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@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=="],
"@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=="],
@@ -4134,6 +4125,8 @@
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"https-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -4852,10 +4845,12 @@
"@scalar/api-client-react/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/api-client-react/@scalar/types/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@scalar/api-client-react/@scalar/types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/api-client/@scalar/openapi-parser/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@scalar/api-client/@scalar/workspace-store/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@scalar/components/@scalar/oas-utils/@scalar/json-magic": ["@scalar/json-magic@0.6.1", "", { "dependencies": { "@scalar/helpers": "0.0.12", "yaml": "2.8.0" } }, "sha512-HJMPY5dUU3EXVS4EkjAFXo+uCrby/YFu/gljKDQnhYWRy5zQ0sJWrOEDcHS8nLoJRCIRD5tiVpCxnUnSb6OoAQ=="],
"@scalar/components/@scalar/oas-utils/@scalar/object-utils": ["@scalar/object-utils@1.2.8", "", { "dependencies": { "@scalar/helpers": "0.0.12", "flatted": "^3.3.3", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1", "type-fest": "5.0.0" } }, "sha512-FjPxEg7Hw0tz2iTKvi+gYt+luZK0TqhX50hUIBuaYa/Ja/OMuKLp9QHhB5U68F1L55CZNP4vwoNNfXeYWnVEZg=="],
@@ -4864,9 +4859,7 @@
"@scalar/components/@scalar/oas-utils/@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/components/@scalar/oas-utils/microdiff": ["microdiff@1.5.0", "", {}, "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q=="],
"@scalar/components/@scalar/oas-utils/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@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/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -4886,7 +4879,7 @@
"@scalar/import/@scalar/openapi-parser/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/openapi-upgrader/@scalar/openapi-types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/import/@scalar/openapi-parser/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@scalar/postman-to-openapi/@scalar/oas-utils/@scalar/json-magic": ["@scalar/json-magic@0.6.1", "", { "dependencies": { "@scalar/helpers": "0.0.12", "yaml": "2.8.0" } }, "sha512-HJMPY5dUU3EXVS4EkjAFXo+uCrby/YFu/gljKDQnhYWRy5zQ0sJWrOEDcHS8nLoJRCIRD5tiVpCxnUnSb6OoAQ=="],
@@ -4896,9 +4889,7 @@
"@scalar/postman-to-openapi/@scalar/oas-utils/@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/postman-to-openapi/@scalar/oas-utils/microdiff": ["microdiff@1.5.0", "", {}, "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q=="],
"@scalar/postman-to-openapi/@scalar/oas-utils/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@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/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -4906,12 +4897,8 @@
"@scalar/snippetz/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/snippetz/@scalar/types/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@scalar/snippetz/@scalar/types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/use-toasts/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.12", "", { "dependencies": { "@vue/compiler-core": "3.5.12", "@vue/shared": "3.5.12" } }, "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg=="],
"@scalar/use-toasts/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.12", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.12", "@vue/compiler-dom": "3.5.12", "@vue/compiler-ssr": "3.5.12", "@vue/shared": "3.5.12", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.47", "source-map-js": "^1.2.0" } }, "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw=="],
@@ -4922,10 +4909,6 @@
"@scalar/use-toasts/vue/@vue/shared": ["@vue/shared@3.5.12", "", {}, "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="],
"@scalar/workspace-store/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/workspace-store/@scalar/types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@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=="],
@@ -5248,9 +5231,9 @@
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@scalar/components/@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/components/@scalar/oas-utils/@scalar/workspace-store/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@scalar/components/@scalar/themes/@scalar/types/type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="],
"@scalar/components/@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.5.0", "", { "dependencies": { "zod": "4.1.11" } }, "sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A=="],
"@scalar/components/@scalar/themes/@scalar/types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -5262,6 +5245,8 @@
"@scalar/import/@scalar/openapi-parser/@scalar/openapi-types/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/postman-to-openapi/@scalar/oas-utils/@scalar/workspace-store/@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.3", "", { "dependencies": { "@scalar/openapi-types": "0.5.0" } }, "sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ=="],
"@scalar/use-toasts/vue/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@scalar/use-toasts/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
+4 -5
View File
@@ -7,10 +7,10 @@
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.29.7",
"turbo": "^2.5.8",
"turbo": "^2.6.1",
"vercel": "^39.4.2"
},
"packageManager": "bun@1.3.0",
"packageManager": "bun@1.3.2",
"overrides": {
"@codemirror/state": "6.4.1",
"@types/react": "catalog:",
@@ -32,8 +32,7 @@
"e2e-customers": "turbo run e2e-customers",
"changeset": "changeset",
"changeset-version": "changeset version && bun run format && bun update",
"release": "turbo run release && bun run publish-all-packages",
"publish-all-packages": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag",
"publish-all-packages": "turbo run publish-to-npm --continue=dependencies-successful",
"download:env": "op read op://gitbook-x-dev/gitbook-open/.env.local >> .env.local",
"clean": "turbo run clean"
},
@@ -42,7 +41,7 @@
"catalog": {
"@tsconfig/strictest": "^2.0.6",
"@tsconfig/node20": "^20.1.6",
"@gitbook/api": "0.146.0",
"@gitbook/api": "0.151.0",
"@scalar/api-client-react": "^1.3.46",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
+8
View File
@@ -1,5 +1,13 @@
# @gitbook/browser-types
## 0.1.3
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
- Updated dependencies [10995e0]
- @gitbook/icons@0.3.4
## 0.1.2
### Patch Changes
+6 -2
View File
@@ -9,7 +9,7 @@
}
},
"sideEffects": false,
"version": "0.1.2",
"version": "0.1.3",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:"
@@ -22,11 +22,15 @@
"scripts": {
"build": "tsdown",
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src"
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/cache-tags
## 0.3.4
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
## 0.3.3
### Patch Changes
+6 -2
View File
@@ -8,7 +8,7 @@
}
},
"sideEffects": false,
"version": "0.3.3",
"version": "0.3.4",
"dependencies": {
"@gitbook/api": "catalog:",
"assert-never": "catalog:"
@@ -21,11 +21,15 @@
"scripts": {
"build": "tsdown",
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src"
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/colors
## 0.4.3
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
## 0.4.2
### Patch Changes
+6 -2
View File
@@ -8,7 +8,7 @@
}
},
"sideEffects": false,
"version": "0.4.2",
"version": "0.4.3",
"devDependencies": {
"bun-types": "catalog:",
"tsdown": "catalog:",
@@ -17,11 +17,15 @@
"scripts": {
"build": "tsdown",
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src"
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+8
View File
@@ -1,5 +1,13 @@
# @gitbook/embed
## 0.1.5
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
- Updated dependencies [10995e0]
- @gitbook/icons@0.3.4
## 0.1.4
### Patch Changes
+6 -2
View File
@@ -13,7 +13,7 @@
}
},
"sideEffects": false,
"version": "0.1.4",
"version": "0.1.5",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -34,11 +34,15 @@
"build-standalone": "bun build src/standalone/index.ts --bundle --minify --outdir=standalone",
"clean": "rm -rf ./dist",
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src"
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+12 -1
View File
@@ -29,6 +29,16 @@
--gitbook-widget-easing-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (prefers-color-scheme: dark) {
:root {
--gitbook-widget-text-color: #FFFFFF;
--gitbook-widget-border-color: #202020;
--gitbook-widget-background-translucent: rgba(15, 15, 15, 0.9);
--gitbook-widget-background-translucent-hover: rgba(20, 20, 20, 0.9);
--gitbook-widget-background-solid: #f0f0f0;
}
}
* {
box-sizing: border-box;
}
@@ -120,7 +130,8 @@
z-index: 9998;
width: calc(min(var(--gitbook-widget-window-width), calc(100vw - var(--gitbook-widget-right) - var(--gitbook-widget-left))));
height: calc(min(var(--gitbook-widget-window-height), calc(100vh - var(--gitbook-widget-window-bottom) - var(--gitbook-widget-top))));
background-color: var(--gitbook-widget-background-solid);
background-color: var(--gitbook-widget-background-translucent);
backdrop-filter: blur(48px);
border: 1px solid var(--gitbook-widget-border-color);
border-radius: var(--gitbook-widget-radius);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+7
View File
@@ -1,5 +1,12 @@
# @gitbook/expr
## 1.2.4
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
- 10995e0: Use NPM dependency for eval-estree-expression
## 1.2.3
### Patch Changes
+7 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@gitbook/expr",
"description": "Safely evaluate & parse user-defined GitBook expressions.",
"version": "1.2.3",
"version": "1.2.4",
"type": "module",
"exports": {
".": {
@@ -16,7 +16,7 @@
"acorn-walk": "^8.3.4",
"assert-never": "catalog:",
"escodegen": "^2.1.0",
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#9cf28d2"
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#fb0246a"
},
"devDependencies": {
"@tsconfig/strictest": "catalog:",
@@ -33,11 +33,15 @@
"typecheck": "tsc --noEmit",
"unit": "bun test",
"clean": "rm -rf ./dist",
"dev": "bun run build -- --watch ./src"
"dev": "bun run build -- --watch ./src",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/fonts
## 0.1.3
### Patch Changes
- 10995e0: Use NPM Trusted publishing for publishing the package.
## 0.1.2
### Patch Changes
+6 -2
View File
@@ -8,7 +8,7 @@
}
},
"sideEffects": false,
"version": "0.1.2",
"version": "0.1.3",
"dependencies": {
"@gitbook/api": "catalog:"
},
@@ -24,7 +24,8 @@
"typecheck": "tsc --noEmit",
"dev": "bun run build -- --watch ./src",
"clean": "rm -rf ./dist && rm -rf ./src/data",
"unit": "bun test"
"unit": "bun test",
"publish-to-npm": "../../scripts/publish-if-new.sh"
},
"files": ["dist", "bin", "README.md", "CHANGELOG.md"],
"engines": {
@@ -33,5 +34,8 @@
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"url": "https://github.com/GitbookIO/gitbook"
}
}
+55
View File
@@ -1,5 +1,60 @@
# gitbook
## 0.20.0
### Minor Changes
- 1d92dc1: Update site layout and blocks with container queries and better transitions
### Patch Changes
- 8fdc5c8: Fix webframe height issue
- 9022d87: Add breadcrumbs to search results
- 0c01832: SearchInput fixes — Translate Clear button, Bold theme color, "Esc" keyboard shortcut
- 1d1f0a7: Fix search container shadow
- 2aa4f2e: Update dropdown menu styling
- a49781e: Printable collapsed code block
- 7d6a316: Revert contentref anchor title
- e27986b: Reword "Everywhere" search scope to "All docs"
- 758b34d: Move the "Ask <query>" to the bottom of search results if query is not a question
- 0ae7cfd: Improve AI Search branding
- f9f8011: Add alt text support to card covers
- ebbb476: Improve ancestor label in references
- 1b0613e: Tweak language dropdown styling
- 3355960: Fix ToggeableLinkItem display
- c31e564: Fix icon resolution for page group
- 55c0b03: Support translations and generic variants together
- 3876f70: Edgeless Assistant window on `depth: flat` sites
- 683bb8f: Add confirmation to "Copy MCP URL" page action
- 4534ea8: Implement unique title tags for sections & variants
- 6d56df7: Treat pages without visible TOC items as `no-toc`
- 8606f22: Add padding to TOC page group items
- 3676d64: Update search scope UI
- c32d449: Fix OpenAPI path display
- 8ce7322: Add OpenAPI servers selection
- 2c3066e: Improve OAuth2 scopes handling in OpenAPI
- 6b1c9fe: Fix opacity of ScrollContainer buttons on bold themes
- Updated dependencies [b4a021a]
- Updated dependencies [a512c90]
- Updated dependencies [df1966d]
- Updated dependencies [b45feaf]
- Updated dependencies [10995e0]
- Updated dependencies [f9f8011]
- Updated dependencies [10995e0]
- Updated dependencies [8ce7322]
- Updated dependencies [2c3066e]
- @gitbook/react-openapi@1.5.3
- @gitbook/openapi-parser@3.0.5
- @gitbook/react-contentkit@0.7.8
- @gitbook/browser-types@0.1.3
- @gitbook/cache-tags@0.3.4
- @gitbook/react-math@0.6.4
- @gitbook/colors@0.4.3
- @gitbook/embed@0.1.5
- @gitbook/fonts@0.1.3
- @gitbook/icons@0.3.4
- @gitbook/expr@1.2.4
## 0.19.2
### Patch Changes
-5
View File
@@ -172,11 +172,6 @@ const testCases: TestsCase[] = [
contentBaseURL: 'https://docs.realapp.link',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.plaza.finance',
contentBaseURL: 'https://docs.plaza.finance',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.publicai.io',
contentBaseURL: 'https://docs.publicai.io',
+107 -29
View File
@@ -33,9 +33,35 @@ import {
headerLinks,
runTestCases,
waitForCookiesDialog,
waitForCoverImages,
waitForNotFound,
} from './util';
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-time-greeting"]');
if (greeting) {
greeting.textContent = 'Good morning';
}
};
const overrideAIResponse = () => {
const userMessage = document.querySelector('[data-testid="ai-chat-message-user"]');
if (userMessage) {
userMessage.textContent = '[Replaced message] Chat message sent by the user';
}
const assistantMessage = document.querySelectorAll(
'[data-testid="ai-chat-message-assistant"] .ai-response-document'
);
assistantMessage.forEach((message) => {
message.innerHTML = '[Replaced message] AI chat response';
});
const suggestions = document.querySelectorAll('[data-testid="ai-chat-followup-suggestion"]');
suggestions.forEach((suggestion) => {
suggestion.textContent = 'Follow-up suggestion';
});
};
const searchTestCases: Test[] = [
{
name: 'Search - AI Mode: None - Complete flow',
@@ -52,7 +78,9 @@ const searchTestCases: Test[] = [
// Fill search input, expecting search results
await searchInput.fill('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
await expect(page.getByTestId('search-results')).toBeVisible({
timeout: 10_000,
});
const pageResults = await page.getByTestId('search-page-result').all();
await expect(pageResults.length).toBeGreaterThanOrEqual(1);
const pageSectionResults = await page.getByTestId('search-page-section-result').all();
@@ -97,9 +125,6 @@ const searchTestCases: Test[] = [
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
// TODO: Re-enable the following tests when we have fixed the AI Search timing out:
// - Search - AI Mode: Search - Complete flow
// - Search - AI Mode: Search - URL query (Initial)
{
name: 'Search - AI Mode: Search - URL query (Results)',
url: `${getCustomizationURL({
@@ -107,16 +132,12 @@ const searchTestCases: Test[] = [
mode: CustomizationAIMode.Search,
},
})}&q=gitbook`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-input')).toHaveValue('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
// TODO: Re-enable the following tests when we have fixed the AI Search timing out:
// - Ask - AI Mode: Search - URL query (Ask initial)
// - Ask - AI Mode: Search - URL query (Ask results)
{
name: 'Ask - AI Mode: Assistant - Complete flow',
url: getCustomizationURL({
@@ -124,25 +145,32 @@ const searchTestCases: Test[] = [
mode: CustomizationAIMode.Assistant,
},
}),
screenshot: false,
run: async (page) => {
const searchInput = page.locator('css=[data-testid="search-input"]');
// Focus search input, expecting recommended questions
await searchInput.focus();
// TODO: Re-enable this part of the test when we have fixed the AI Search timing out
// await expect(page.getByTestId('search-results')).toBeVisible();
// const recommendedQuestions = await page
// .getByTestId('search-recommended-question')
// .all();
// await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
await expect(page.getByTestId('search-results')).toBeVisible({
timeout: 30_000,
});
const recommendedQuestions = await page
.getByTestId('search-recommended-question')
.all();
await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
// Fill search input, expecting AI search option
await searchInput.fill('What is gitbook?');
await searchInput.fill(AI_PROMPT);
const aiSearchResult = page.getByTestId('search-ask-question');
await expect(aiSearchResult).toBeVisible();
await aiSearchResult.click();
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-message-user').first()).toHaveText(AI_PROMPT);
await expect(page.getByTestId('ai-chat-message-assistant').first()).toBeVisible();
await expect(page.getByTestId('ai-chat-followup-suggestion')).toHaveCount(3, {
timeout: 60_000,
});
// Override text content for visual consistency in screenshots
await page.evaluate(overrideAIResponse);
},
},
{
@@ -152,11 +180,12 @@ const searchTestCases: Test[] = [
mode: CustomizationAIMode.Assistant,
},
}),
screenshot: false,
run: async (page) => {
await page.keyboard.press('ControlOrMeta+I');
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
// Override text content for visual consistency in screenshots
await page.evaluate(overrideAIInitialState);
},
},
{
@@ -171,6 +200,8 @@ const searchTestCases: Test[] = [
await page.getByTestId('ai-chat-button').click();
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
// Override text content for visual consistency in screenshots
await page.evaluate(overrideAIInitialState);
},
},
{
@@ -180,12 +211,13 @@ const searchTestCases: Test[] = [
mode: CustomizationAIMode.Assistant,
},
})}&ask=`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).not.toBeFocused();
await expect(page.getByTestId('search-input')).not.toHaveValue('What is GitBook?');
await expect(page.getByTestId('search-input')).toBeEmpty();
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
// Override text content for visual consistency in screenshots
await page.evaluate(overrideAIInitialState);
},
},
{
@@ -194,17 +226,18 @@ const searchTestCases: Test[] = [
ai: {
mode: CustomizationAIMode.Assistant,
},
})}&ask=What+is+GitBook%3F`,
screenshot: false,
})}&ask=${encodeURIComponent(AI_PROMPT)}`,
run: async (page) => {
await expect(page.getByTestId('search-input')).not.toBeFocused();
await expect(page.getByTestId('search-input')).not.toHaveValue('What is GitBook?');
await expect(page.getByTestId('ai-chat')).toBeVisible({
timeout: 15_000,
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-message-user').first()).toHaveText(AI_PROMPT);
await expect(page.getByTestId('ai-chat-message-assistant').first()).toBeVisible();
await expect(page.getByTestId('ai-chat-followup-suggestion')).toHaveCount(3, {
timeout: 60_000,
});
await expect(page.getByTestId('ai-chat-message').first()).toHaveText(
'What is GitBook?'
);
// Override text content for visual consistency in screenshots
await page.evaluate(overrideAIResponse);
},
},
];
@@ -337,6 +370,37 @@ const testCases: TestsCase[] = [
).toBeVisible();
},
},
{
name: 'Switch variant with alternate link in metadata',
url: 'rfcs',
run: async (page) => {
const spaceDropdown = page
.locator('[data-testid="space-dropdown-button"]')
.locator('visible=true');
await spaceDropdown.click();
const variantSelectionDropdown = page.locator(
'css=[data-testid="dropdown-menu"]'
);
// Click the variant space called 'Multi-Variants' for which
// there is an alternate link in the current (RFC variant) page metadata
await variantSelectionDropdown
.getByRole('menuitem', {
name: 'Multi-Variants',
})
.click();
// It should navigate to the alternate link defined in the metadata (a completely different page)
await page.waitForURL((url) =>
url.pathname.includes('multi-variants/reference/api-reference/pets')
);
// Verify we are on the correct page by checking the h1
await expect(
page.getByRole('heading', { level: 1, name: 'Pets' })
).toBeVisible();
},
},
],
},
{
@@ -848,6 +912,11 @@ const testCases: TestsCase[] = [
url: 'blocks/cards',
fullPage: true,
},
{
name: 'Updates',
url: 'blocks/updates',
fullPage: true,
},
{
name: 'Math',
url: 'blocks/math',
@@ -906,7 +975,10 @@ const testCases: TestsCase[] = [
{
name: 'With cover',
url: 'page-options/page-with-cover',
run: waitForCookiesDialog,
run: async (page) => {
await waitForCookiesDialog(page);
await waitForCoverImages(page);
},
},
{
name: 'With cover for dark mode',
@@ -921,12 +993,18 @@ const testCases: TestsCase[] = [
{
name: 'With hero cover',
url: 'page-options/page-with-hero-cover',
run: waitForCookiesDialog,
run: async (page) => {
await waitForCookiesDialog(page);
await waitForCoverImages(page);
},
},
{
name: 'With cover and no TOC',
url: 'page-options/page-with-cover-and-no-toc',
run: waitForCookiesDialog,
run: async (page) => {
await waitForCookiesDialog(page);
await waitForCoverImages(page);
},
screenshot: {
waitForTOCScrolling: false,
},
+7
View File
@@ -154,6 +154,13 @@ export async function waitForNotFound(_page: Page, response: Response | null) {
expect(response?.status()).toBe(404);
}
export async function waitForCoverImages(page: Page) {
// Wait for cover images to exist (not the shimmer placeholder)
await expect(page.locator('img[alt="Page cover"]').first()).toBeVisible({
timeout: 10_000,
});
}
/**
* Transform test cases into Playwright tests and run it.
*/
@@ -8,9 +8,6 @@
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
@@ -8,9 +8,6 @@
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"env": {
"preview": {
"vars": {
@@ -12,9 +12,6 @@
"directory": "../../.open-next/assets",
"binding": "ASSETS"
},
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.19.2",
"version": "0.20.0",
"private": true,
"dependencies": {
"@cloudflare/workers-types": "^4.20251011.0",
@@ -8,16 +8,16 @@
"@gitbook/browser-types": "workspace:*",
"@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*",
"@gitbook/embed": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/expr": "workspace:*",
"@gitbook/embed": "workspace:*",
"@gitbook/fonts": "workspace:*",
"@gitbook/icons": "workspace:*",
"@gitbook/openapi-parser": "workspace:*",
"@gitbook/react-contentkit": "workspace:*",
"@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*",
"@modelcontextprotocol/sdk": "^1.17.5",
"@modelcontextprotocol/sdk": "1.17.5",
"@opennextjs/aws": "^3.8.5",
"@opennextjs/cloudflare": "^1.11.0",
"@radix-ui/react-checkbox": "^1.0.4",
@@ -36,6 +36,7 @@
"classnames": "catalog:",
"direction": "^2.0.1",
"event-iterator": "^2.0.0",
"feed": "^5.1.0",
"image-size": "^2.0.2",
"js-cookie": "^3.0.5",
"jsontoxml": "^1.0.1",
@@ -104,7 +105,8 @@
"ts-essentials": "^10.0.1",
"typescript": "catalog:",
"vercel": "^39.3.0",
"wrangler": "^4.43.0"
"wrangler": "^4.43.0",
"rss-parser": "^3.13.0"
},
"scripts": {
"generate": "./scripts/generate.sh",
@@ -22,7 +22,11 @@ export default async function SiteDynamicLayout({
const withTracking = shouldTrackEvents(await headers());
return (
<CustomizationRootLayout forcedTheme={forcedTheme} context={context}>
<CustomizationRootLayout
className="site-background"
forcedTheme={forcedTheme}
context={context}
>
<SiteLayout
context={context}
forcedTheme={forcedTheme}
@@ -1,16 +1,7 @@
import type { RouteLayoutParams } from '@/app/utils';
import { EmbeddableAssistantPage } from '@/components/Embeddable';
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
export const dynamic = 'force-static';
type PageProps = {
params: Promise<RouteLayoutParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableDynamicContext(params);
return <EmbeddableAssistantPage context={context} />;
export default async function Page() {
return <EmbeddableAssistantPage />;
}
@@ -19,7 +19,7 @@ export default async function SiteStaticLayout({
const withTracking = shouldTrackEvents();
return (
<CustomizationRootLayout context={context}>
<CustomizationRootLayout className="site-background" context={context}>
<SiteLayout
context={context}
withTracking={withTracking}
@@ -1,16 +1,7 @@
import type { RouteLayoutParams } from '@/app/utils';
import { EmbeddableAssistantPage } from '@/components/Embeddable';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
export const dynamic = 'force-static';
type PageProps = {
params: Promise<RouteLayoutParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableStaticContext(params);
return <EmbeddableAssistantPage context={context} />;
export default async function Page() {
return <EmbeddableAssistantPage />;
}
@@ -0,0 +1,12 @@
import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@/app/utils';
import { servePageRSS } from '@/routes/rss';
import type { NextRequest } from 'next/server';
export const dynamic = 'force-static';
export async function GET(_request: NextRequest, { params }: { params: Promise<RouteParams> }) {
const { context } = await getStaticSiteContext(await params);
const pathname = getPagePathFromParams(await params);
return servePageRSS(context, pathname);
}
@@ -16,14 +16,14 @@ export function AIMessageView(
) {
const { message, context, withToolCalls = true, withLinkPreviews = true } = props;
return (
return message.steps.length > 0 ? (
<div className="flex flex-col gap-2">
{message.steps.map((step, index) => {
return (
<div
key={index}
className={tcls(
'flex animate-fade-in-slow flex-col gap-2',
'flex flex-col gap-2',
step.content.nodes.length > 0 ? 'has-content' : ''
)}
>
@@ -35,7 +35,7 @@ export function AIMessageView(
wrapBlocksInSuspense: false,
withLinkPreviews,
}}
style="mt-2 space-y-4 empty:hidden"
style="ai-response-document mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
/>
{withToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (
@@ -45,5 +45,5 @@ export function AIMessageView(
);
})}
</div>
);
) : null;
}
@@ -39,7 +39,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont
const { toolCall, context } = props;
return (
<div className="flex origin-left animate-scale-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"
@@ -160,7 +160,7 @@ async function DescriptionForSearchToolCall(props: {
const hasResults = toolCall.results.length > 0;
return (
<details className={tcls('-ml-5 group flex w-full flex-col', hasResults ? 'gap-2' : '')}>
<details className="-ml-5 group flex w-full flex-col">
<summary
className={tcls(
'-mx-2 flex list-none items-center gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md pr-4 pl-7 transition-colors marker:hidden',
@@ -187,7 +187,7 @@ async function DescriptionForSearchToolCall(props: {
) : null}
</summary>
{hasResults ? (
<div className="hide-scrollbar mt-1 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all transition-discrete duration-500 group-open:max-h-96 group-open:opacity-11">
<div className="hide-scrollbar mt-4 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all transition-discrete duration-500 group-open:max-h-96 group-open:opacity-11">
<ol className="space-y-1">
{searchResultsWithHrefs.map((result, index) => (
<li
@@ -2,12 +2,14 @@
import { t, tString, useLanguage } from '@/intl/client';
import type { TranslationLanguage } from '@/intl/translations';
import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import {
type AIChatController,
type AIChatState,
useAI,
useAIChatController,
useAIChatState,
} from '../AI';
@@ -23,15 +25,15 @@ import {
import { useTrackEvent } from '../Insights';
import { useNow } from '../hooks';
import { Button } from '../primitives';
import { ScrollContainer } from '../primitives/ScrollContainer';
import { AIChatControlButton } from './AIChatControlButton';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
import { AIChatMessages } from './AIChatMessages';
import AIChatSuggestedQuestions from './AIChatSuggestedQuestions';
export function AIChat(props: { trademark: boolean }) {
const { trademark } = props;
export function AIChat() {
const { config } = useAI();
const language = useLanguage();
const chat = useAIChatState();
const chatController = useAIChatController();
@@ -65,21 +67,22 @@ export function AIChat(props: { trademark: boolean }) {
}
}, [chat.opened, trackEvent]);
if (!chat.opened) {
return null;
}
return (
<div
data-testid="ai-chat"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enter-from-right lg:pr-4 lg:pl-0 xl:w-96"
className={tcls(
'ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl scroll-mt-36 px-4 py-4 transition-[width,opacity,margin,display] transition-discrete duration-300 sm:px-6 lg:fixed lg:w-80 lg:p-0 xl:w-96',
chat.opened
? 'lg:starting:ml-0 lg:starting:w-0 lg:starting:opacity-0'
: 'hidden lg:ml-0 lg:w-0! lg:opacity-0'
)}
>
<EmbeddableFrame className="relative circular-corners:rounded-3xl rounded-corners:rounded-md depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle">
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base transition-all duration-300 max-lg:circular-corners:rounded-3xl max-lg:rounded-corners:rounded-md max-lg:border lg:w-80 xl:w-96">
<EmbeddableFrameHeader>
<AIChatDynamicIcon trademark={trademark} />
<AIChatDynamicIcon trademark={config.trademark} />
<EmbeddableFrameHeaderMain>
<EmbeddableFrameTitle>
{getAIChatName(language, trademark)}
{getAIChatName(language, config.trademark)}
</EmbeddableFrameTitle>
<AIChatSubtitle chat={chat} />
</EmbeddableFrameHeaderMain>
@@ -96,7 +99,7 @@ export function AIChat(props: { trademark: boolean }) {
</EmbeddableFrameButtons>
</EmbeddableFrameHeader>
<EmbeddableFrameBody>
<AIChatBody chatController={chatController} chat={chat} trademark={trademark} />
<AIChatBody chatController={chatController} chat={chat} />
</EmbeddableFrameBody>
</EmbeddableFrame>
</div>
@@ -143,10 +146,33 @@ export function AIChatSubtitle(props: {
const language = useLanguage();
return (
<EmbeddableFrameSubtitle className={chat.loading ? 'h-3 opacity-11' : 'h-0 opacity-0'}>
{chat.messages[chat.messages.length - 1]?.content
? tString(language, 'ai_chat_working')
: tString(language, 'ai_chat_thinking')}
<EmbeddableFrameSubtitle
className={tcls('relative', chat.loading ? 'h-3 opacity-11' : 'h-0 opacity-0')}
>
<span
className={tcls(
'absolute left-0',
chat.loading
? chat.messages[chat.messages.length - 1]?.content
? 'animate-blur-in-slow'
: 'hidden'
: 'animate-blur-out-slow'
)}
>
{t(language, 'ai_chat_working')}
</span>
<span
className={tcls(
'absolute left-0',
chat.loading
? chat.messages[chat.messages.length - 1]?.content
? 'animate-blur-out-slow'
: 'animate-blur-in-slow'
: 'hidden'
)}
>
{t(language, 'ai_chat_thinking')}
</span>
</EmbeddableFrameSubtitle>
);
}
@@ -157,20 +183,13 @@ export function AIChatSubtitle(props: {
export function AIChatBody(props: {
chatController: AIChatController;
chat: AIChatState;
trademark: boolean;
welcomeMessage?: string;
suggestions?: string[];
}) {
const { chatController, chat, trademark, suggestions } = props;
const { chatController, chat, suggestions } = props;
const { trademark } = useAI().config;
const [input, setInput] = React.useState('');
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
// Ref for the last user message element
const lastUserMessageRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLDivElement>(null);
const [inputHeight, setInputHeight] = React.useState(0);
const language = useLanguage();
const now = useNow(60 * 60 * 1000); // Refresh every hour for greeting
@@ -184,67 +203,43 @@ export function AIChatBody(props: {
return tString(language, 'ai_chat_assistant_greeting_evening');
}, [now, language]);
// Auto-scroll to the latest user message when messages change
React.useEffect(() => {
if (chat.messages.length > 0 && lastUserMessageRef.current) {
lastUserMessageRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [chat.messages.length]);
React.useEffect(() => {
const timeout = setTimeout(() => {
if (lastUserMessageRef.current) {
lastUserMessageRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, 100);
// We want the chat messages to scroll underneath the input, but they should scroll past the input when scrolling all the way down.
// The best way to do this is to observe the input height and adjust the padding bottom of the scroll container accordingly.
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
setInputHeight(entry.contentRect.height + 32);
});
});
if (inputRef.current) {
observer.observe(inputRef.current);
}
return () => {
observer.disconnect();
clearTimeout(timeout);
};
}, []);
return (
<>
<div
ref={scrollContainerRef}
className="gutter-stable flex grow scroll-pt-4 flex-col gap-4 overflow-y-auto p-4"
style={{
paddingBottom: `${inputHeight}px`,
}}
<ScrollContainer
className="shrink grow basis-80 animate-fade-in-slow [container-type:size]"
contentClassName="p-4 gutter-stable flex flex-col gap-4"
orientation="vertical"
fadeEdges={['leading']}
active={`message-group-${chat.messages.filter((message) => message.role === 'user').length - 1}`}
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex size-32 animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle">
<AIChatIcon
state="intro"
trademark={trademark}
className="size-16 animate-[present_500ms_200ms_both]"
/>
</div>
<div className="animate-[fadeIn_500ms_400ms_both]">
<h5 className=" text-center font-bold text-lg text-tint-strong">
{timeGreeting}
</h5>
<p className="text-center text-tint">
{t(language, 'ai_chat_assistant_description')}
</p>
<div className="flex grow flex-col">
<div className="my-auto flex flex-row items-center gap-4 pb-6 [@container(min-height:400px)]:flex-col">
<div
className="flex size-16 shrink-0 animate-scale-in items-center justify-center rounded-full bg-primary-solid/1 [@container(min-height:400px)]:size-32"
style={{ animationDelay: '.3s' }}
>
<AIChatIcon
state="intro"
trademark={trademark}
className="size-8 text-primary [@container(min-height:400px)]:size-16"
/>
</div>
<div className="flex flex-col items-start [@container(min-height:400px)]:items-center">
<h5
className="animate-blur-in-slow font-bold text-lg text-tint-strong [@container(min-height:400px)]:text-center"
style={{ animationDelay: '.5s' }}
data-testid="ai-chat-time-greeting"
>
{timeGreeting}
</h5>
<p
className="animate-blur-in-slow text-tint [@container(min-height:400px)]:text-center"
style={{ animationDelay: '.6s' }}
>
{t(language, 'ai_chat_assistant_description')}
</p>
</div>
</div>
{!chat.error ? (
<AIChatSuggestedQuestions
@@ -254,17 +249,11 @@ export function AIChatBody(props: {
) : null}
</div>
) : (
<AIChatMessages
chat={chat}
chatController={chatController}
lastUserMessageRef={lastUserMessageRef}
/>
<AIChatMessages chat={chat} chatController={chatController} />
)}
</div>
<div
ref={inputRef}
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-linear-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
>
</ScrollContainer>
<div className="flex flex-col gap-2 px-4 pb-4">
{/* Display an error banner when something went wrong. */}
{chat.error ? <AIChatError chatController={chatController} /> : null}
@@ -23,7 +23,7 @@ export function AIChatButton(props: {
iconOnly={!showLabel}
size="medium"
variant="header"
className="h-9 px-2.5 max-md:[&_.button-content]:hidden"
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)}
@@ -3,6 +3,7 @@ import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import { useEffect, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useAIChatState } from '../AI/useAIChat';
import { Button, HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
@@ -19,6 +20,7 @@ export function AIChatInput(props: {
const { value, onChange, onSubmit, disabled, loading } = props;
const language = useLanguage();
const chat = useAIChatState();
const inputRef = useRef<HTMLTextAreaElement>(null);
@@ -32,7 +34,7 @@ export function AIChatInput(props: {
};
useEffect(() => {
if (!disabled && !loading) {
if (chat.opened && !disabled && !loading) {
// Add a small delay to ensure the input is rendered before focusing
// This fixes inconsistent focus behaviour across browsers
const timeout = setTimeout(() => {
@@ -41,7 +43,7 @@ export function AIChatInput(props: {
return () => clearTimeout(timeout);
}
}, [disabled, loading]);
}, [disabled, loading, chat.opened]);
useHotkeys(
'mod+i',
@@ -55,7 +57,7 @@ export function AIChatInput(props: {
);
return (
<div className="depth-subtle:has-[textarea:focus]:-translate-y-px relative flex flex-col overflow-hidden circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-base/9 depth-subtle:shadow-sm shadow-tint/6 ring-1 ring-tint-subtle backdrop-blur-lg transition-all depth-subtle:has-[textarea:focus]:shadow-lg has-[textarea:focus]:shadow-primary-subtle has-[textarea:focus]:ring-2 has-[textarea:focus]:ring-primary-hover contrast-more:bg-tint-base dark:shadow-tint-1">
<div className="depth-subtle:has-[textarea:focus]:-translate-y-px relative flex animate-blur-in-slow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-xl bg-tint-base/9 depth-subtle:shadow-sm shadow-tint/6 ring-1 ring-tint-subtle backdrop-blur-lg transition-all depth-subtle:has-[textarea:focus]:shadow-lg has-[textarea:focus]:shadow-primary-subtle has-[textarea:focus]:ring-2 has-[textarea:focus]:ring-primary-hover contrast-more:bg-tint-base dark:shadow-tint-1">
<textarea
ref={inputRef}
disabled={disabled || loading}
@@ -106,7 +108,7 @@ export function AIChatInput(props: {
</div>
) : null}
<div className="absolute inset-x-0 bottom-0 flex items-center gap-2 px-2 py-2">
<HoverCardRoot>
<HoverCardRoot openDelay={500}>
<HoverCard
className="max-w-xs bg-tint p-2 text-sm text-tint"
arrow={{ className: 'fill-tint-3' }}
@@ -2,7 +2,6 @@ import { useLanguage } from '@/intl/client';
import { tString } from '@/intl/translate';
import { tcls } from '@/lib/tailwind';
import { AIMessageRole } from '@gitbook/api';
import type React from 'react';
import type { AIChatController, AIChatState } from '../AI';
import { AIChatToolConfirmations } from './AIChatToolConfirmations';
import { AIResponseFeedback } from './AIResponseFeedback';
@@ -11,86 +10,124 @@ import { AIChatFollowupSuggestions } from './AiChatFollowupSuggestions';
export function AIChatMessages(props: {
chat: AIChatState;
chatController: AIChatController;
lastUserMessageRef?: React.RefObject<HTMLDivElement | null>;
}) {
const { chat, chatController, lastUserMessageRef } = props;
const { chat, chatController } = props;
return (
<>
{chat.messages.map((message, index) => {
const isLastMessage = index === chat.messages.length - 1;
const isLastUserMessage =
message.role === AIMessageRole.User &&
index === chat.messages.map((m) => m.role).lastIndexOf(AIMessageRole.User);
// Group messages: user messages start a new group, all following messages until next user message belong to that group
type Message = (typeof chat.messages)[0];
type MessageGroup = { message: Message; originalIndex: number };
const messageGroups: Array<Array<MessageGroup>> = [];
let currentGroup: Array<MessageGroup> = [];
return (
<div
ref={isLastUserMessage ? lastUserMessageRef : undefined}
data-testid="ai-chat-message"
className={tcls(
message.content ? 'animate-fade-in-slow' : '',
'shrink-0',
'last:min-h-[calc(100%-5rem)]',
'scroll-mt-36',
'lg:scroll-mt-0',
'flex flex-col gap-6',
'break-words',
'group/message',
message.role === AIMessageRole.User
? 'max-w-[80%] self-end circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint px-4 py-2'
: 'text-tint-strong'
)}
style={{
animationDelay: `${Math.min(index * 0.05, 0.5)}s`,
}}
key={index}
>
{message.content ? message.content : null}
chat.messages.forEach((message, index) => {
if (message.role === AIMessageRole.User) {
// Start a new group
if (currentGroup.length > 0) {
messageGroups.push(currentGroup);
}
currentGroup = [{ message, originalIndex: index }];
} else {
// Add to current group
currentGroup.push({ message, originalIndex: index });
}
});
{isLastMessage && chat.loading ? (
<div className="flex w-full animate-fade-in-slow flex-col gap-2">
{!message.content ? <HoldMessage /> : null}
<LoadingSkeleton />
</div>
) : null}
// Add the last group if it exists
if (currentGroup.length > 0) {
messageGroups.push(currentGroup);
}
{isLastMessage ? (
<>
{!chat.loading &&
!chat.error &&
chat.query &&
chat.responseId &&
chat.pendingTools.length === 0 ? (
<AIResponseFeedback
responseId={chat.responseId}
query={chat.query}
className="-ml-1 -mt-4"
return messageGroups.map((group, groupIndex) => {
const isLastGroup = group === messageGroups[messageGroups.length - 1];
return (
<div
key={groupIndex}
id={`message-group-${groupIndex}`}
className={tcls(
'flex flex-col gap-2 pt-2',
isLastGroup ? 'shrink-0 basis-full' : '',
'transition-discrete'
)}
style={{ animationDelay: '.2s' }}
>
{group.map(({ message, originalIndex }) => {
const isLastMessage = originalIndex === chat.messages.length - 1;
return (
<div
key={originalIndex}
data-testid={
message.role === AIMessageRole.User
? 'ai-chat-message-user'
: 'ai-chat-message-assistant'
}
id={`message-${originalIndex}`}
className={tcls(
'flex flex-col gap-6',
'break-words',
'group/message',
'animate-blur-in-slow',
isLastMessage ? 'basis-full' : '',
message.role === AIMessageRole.User
? 'max-w-[80%] origin-top-right self-end circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint px-4 py-2'
: 'origin-top-left text-tint-strong'
)}
style={{
animationDelay: `${Math.min(originalIndex * 0.1, 0.6)}s`,
}}
>
{message.content}
{isLastMessage && message.role === AIMessageRole.Assistant ? (
<div
className={tcls(
'flex w-full shrink-0 flex-col gap-2 starting:opacity-0 transition-all transition-discrete duration-500',
chat.loading ? '' : 'hidden opacity-0'
)}
>
<HoldMessage className={message.content ? 'hidden' : ''} />
<LoadingSkeleton />
</div>
) : null}
{isLastMessage ? (
<>
{!chat.loading &&
!chat.error &&
chat.query &&
chat.responseId &&
chat.pendingTools.length === 0 ? (
<AIResponseFeedback
responseId={chat.responseId}
query={chat.query}
className="-ml-1 -mt-4"
/>
) : null}
<AIChatToolConfirmations chat={chat} />
<AIChatFollowupSuggestions
chat={chat}
chatController={chatController}
/>
) : null}
<AIChatToolConfirmations chat={chat} />
<AIChatFollowupSuggestions
chat={chat}
chatController={chatController}
/>
</>
) : null}
</div>
);
})}
</>
);
</>
) : null}
</div>
);
})}
</div>
);
});
}
export function HoldMessage({
breakLines = false,
className = '',
className,
}: { breakLines?: boolean; className?: string }) {
const language = useLanguage();
return (
<div
className={tcls(
'animate-[heightIn_500ms_4500ms_ease_both] overflow-hidden py-2 text-tint-subtle',
'animate-[heightIn_.5s_5s_ease_both] overflow-hidden py-2 text-tint-subtle [&.hidden]:animate-[heightOut_1s_ease-in_both]',
className
)}
>
@@ -99,7 +136,7 @@ export function HoldMessage({
.map((word, index) => (
<span
key={index}
className="animate-fade-in-slow"
className="animate-blur-in-slow"
style={{
animationDelay: `${5000 + index * 200}ms`,
}}
@@ -113,7 +150,7 @@ export function HoldMessage({
.map((word, index) => (
<span
key={index}
className="animate-fade-in-slow"
className="animate-blur-in-slow"
style={{
animationDelay: `${10000 + index * 200}ms`,
}}
@@ -131,9 +168,9 @@ function LoadingSkeleton() {
{Array.from({ length: 7 }).map((_, index) => (
<div
key={index}
className="h-4 animate-[fadeIn_500ms_ease_both,pulse_1.5s_infinite] circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-4"
className="h-4 animate-[blurIn_500ms_ease-out_both,pulse_1.5s_infinite] circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-solid/2"
style={{
width: `calc(${(index % 4) * 20 + 10}% - 4px)`,
width: `calc(${(4 - (index % 4)) * 8 + 14}% - 4px)`,
animationDelay: `${index * 0.1}s`,
}}
/>
@@ -17,15 +17,15 @@ export default function AIChatSuggestedQuestions(props: {
} = props;
return (
<div className="flex flex-col items-center gap-2">
<div className="flex flex-col items-start gap-2 self-start">
{suggestions.map((question, index) => (
<Button
key={question}
variant="secondary"
variant="blank"
size="medium"
className="max-w-full animate-[present_500ms_both] whitespace-normal"
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: `${800 + index * 100}ms`,
animationDelay: `${1000 + index * 100}ms`,
}}
onClick={() => chatController.postMessage({ message: question })}
>
@@ -29,7 +29,7 @@ export function AIChatToolConfirmations(props: {
);
return (
<div className="flex w-full flex-wrap justify-end gap-2">
<div className="flex w-full flex-wrap justify-end gap-2 empty:hidden">
{chat.pendingTools.map((tool, index) => (
<div
className="flex w-full animate-present-slow flex-col gap-1"
@@ -24,7 +24,9 @@ export function AIResponseFeedback(props: {
};
return (
<div className={tcls('flex h-fit items-center', className)}>
<div
className={tcls('flex h-fit animate-[heightIn_.5s_ease_both] items-center', className)}
>
<Button
icon="thumbs-up"
iconOnly
@@ -35,7 +37,7 @@ export function AIResponseFeedback(props: {
rating !== null && rating !== 1 && 'px-0 text-[0rem] opacity-0'
)}
size="medium"
style={{ animationDuration: '.5s' }}
style={{ animationDelay: '.5s', animationDuration: '.5s' }}
onClick={() => handleRating(1)}
disabled={rating !== null}
active={rating === 1}
@@ -51,7 +53,7 @@ export function AIResponseFeedback(props: {
rating !== null && rating !== -1 && 'px-0 text-[0rem] opacity-0'
)}
size="medium"
style={{ animationDelay: '.2s', animationDuration: '.5s' }}
style={{ animationDelay: '.7s', animationDuration: '.5s' }}
onClick={() => handleRating(-1)}
disabled={rating !== null}
active={rating === -1}
@@ -59,8 +61,8 @@ export function AIResponseFeedback(props: {
/>
{rating !== null ? (
<span
className="ml-2 animate-fade-in-slow text-tint-subtle"
style={{ animationDelay: '.3s' }}
className="ml-2 animate-blur-in-slow text-tint-subtle"
style={{ animationDelay: '.5s' }}
>
{t(language, 'was_this_helpful_thank_you')}
</span>
@@ -15,22 +15,28 @@ export function AIChatFollowupSuggestions(props: {
}
return (
<div className="mt-auto flex w-full flex-wrap justify-end gap-2">
{chat.followUpSuggestions.map((suggestion, index) => (
<Button
key={index}
onClick={() => {
chatController.postMessage({ message: suggestion });
}}
label={suggestion}
className="whitespace-normal! max-w-full animate-[present_500ms_both] text-left ring-1 ring-tint-subtle"
size="medium"
variant="blank"
style={{
animationDelay: `${250 + Math.min(index * 50, 150)}ms`,
}}
/>
))}
<div className="flex grow flex-col">
<div
className="sticky bottom-0 mt-auto flex flex-col items-start gap-2"
data-testid="ai-chat-followup-suggestions"
>
{chat.followUpSuggestions.map((suggestion, index) => (
<Button
data-testid="ai-chat-followup-suggestion"
key={index}
onClick={() => {
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 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`,
}}
/>
))}
</div>
</div>
);
}
@@ -27,7 +27,7 @@ export function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
ancestorBlocks={[]}
context={context}
nodes={fragment.nodes}
style={['space-y-4']}
style={['contents']}
/>
}
>
@@ -1,84 +1,30 @@
'use client';
import * as Popover from '@radix-ui/react-popover';
import type React from 'react';
import { tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { Tooltip } from '@/components/primitives';
export function AnnotationPopover(props: { children: React.ReactNode; body: React.ReactNode }) {
const { children, body } = props;
const language = useLanguage();
return (
<Popover.Root>
<Popover.Trigger asChild>
<button
data-testid="annotation-button"
aria-label={tString(language, 'annotation_button_label')}
className={tcls(
'decoration-dotted',
'decoration-1',
'underline',
'underline-offset-2'
)}
>
{children}
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className={tcls(
'text-sm',
'max-w-[280px]',
'bg-tint',
'ring-1',
'ring-tint',
'rounded-sm',
'shadow-1xs',
'shadow-tint-12/1',
'dark:shadow-tint-1/2',
'p-3',
'[&_p]:leading-snug',
'-outline-offset-2',
'outline-2',
'outline-primary/8',
'z-20'
)}
sideOffset={4}
>
{body}
<Popover.Arrow asChild>
<svg
viewBox="0 0 8 5"
className={tcls(
'relative',
'z-2',
'fill-tint-3', // Same as bg-tint
'stroke-tint-7', // Same as ring-tint
'[paint-order:stroke_fill]'
)}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clipAnnotation)">
<path
d="M0 0L4 4L8 0"
strokeWidth="2"
strokeLinecap="round"
stroke="inherit"
fill="inherit"
/>
</g>
<defs>
<clipPath id="clipAnnotation">
<rect width="8" height="5" fill="white" />
</clipPath>
</defs>
</svg>
</Popover.Arrow>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
<Tooltip
label={body}
contentProps={{
role: 'definition',
}}
className="bg-tint-base px-4 py-3 text-sm text-tint-strong shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1"
arrow={true}
arrowProps={{ className: 'fill-tint-1' }}
>
<dfn
data-testid="annotation-button"
className="cursor-help underline decoration-1 decoration-dotted underline-offset-2"
// biome-ignore lint/a11y/noNoninteractiveTabindex: we want to be able to focus the definition to open the tooltip
tabIndex={0}
>
{children}
</dfn>
</Tooltip>
);
}
@@ -6,6 +6,7 @@ import {
SkeletonImage,
SkeletonParagraph,
SkeletonSmall,
SkeletonUpdate,
} from '@/components/primitives';
import type { ClassValue } from '@/lib/tailwind';
@@ -34,6 +35,8 @@ import { Stepper } from './Stepper';
import { StepperStep } from './StepperStep';
import { Table } from './Table';
import { Tabs } from './Tabs';
import { Update } from './Update';
import { Updates } from './Updates';
export interface BlockProps<Block extends DocumentBlock> extends DocumentContextProps {
block: Block;
@@ -105,6 +108,10 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
return <Stepper {...props} block={block} />;
case 'stepper-step':
return <StepperStep {...props} block={block} />;
case 'updates':
return <Updates {...props} block={block} />;
case 'update':
return <Update {...props} block={block} />;
case 'if':
// If block should be processed by the API.
return null;
@@ -165,10 +172,13 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
case 'images':
case 'drawing':
return <SkeletonImage id={id} style={style} />;
case 'updates':
return <SkeletonUpdate id={id} style={style} />;
case 'image':
case 'code-line':
case 'tabs-item':
case 'column':
case 'update':
throw new Error(`Blocks (${block.type}) should be directly rendered by parent`);
default:
return nullIfNever(block);
@@ -1,9 +1,18 @@
import { type DocumentBlockContentRef, SiteInsightsLinkPosition } from '@gitbook/api';
import {
type ContentRef,
type DocumentBlockContentRef,
SiteInsightsLinkPosition,
} from '@gitbook/api';
import { Card } from '@/components/primitives';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
import { Card, type CardProps } from '@/components/primitives';
import {
type ResolvedContentRef,
resolveContentRef,
resolveContentRefFallback,
} from '@/lib/references';
import type { BlockProps } from './Block';
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>) {
const { block, context, style } = props;
@@ -16,51 +25,45 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
: null;
if (!resolved) {
return null;
const fallback = resolveContentRefFallback(block.data.ref);
if (!fallback) {
return null;
}
return (
<NotFoundRefHoverCard context={context}>
<BlockContentRefCard
contentRef={block.data.ref}
resolved={fallback}
style={style}
/>
</NotFoundRefHoverCard>
);
}
const isContentInOtherSpace =
context.contentContext?.space &&
'space' in block.data.ref &&
context.contentContext.space.id !== block.data.ref.space;
const kind = block?.data?.ref?.kind;
if ((resolved.active && kind === 'space') || isContentInOtherSpace) {
return <SpaceRefCard {...props} resolved={resolved} />;
}
return <BlockContentRefCard contentRef={block.data.ref} resolved={resolved} style={style} />;
}
function BlockContentRefCard(
props: {
resolved: ResolvedContentRef;
contentRef: ContentRef;
} & Omit<CardProps, 'href' | 'title'>
) {
const { ref, resolved, contentRef, ...rest } = props;
return (
<Card
ref={ref}
leadingIcon={resolved.icon ? resolved.icon : null}
href={resolved.href}
title={resolved.text}
style={style}
insights={{
type: 'link_click',
link: {
target: block.data.ref,
target: contentRef,
position: SiteInsightsLinkPosition.Content,
},
}}
/>
);
}
async function SpaceRefCard(
props: { resolved: ResolvedContentRef } & BlockProps<DocumentBlockContentRef>
) {
const { context, style, resolved } = props;
const spaceId = context.contentContext?.space.id;
if (!spaceId) {
return null;
}
return (
<Card
href={resolved.href}
title={resolved.text}
postTitle={resolved.subText}
style={style}
{...rest}
/>
);
}
@@ -19,6 +19,7 @@ import { plainHighlight } from './plain-highlight';
type ClientBlockProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
inlines: RenderedInline[];
inlineExprVariables: InlineExpressionVariables;
mode: BlockProps<DocumentBlockCode>['context']['mode'];
};
export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
@@ -28,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, style, inlines, inlineExprVariables } = props;
const { block, mode, style, inlines, inlineExprVariables } = props;
const blockRef = useRef<HTMLDivElement>(null);
const isInViewportRef = useRef(false);
const [isInViewport, setIsInViewport] = useState(false);
@@ -123,7 +124,9 @@ export function ClientCodeBlock(props: ClientBlockProps) {
const numberOfLinesOfCode = lines?.length ?? plainLines.length;
const collapsedLineCount =
block.data.collapsedLineCount || CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT;
const isExpandable = Boolean(expandable && numberOfLinesOfCode > collapsedLineCount);
const isExpandable = Boolean(
expandable && mode !== 'print' && numberOfLinesOfCode > collapsedLineCount
);
const codeBlockBodyId = useId();
@@ -73,6 +73,7 @@ export async function CodeBlock(props: BlockProps<DocumentBlockCode>) {
style={style}
inlines={richInlines}
inlineExprVariables={variables}
mode={context.mode}
/>
</React.Suspense>
);
@@ -80,6 +80,7 @@ export function Column(props: {
<div
className={tcls(
'flex flex-col',
'@container/column',
(verticalAlignment === VerticalAlignment.Top || !verticalAlignment) &&
'justify-start',
verticalAlignment === VerticalAlignment.Middle && 'justify-center',
@@ -36,7 +36,10 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
<HashLinkButton
id={id}
block={block}
className={tcls('-ml-6', 'pr-2')}
className={tcls(
'-ml-6 pr-2',
'[.flip-heading-hash_&]:order-last [.flip-heading-hash_&]:ml-1 [.flip-heading-hash_&]:pl-2'
)}
iconClassName={tcls('size-4')}
label="Direct link to heading"
/>
@@ -5,6 +5,7 @@ import { type ClassValue, tcls } from '@/lib/tailwind';
import { getSpaceLanguage, tString } from '@/intl/server';
import { languages } from '@/intl/translations';
import { isHeadingBlock } from '@/lib/document';
import { Block, type BlockProps } from './Block';
import { Blocks } from './Blocks';
import { getBlockTextStyle } from './spacing';
@@ -18,7 +19,7 @@ export function Hint({
const hintStyle = HINT_STYLES[block.data.style] ?? HINT_STYLES.info;
const firstNode = block.nodes[0]!;
const firstLine = getBlockTextStyle(firstNode);
const hasHeading = ['heading-1', 'heading-2', 'heading-3'].includes(firstNode.type);
const hasHeading = isHeadingBlock(firstNode);
const language = contextProps.context.contentContext
? getSpaceLanguage(contextProps.context.contentContext)
@@ -65,9 +66,9 @@ export function Hint({
{hasHeading ? (
<Block
style={tcls(
'w-full items-start py-4! pl-3 text-[1em] *:flex-none',
'w-full items-start py-4! pl-3 text-[1em]! *:flex-none',
// Heading hash styles
'flip-heading-hash pr-8',
'flip-heading-hash pr-8 [&_.hash]:bg-transparent',
hintStyle.header
)}
ancestorBlocks={[...ancestorBlocks, block]}
@@ -1,31 +1,29 @@
import { resolveContentRef } from '@/lib/references';
import { resolveContentRef, resolveContentRefFallback } from '@/lib/references';
import * as api from '@gitbook/api';
import type { IconName } from '@gitbook/icons';
import { Button } from '../primitives';
import type { InlineProps } from './Inline';
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
export async function InlineButton(props: InlineProps<api.DocumentInlineButton>) {
const { inline, context } = props;
if (!context.contentContext) {
throw new Error('InlineButton requires a contentContext');
}
const resolved = context.contentContext
? await resolveContentRef(inline.data.ref, context.contentContext)
: null;
const resolved = await resolveContentRef(inline.data.ref, context.contentContext);
const href = resolved?.href ?? resolveContentRefFallback(inline.data.ref)?.href;
if (!resolved) {
return null;
}
return (
const inlineElement = (
// Set the leading to have some vertical space between adjacent buttons
<span className="inline-button leading-12 [&:has(+.inline-button)]:mr-2">
<Button
href={resolved.href}
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',
@@ -37,4 +35,10 @@ export async function InlineButton(props: InlineProps<api.DocumentInlineButton>)
/>
</span>
);
if (!resolved) {
return <NotFoundRefHoverCard context={context}>{inlineElement}</NotFoundRefHoverCard>;
}
return inlineElement;
}
@@ -1,12 +1,17 @@
import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
import { type ContentRef, type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
import { getSpaceLanguage, tString } from '@/intl/server';
import { type TranslationLanguage, languages } from '@/intl/translations';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
import {
type ResolvedContentRef,
resolveContentRef,
resolveContentRefFallback,
} from '@/lib/references';
import { Icon } from '@gitbook/icons';
import { HoverCard, HoverCardRoot, HoverCardTrigger, StyledLink } from '../../primitives';
import { StyledLink } from '../../primitives';
import type { InlineProps } from '../Inline';
import { Inlines } from '../Inlines';
import { NotFoundRefHoverCard } from '../NotFoundRefHoverCard';
import { InlineLinkTooltip } from './InlineLinkTooltip';
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
@@ -18,52 +23,75 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
resolveAnchorText: false,
})
: null;
const { contentContext } = context;
const language = contentContext ? getSpaceLanguage(contentContext) : languages.en;
const inlinesElement = (
<Inlines
context={context}
document={document}
nodes={inline.nodes}
ancestorInlines={[...ancestorInlines, inline]}
/>
);
if (!contentContext || !resolved) {
if (!resolved) {
const fallback = resolveContentRefFallback(inline.data.ref);
return (
<HoverCardRoot>
<HoverCardTrigger>
<span className="cursor-not-allowed underline">
<Inlines
context={context}
document={document}
nodes={inline.nodes}
ancestorInlines={[...ancestorInlines, inline]}
/>
</span>
</HoverCardTrigger>
<HoverCard className="flex flex-col gap-1 p-4">
<div className="flex items-center gap-2">
<Icon icon="ban" className="size-4 text-tint-subtle" />
<h5 className="font-semibold">{tString(language, 'notfound_title')}</h5>
</div>
<p className="text-sm text-tint">{tString(language, 'notfound_link')}</p>
</HoverCard>
</HoverCardRoot>
<NotFoundRefHoverCard context={context}>
{fallback ? (
<InlineLinkAnchor href={fallback.href} contentRef={inline.data.ref} isExternal>
{inlinesElement}
</InlineLinkAnchor>
) : (
<span className="cursor-not-allowed underline">{inlinesElement}</span>
)}
</NotFoundRefHoverCard>
);
}
const isExternal = inline.data.ref.kind === 'url';
const isMailto = resolved.href.startsWith('mailto:');
const content = (
<StyledLink
const anchorElement = (
<InlineLinkAnchor
href={resolved.href}
contentRef={inline.data.ref}
isExternal={inline.data.ref.kind === 'url'}
>
{inlinesElement}
</InlineLinkAnchor>
);
if (context.withLinkPreviews) {
const language = contentContext ? getSpaceLanguage(contentContext) : languages.en;
return (
<InlineLinkTooltipWrapper inline={inline} language={language} resolved={resolved}>
{anchorElement}
</InlineLinkTooltipWrapper>
);
}
return anchorElement;
}
function InlineLinkAnchor(props: {
href: string;
contentRef: ContentRef;
isExternal?: boolean;
children: React.ReactNode;
}) {
const { href, isExternal, contentRef, children } = props;
const isMailto = href.startsWith('mailto:');
return (
<StyledLink
href={href}
insights={{
type: 'link_click',
link: {
target: inline.data.ref,
target: contentRef,
position: SiteInsightsLinkPosition.Content,
},
}}
>
<Inlines
context={context}
document={document}
nodes={inline.nodes}
ancestorInlines={[...ancestorInlines, inline]}
/>
{children}
{isMailto ? (
<Icon
icon="envelope"
@@ -77,16 +105,6 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
) : null}
</StyledLink>
);
if (context.withLinkPreviews) {
return (
<InlineLinkTooltipWrapper inline={inline} language={language} resolved={resolved}>
{content}
</InlineLinkTooltipWrapper>
);
}
return content;
}
/**
@@ -27,7 +27,10 @@ export const contentKitServerContext: ContentKitServerContext = {
codeBlock: (props) => {
return <PlainCodeBlock code={props.code} syntax={props.syntax} />;
},
// For some reason, Next thinks that this function is used in a client component
// it's likely an issue with the compiler not being able to track the usage of this function properly
markdown: async ({ className, markdown }) => {
'use server';
const parsed = await parseMarkdown(markdown);
return <div className={className} dangerouslySetInnerHTML={{ __html: parsed }} />;
},
@@ -32,8 +32,6 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
ancestorBlocks={[...ancestorBlocks, block]}
blockStyle={tcls(
'min-h-lh',
// flip heading hash icon if list item is a heading
'flip-heading-hash',
// remove margin-top for the first heading in a list
'[h2]:pt-0',
'[h3]:pt-0',
@@ -0,0 +1,32 @@
import { getSpaceLanguage, tString } from '@/intl/server';
import { languages } from '@/intl/translations';
import { Icon } from '@gitbook/icons';
import type { DocumentContextProps } from '../DocumentView';
import { HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
/**
* Hover card displayed for a link not found.
*/
export function NotFoundRefHoverCard(
props: DocumentContextProps & {
children: React.ReactNode;
}
) {
const {
context: { contentContext },
children,
} = props;
const language = contentContext ? getSpaceLanguage(contentContext) : languages.en;
return (
<HoverCardRoot>
<HoverCardTrigger>{children}</HoverCardTrigger>
<HoverCard className="flex flex-col gap-1 p-4">
<div className="flex items-center gap-2">
<Icon icon="ban" className="size-4 text-tint-subtle" />
<h5 className="font-semibold">{tString(language, 'notfound_title')}</h5>
</div>
<p className="text-sm text-tint">{tString(language, 'notfound_link')}</p>
</HoverCard>
</HoverCardRoot>
);
}
@@ -41,6 +41,7 @@ export function getOpenAPIContext(args: {
plus: <Icon icon="plus" />,
copy: <Icon icon="copy" />,
check: <Icon icon="check" />,
lock: <Icon icon="lock" />,
},
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
renderDocument: (documentProps) => (
@@ -182,7 +182,7 @@
/* Schema Presentation */
.openapi-schema-presentation {
@apply flex flex-col gap-1 font-normal;
@apply flex flex-col gap-1 font-normal scroll-mt-[calc(var(--toc-top-offset)+0.5rem)];
}
.openapi-schema-properties:last-child {
@@ -249,7 +249,7 @@
}
.openapi-schema-circular {
@apply text-xs text-tint;
@apply text-sm text-tint;
}
.openapi-schema-circular a {
@@ -257,7 +257,7 @@
}
.openapi-schema-circular-glyph {
@apply text-base;
@apply text-base mr-1;
}
/* Schema Enum */
@@ -334,7 +334,7 @@
}
.openapi-securities-scopes ul {
@apply !my-0;
@apply !my-0 ml-4 pl-0;
}
.openapi-securities-url {
@@ -398,15 +398,13 @@
@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 {
@apply truncate;
@apply [&>*:not(:first-child)]:hidden *:truncate *:!p-0 *:!m-0;
}
.openapi-disclosure-group-trigger[aria-expanded="false"] .openapi-response-description.openapi-markdown {
@apply truncate;
@apply [&>*:not(:first-child)]:hidden *:truncate *:!p-0 *:!m-0;
}
.openapi-response-tab-content {
@apply basis-[60%]
}
.openapi-disclosure-group-trigger[aria-expanded="false"] .openapi-response-tab-content {
@apply basis-[60%];
}
.openapi-response-body {
@@ -528,7 +526,7 @@
.openapi-panel,
.openapi-codesample,
.openapi-response-examples {
@apply border shrink min-h-40 overflow-hidden rounded-md 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 {
@@ -851,7 +849,7 @@ body:has(.openapi-select-popover) {
.openapi-schema-alternatives .openapi-disclosure,
.openapi-schemas-disclosure .openapi-schema.openapi-disclosure
) {
@apply rounded-xl straight-corners:rounded-none;
@apply rounded-md circular-corners:rounded-xl straight-corners:rounded-none;
}
.openapi-disclosure .openapi-schemas-disclosure .openapi-schema.openapi-disclosure {
@@ -866,10 +864,10 @@ body:has(.openapi-select-popover) {
@apply ring-1 shadow-sm;
}
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:first-child) {
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure,.openapi-required-scopes):not(:first-child) {
@apply mt-2;
}
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:last-child) {
.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure,.openapi-required-scopes):not(:last-child) {
@apply mb-2;
}
@@ -1016,3 +1014,26 @@ body:has(.openapi-select-popover) {
.openapi-path-copy-button-icon svg {
@apply text-tint size-4;
}
.openapi-required-scopes {
@apply border text-base rounded-md straight-corners:rounded-none circular-corners:rounded-md font-medium mx-0;
}
.openapi-required-scopes .openapi-required-scopes-header {
@apply flex items-center gap-3;
}
.openapi-required-scopes .openapi-required-scopes-header svg {
@apply size-3.5 text-tint-subtle rotate-none;
}
.openapi-required-scopes .openapi-disclosure-group-panel {
@apply px-3 pb-3;
}
.openapi-required-scopes .openapi-securities-scopes {
@apply ml-6 font-normal *:!text-[0.8125rem];
}
.openapi-required-scopes .openapi-required-scopes-description {
@apply text-xs !text-tint font-normal mb-2;
}
@@ -60,7 +60,7 @@ export async function RecordCard(
'overflow-hidden',
'[&_.heading>div:first-child]:hidden',
'[&_.heading>div]:text-[.8em]',
'md:[&_.heading>div]:text-[1em]',
'@xl:[&_.heading>div]:text-[1em]',
'[&_.blocks:first-child_.heading]:pt-0', // Remove padding-top on first heading in card
// On mobile, check if we can display the cover responsively or not:
@@ -69,10 +69,10 @@ export async function RecordCard(
lightCoverIsSquareOrPortrait || darkCoverIsSquareOrPortrait
? [
lightCoverIsSquareOrPortrait
? 'grid-cols-[40%__1fr] min-[432px]:grid-cols-none min-[432px]:grid-rows-[auto_1fr]'
? '@sm:grid-cols-none grid-cols-[40%__1fr] @sm:grid-rows-[auto_1fr]'
: '',
darkCoverIsSquareOrPortrait
? 'dark:grid-cols-[40%__1fr] dark:min-[432px]:grid-cols-none dark:min-[432px]:grid-rows-[auto_1fr]'
? 'dark:@sm:grid-cols-none dark:grid-cols-[40%__1fr] dark:@sm:grid-rows-[auto_1fr]'
: '',
].filter(Boolean)
: 'grid-rows-[auto_1fr]'
@@ -112,11 +112,9 @@ export async function RecordCard(
'bg-tint-subtle',
lightCoverIsSquareOrPortrait || darkCoverIsSquareOrPortrait
? [
lightCoverIsSquareOrPortrait
? 'min-[432px]:aspect-video min-[432px]:h-auto'
: '',
lightCoverIsSquareOrPortrait ? '@sm:aspect-video @sm:h-auto' : '',
darkCoverIsSquareOrPortrait
? 'dark:min-[432px]:aspect-video dark:min-[432px]:h-auto'
? 'dark:@sm:aspect-video dark:@sm:h-auto'
: '',
].filter(Boolean)
: ['h-auto', 'aspect-video'],
@@ -15,8 +15,8 @@ export function ViewCards(props: TableViewProps<DocumentTableViewCards>) {
'inline-grid',
'gap-4',
'grid-cols-1',
'min-[432px]:grid-cols-2',
view.cardSize === 'large' ? 'md:grid-cols-2' : 'md:grid-cols-3',
'@sm:grid-cols-2',
view.cardSize === 'large' ? '@xl:grid-cols-2' : '@xl:grid-cols-3',
block.data.fullWidth ? 'large:flex-column' : null
)}
>
@@ -9,7 +9,7 @@ import React, {
type ComponentPropsWithRef,
} from 'react';
import { useHash, useListOverflow } from '@/components/hooks';
import { NavigationStatusContext, useListOverflow } from '@/components/hooks';
import { DropdownMenu, DropdownMenuItem } from '@/components/primitives';
import { useLanguage } from '@/intl/client';
import { tString } from '@/intl/translate';
@@ -77,7 +77,7 @@ export function DynamicTabs(props: {
const { id, tabs, className } = props;
const router = useRouter();
const hash = useHash();
const { onNavigationClick, hash } = React.useContext(NavigationStatusContext);
const [initialized, setInitialized] = useState(false);
const [tabsState, setTabsState] = useTabsState();
const activeState = useMemo(() => {
@@ -112,6 +112,7 @@ export function DynamicTabs(props: {
touchedRef.current = true;
const href = `#${tab.id}`;
if (window.location.hash !== href) {
onNavigationClick(href);
router.replace(href, { scroll: false });
}
}
@@ -167,7 +168,7 @@ export function DynamicTabs(props: {
// Scroll to active element in the tab.
React.useLayoutEffect(() => {
// If there is no hash or active tab, nothing to scroll.
if (!hash || !active) {
if (!hash || hash !== '' || !active) {
return;
}
@@ -0,0 +1,79 @@
import { formatDateFull, formatDateShort, formatNumericDate } from '@/components/utils/dates';
import { tcls } from '@/lib/tailwind';
import type { DocumentBlockUpdate, DocumentBlockUpdates } from '@gitbook/api';
import { assert } from 'ts-essentials';
import type { BlockProps } from './Block';
import { Blocks } from './Blocks';
export function Update(props: BlockProps<DocumentBlockUpdate>) {
const { block, style, ancestorBlocks, ...contextProps } = props;
// Get the parent Updates block to retrieve properties from
const parentUpdates = ancestorBlocks.find(
(ancestor): ancestor is DocumentBlockUpdates => ancestor.type === 'updates'
);
if (!parentUpdates) {
assert(parentUpdates, 'Parent updates block should exist');
return null;
}
// Get the date for this Update block and parse it to a Date object
const date = block.data.date;
const parsedDate = parseISODate(date);
if (!parsedDate) {
assert(date, 'Date should exist on Update block');
return null;
}
// Then get the format from the parent Updates block and use that format
const dateFormat = parentUpdates.data?.format ?? 'full';
const displayDate = {
numeric: formatNumericDate(parsedDate),
full: formatDateFull(parsedDate),
short: formatDateShort(parsedDate),
}[dateFormat];
return (
<div
className={tcls(
'relative flex flex-col gap-2 md:flex-row md:gap-4 lg:gap-12 xl:gap-20',
style
)}
>
<div
className={tcls(
// Date is only sticky on larger screens when we use flex-row layout
'h-fit w-40 min-w-40 shrink-0 md:sticky md:top-[calc(var(--toc-top-offset)+8px)]!'
)}
>
<time
// Adding a dateTime attribute for accessibility (and SEO)
dateTime={date}
className="inline-flex items-center font-medium text-neutral-10 text-sm tracking-wide"
>
{displayDate}
</time>
</div>
<Blocks
{...contextProps}
nodes={block.nodes}
ancestorBlocks={[...ancestorBlocks, block]}
// 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>
);
}
/**
* Parse an ISO date string into a Date object.
*/
function parseISODate(value: string): Date | undefined {
if (!value) {
return undefined;
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? undefined : date;
}
@@ -0,0 +1,16 @@
import type { DocumentBlockUpdates } from '@gitbook/api';
import type { BlockProps } from './Block';
import { Blocks } from './Blocks';
export function Updates(props: BlockProps<DocumentBlockUpdates>) {
const { block, style, ancestorBlocks, ...contextProps } = props;
return (
<Blocks
{...contextProps}
nodes={block.nodes}
ancestorBlocks={[...ancestorBlocks, block]}
style={[style, 'flex flex-col gap-20']}
/>
);
}
@@ -19,19 +19,19 @@ export function getBlockTextStyle(block: DocumentBlock): {
};
case 'heading-1':
return {
textSize: 'text-3xl font-semibold',
textSize: 'text-xl @xs:text-2xl @lg:text-3xl font-semibold',
lineHeight: 'leading-tight',
marginTop: 'column-first-of-type:pt-0 pt-[1em]',
};
case 'heading-2':
return {
textSize: 'text-2xl font-semibold',
textSize: 'text-lg @xs:text-xl @lg:text-2xl font-semibold',
lineHeight: 'leading-snug',
marginTop: 'column-first-of-type:pt-0 pt-[0.75em]',
};
case 'heading-3':
return {
textSize: 'text-xl font-semibold',
textSize: 'text-base @xs:text-lg @lg:text-xl font-semibold',
lineHeight: 'leading-snug',
marginTop: 'column-first-of-type:pt-0 pt-[0.5em]',
};
@@ -3,17 +3,17 @@ import type { DocumentMarkColor } from '@gitbook/api';
export const textColorToStyle: { [color in DocumentMarkColor['data']['text']]: ClassValue } = {
default: [],
blue: ['text-blue-500'],
red: ['text-red-500'],
green: ['text-green-500'],
yellow: ['text-yellow-600'],
purple: ['text-purple-500'],
orange: ['text-orange-500'],
$primary: ['text-primary'],
$info: ['text-info'],
$success: ['text-success'],
$warning: ['text-warning'],
$danger: ['text-danger'],
blue: ['text-blue-500 contrast-more:text-blue-800'],
red: ['text-red-500 contrast-more:text-red-800'],
green: ['text-green-500 contrast-more:text-green-800'],
yellow: ['text-yellow-600 contrast-more:text-yellow-800'],
purple: ['text-purple-500 contrast-more:text-purple-800'],
orange: ['text-orange-500 contrast-more:text-orange-800'],
$primary: ['text-primary-subtle contrast-more:text-primary'],
$info: ['text-info-subtle contrast-more:text-info'],
$success: ['text-success-subtle contrast-more:text-success'],
$warning: ['text-warning-subtle contrast-more:text-warning'],
$danger: ['text-danger-subtle contrast-more:text-danger'],
};
export const backgroundColorToStyle: {
@@ -1,12 +1,14 @@
'use client';
import { useAIChatController, useAIChatState } from '@/components/AI';
import { useAI, useAIChatController, useAIChatState } from '@/components/AI';
import {
AIChatBody,
AIChatControlButton,
AIChatDynamicIcon,
AIChatSubtitle,
getAIChatName,
} from '@/components/AIChat';
import { useLanguage } from '@/intl/client';
import * as api from '@gitbook/api';
import React from 'react';
import { useTrackEvent } from '../Insights';
@@ -23,13 +25,12 @@ import { EmbeddableIframeButtons, useEmbeddableConfiguration } from './Embeddabl
/**
* Embeddable AI chat window in an iframe.
*/
export function EmbeddableAIChat(props: {
trademark: boolean;
}) {
const { trademark } = props;
export function EmbeddableAIChat() {
const chat = useAIChatState();
const { config } = useAI();
const chatController = useAIChatController();
const configuration = useEmbeddableConfiguration();
const language = useLanguage();
// Track the view of the AI chat
const trackEvent = useTrackEvent();
@@ -48,9 +49,11 @@ export function EmbeddableAIChat(props: {
return (
<EmbeddableFrame>
<EmbeddableFrameHeader>
<AIChatDynamicIcon trademark={trademark} />
<AIChatDynamicIcon trademark={config.trademark} />
<EmbeddableFrameHeaderMain>
<EmbeddableFrameTitle>GitBook Assistant</EmbeddableFrameTitle>
<EmbeddableFrameTitle>
{getAIChatName(language, config.trademark)}
</EmbeddableFrameTitle>
<AIChatSubtitle chat={chat} />
</EmbeddableFrameHeaderMain>
<EmbeddableFrameButtons>
@@ -60,7 +63,6 @@ export function EmbeddableAIChat(props: {
</EmbeddableFrameHeader>
<EmbeddableFrameBody>
<AIChatBody
trademark={trademark}
chatController={chatController}
chat={chat}
suggestions={configuration.suggestions}
@@ -1,13 +1,8 @@
import type { GitBookSiteContext } from '@/lib/context';
import { EmbeddableAIChat } from './EmbeddableAIChat';
/**
* Reusable page component for the embed assistant page.
*/
export async function EmbeddableAssistantPage(props: {
context: GitBookSiteContext;
}) {
const { context } = props;
return <EmbeddableAIChat trademark={context.customization.trademark.enabled} />;
export async function EmbeddableAssistantPage() {
return <EmbeddableAIChat />;
}
@@ -32,7 +32,7 @@ export async function EmbeddableDocsPage(props: EmbeddableDocsPageProps) {
});
return (
<EmbeddableFrame>
<EmbeddableFrame className="site-background">
<EmbeddableFrameHeader>
<EmbeddableFrameHeaderMain>
<Button
@@ -17,7 +17,7 @@ export const EmbeddableFrame = React.forwardRef<HTMLDivElement, EmbeddableFrameP
<div
{...divProps}
className={tcls(
'flex h-full grow animate-fade-in-slow flex-col overflow-hidden bg-tint-base text-sm text-tint',
'flex h-full grow flex-col overflow-hidden bg-radial-[circle_at_bottom] from-primary to-50% to-transparent text-sm text-tint',
divProps.className
)}
ref={ref}
@@ -34,7 +34,7 @@ export function EmbeddableFrameHeader(props: {
const { children } = props;
return (
<div className="flex select-none items-center gap-2 border-tint-subtle border-b bg-tint-subtle px-4 py-2 text-tint-strong">
<div className="relative z-10 flex animate-fade-in-slow select-none items-center gap-2 px-4 py-2 text-tint-strong">
{children}
</div>
);
@@ -73,7 +73,7 @@ export function EmbeddableFrameSubtitle(props: {
return (
<div
className={tcls(
'text-tint text-xs leading-none transition-all duration-500',
'origin-left text-tint text-xs leading-none transition-all duration-500',
className
)}
>
@@ -87,5 +87,5 @@ export function EmbeddableFrameButtons(props: {
}) {
const { children } = props;
return <div className="ml-auto flex gap-2">{children}</div>;
return <div className="-mr-2 ml-auto flex gap-2">{children}</div>;
}
+109 -98
View File
@@ -29,112 +29,123 @@ export function Footer(props: { context: GitBookSiteContext }) {
className={tcls(
'border-tint-subtle border-t',
// If the footer only contains a mode toggle, we only show it on smaller screens
mobileOnly ? 'xl:hidden' : null
mobileOnly ? '@7xl:hidden' : null
)}
>
<div className={tcls(CONTAINER_STYLE, 'px-4', 'py-8', 'lg:py-12', 'mx-auto')}>
<div className="motion-safe:transition-[padding] motion-safe:duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<div
className={tcls(
'mx-auto grid max-w-3xl site-width-wide:max-w-screen-2xl justify-between gap-12 lg:max-w-none!',
'grid-cols-[auto_auto]',
'lg:grid-cols-[18rem_minmax(auto,48rem)_auto]',
'xl:grid-cols-[18rem_minmax(auto,48rem)_14rem]',
'lg:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_auto]',
'xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_14rem]',
'lg:page-no-toc:grid-cols-[minmax(auto,48rem)_auto]',
'xl:page-no-toc:grid-cols-[14rem_minmax(auto,48rem)_14rem]',
'lg:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[minmax(auto,90rem)_auto]',
'xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[14rem_minmax(auto,90rem)_14rem]'
CONTAINER_STYLE,
'px-4',
'py-8',
'@4xl:py-12',
'mx-auto',
'@container/footer'
)}
>
{
// Footer Logo
customization.footer.logo ? (
<div className="col-start-1 row-start-1">
<Image
alt="Logo"
resize={context.imageResizer}
sources={{
light: {
src: customization.footer.logo.light,
},
dark: customization.footer.logo.dark
? {
src: customization.footer.logo.dark,
}
: null,
}}
loading="lazy"
style={[
'w-auto',
'max-w-40',
'lg:max-w-64',
'max-h-10',
'lg:max-h-12',
'object-contain',
'object-left',
'rounded-sm',
'straight-corners:rounded-xs',
]}
sizes={[
{
width: 320,
},
]}
/>
</div>
) : null
}
{
// Theme Toggle
customization.themes.toggeable ? (
<div className="-col-start-2 row-start-1 flex items-start justify-end xl:hidden">
<React.Suspense fallback={null}>
<ThemeToggler />
</React.Suspense>
</div>
) : null
}
{
// Navigation groups (split into equal columns)
customization.footer.groups?.length > 0 ? (
<div
className={tcls(
'col-span-2 lg:page-has-toc:col-span-1 lg:page-has-toc:col-start-2 xl:page-no-toc:col-span-1 xl:page-no-toc:col-start-2'
)}
>
<div className="mx-auto flex max-w-3xl site-width-wide:max-w-screen-2xl flex-col gap-10 sm:flex-row sm:gap-6">
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
(column, columnIndex) => (
<div
key={columnIndex}
className="flex flex-1 grow flex-col gap-10"
>
{column.map((group, groupIndex) => (
<FooterLinksGroup
key={groupIndex}
group={group}
context={context}
/>
))}
</div>
)
)}
<div
className={tcls(
'mx-auto flex @xs:grid @4xl:max-w-none! max-w-3xl site-width-wide:max-w-screen-2xl flex-col justify-between gap-12',
'grid-cols-[auto_auto]',
'@4xl:grid-cols-[18rem_minmax(auto,48rem)_auto]',
'@7xl:grid-cols-[18rem_minmax(auto,48rem)_14rem]',
'@4xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_auto]',
'@7xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_14rem]',
'@4xl:page-no-toc:grid-cols-[minmax(auto,48rem)_auto]',
'@7xl:page-no-toc:grid-cols-[14rem_minmax(auto,48rem)_14rem]',
'@4xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[minmax(auto,90rem)_auto]',
'@7xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[14rem_minmax(auto,90rem)_14rem]'
)}
>
{
// Footer Logo
customization.footer.logo ? (
<div className="col-start-1 row-start-1">
<Image
alt="Logo"
resize={context.imageResizer}
sources={{
light: {
src: customization.footer.logo.light,
},
dark: customization.footer.logo.dark
? {
src: customization.footer.logo.dark,
}
: null,
}}
loading="lazy"
style={[
'w-auto',
'max-w-40',
'@4xl:max-w-64',
'max-h-10',
'@4xl:max-h-12',
'object-contain',
'object-left',
'rounded-sm',
'straight-corners:rounded-xs',
]}
sizes={[
{
width: 320,
},
]}
/>
</div>
</div>
) : null
}
) : null
}
{
// Legal
customization.footer.copyright ? (
<div className="order-last col-span-full flex w-full grow flex-col items-center gap-2 text-center text-tint text-xs">
<p>{customization.footer.copyright}</p>
</div>
) : null
}
{
// Theme Toggle
customization.themes.toggeable ? (
<div className="-col-start-2 row-start-1 flex items-start @xs:justify-end xl:hidden">
<React.Suspense fallback={null}>
<ThemeToggler />
</React.Suspense>
</div>
) : null
}
{
// Navigation groups (split into equal columns)
customization.footer.groups?.length > 0 ? (
<div
className={tcls(
'@4xl:page-has-toc:col-span-1 @7xl:page-no-toc:col-span-1 col-span-2 @4xl:page-has-toc:col-start-2 @7xl:page-no-toc:col-start-2'
)}
>
<div className="mx-auto flex max-w-3xl site-width-wide:max-w-screen-2xl @xl:flex-row flex-col @xl:gap-6 gap-10">
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
(column, columnIndex) => (
<div
key={columnIndex}
className="flex flex-1 grow flex-col gap-10"
>
{column.map((group, groupIndex) => (
<FooterLinksGroup
key={groupIndex}
group={group}
context={context}
/>
))}
</div>
)
)}
</div>
</div>
) : null
}
{
// Legal
customization.footer.copyright ? (
<div className="order-last col-span-full flex w-full grow flex-col items-center gap-2 text-center text-tint text-xs">
<p>{customization.footer.copyright}</p>
</div>
) : null
}
</div>
</div>
</div>
</footer>
@@ -25,12 +25,12 @@ export function Header(props: {
};
}) {
const { context, withTopHeader, variants } = props;
const { siteSpace, siteSpaces, sections, customization } = context;
const { siteSpace, visibleSiteSpaces, visibleSections, customization } = context;
const withSections = Boolean(
sections &&
(sections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
sections.list.some((s) => s.object === 'site-section-group'))
visibleSections &&
(visibleSections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
visibleSections.list.some((s) => s.object === 'site-section-group'))
);
return (
@@ -67,7 +67,7 @@ export function Header(props: {
'theme-bold:shadow-tint-12/2'
)}
>
<div className="transition-[padding] duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<div className="transition-all duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<div
className={tcls(
'gap-4',
@@ -79,13 +79,19 @@ export function Header(props: {
'py-3',
'min-h-16',
'sm:h-16',
CONTAINER_STYLE
CONTAINER_STYLE,
'transition-[max-width] duration-300',
'@container/header'
)}
>
<div
className={tcls(
'flex max-w-full lg:basis-72',
'min-w-0 shrink items-center justify-start gap-2 lg:gap-4'
'flex max-w-full',
'min-w-0 shrink items-center justify-start gap-2 lg:gap-4',
'search' in customization.styling &&
customization.styling.search === 'prominent'
? 'lg:@2xl:basis-72'
: null
)}
>
<HeaderMobileMenu
@@ -96,7 +102,7 @@ export function Header(props: {
'hover:bg-tint-hover',
'hover:theme-bold:bg-header-link/3',
variants.generic.length > 1
? 'xl:hidden'
? 'lg:hidden'
: 'page-no-toc:hidden lg:hidden'
)}
/>
@@ -108,22 +114,23 @@ export function Header(props: {
'flex',
'grow-0',
'shrink-0',
'md:basis-56',
'@2xl:basis-56',
'justify-self-end',
'items-center',
'gap-2',
'transition-[margin] duration-300',
'search' in customization.styling &&
customization.styling.search === 'prominent'
? [
'md:grow-[0.8]',
'lg:basis-40',
'md:max-w-[40%]',
'lg:max-w-lg',
'lg:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem)
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'md:mr-auto',
'@2xl:grow-[0.8]',
'@4xl:basis-40',
'@2xl:max-w-[40%]',
'@4xl:max-w-lg',
'lg:@2xl:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem)
'not-chat-open:xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'@2xl:mr-auto',
'order-last',
'md:order-[unset]',
'@2xl:order-[unset]',
]
: ['order-last']
)}
@@ -132,23 +139,22 @@ export function Header(props: {
style={customization.styling.search}
withVariants={variants.generic.length > 1}
withSiteVariants={
sections?.list.some(
visibleSections?.list.some(
(s) =>
s.object === 'site-section' && s.siteSpaces.length > 1
) ?? false
}
withSections={sections ? sections.list.length > 1 : false}
withSections={
visibleSections ? visibleSections.list.length > 1 : false
}
section={
sections
? // Client-encode to avoid a serialisation issue that was causing the language selector to disappear
encodeClientSiteSections(context, sections).current
visibleSections
? // Client-encode to avoid a serialization issue that was causing the language selector to disappear
encodeClientSiteSections(context, visibleSections).current
: undefined
}
spaceTitle={siteSpace.title}
siteSpaceId={siteSpace.id}
siteSpaceIds={siteSpaces
.filter((s) => s.space.language === siteSpace.space.language)
.map((s) => s.id)}
siteSpace={siteSpace}
siteSpaces={visibleSiteSpaces}
viewport={!withTopHeader ? 'mobile' : undefined}
/>
</div>
@@ -192,9 +198,9 @@ export function Header(props: {
</div>
</div>
{sections && withSections ? (
{visibleSections && withSections ? (
<div className="transition-[padding] duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<SiteSectionTabs sections={encodeClientSiteSections(context, sections)}>
<SiteSectionTabs sections={encodeClientSiteSections(context, visibleSections)}>
{variants.translations.length > 1 ? (
<TranslationsDropdown
context={context}
@@ -14,7 +14,7 @@ export async function HeaderLinks({ children, style }: HeaderLinksProps) {
<div
className={tcls(
styles.containerHeaderlinks,
'lg:[&>.button+.button]:-ml-2 z-20 ml-auto flex min-w-9 shrink grow items-center justify-end gap-x-4 lg:gap-x-6 xl:grow-0',
'@4xl:[&>.button+.button]:-ml-2 z-20 ml-auto flex min-w-9 shrink grow @7xl:grow-0 items-center justify-end @4xl:gap-x-6 gap-x-4',
style
)}
>
@@ -3,6 +3,7 @@ import type { GitBookSiteContext } from '@/lib/context';
import { Image } from '@/components/utils';
import { tcls } from '@/lib/tailwind';
import { resolveContentRef } from '@/lib/references';
import { Link } from '../primitives';
import { CurrentContentIcon } from './CurrentContentIcon';
@@ -18,9 +19,13 @@ export async function HeaderLogo(props: HeaderLogoProps) {
const { context } = props;
const { customization, linker } = context;
const primaryLink = customization.header.primaryLink
? await resolveContentRef(customization.header.primaryLink, context)
: null;
return (
<Link
href={linker.toPathInSite('')}
href={primaryLink?.href ?? linker.toPathInSite('')}
className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')}
>
{customization.header.logo ? (
@@ -45,11 +45,12 @@ export function SpacesDropdown(props: {
}
>
<SpacesDropdownMenuItems
slimSpaces={siteSpaces.map((space) => ({
id: space.id,
title: space.title,
url: getSiteSpaceURL(context, space),
isActive: space.id === siteSpace.id,
slimSpaces={siteSpaces.map((siteSp) => ({
id: siteSp.id,
title: siteSp.title,
url: getSiteSpaceURL(context, siteSp),
isActive: siteSp.id === siteSpace.id,
spaceId: siteSp.space.id,
}))}
curPath={siteSpace.path}
/>
@@ -1,26 +1,39 @@
'use client';
import type { Space } from '@gitbook/api';
import { joinPath } from '@/lib/paths';
import { useCurrentPagePath } from '../hooks';
import { useCurrentPageMetadata, useCurrentPagePath } from '../hooks';
import { DropdownMenuItem } from '../primitives/DropdownMenu';
interface VariantSpace {
id: Space['id'];
title: Space['title'];
id: string;
title: string;
url: string;
isActive: boolean;
spaceId: string;
}
// When switching to a different variant space, we reconstruct the URL by swapping the space path.
function useVariantSpaceHref(variantSpaceUrl: string, currentSpacePath: string, active = false) {
/**
* Return the href for a variant space, taking into account the current page path and metadata.
*/
function useVariantSpaceHref(variantSpace: VariantSpace, currentSpacePath: string, active = false) {
const currentPathname = useCurrentPagePath();
const { metaLinks } = useCurrentPageMetadata();
// We first check if there is an alternate link for the variant space in the current page metadata.
const pageHasAlternateForVariant = metaLinks?.alternates.find(
(alt) => alt.space?.id === variantSpace.spaceId
);
if (pageHasAlternateForVariant) {
return pageHasAlternateForVariant.href;
}
// If there is no alternate link, we reconstruct the URL by swapping the space path.
// We need to ensure that the variant space URL is not the same as the current space path.
// If it is, we return only the variant space URL to redirect to the root of the variant space.
// This is necessary in case the currentPathname is the same as the variantSpaceUrl,
// otherwise we would redirect to the same space if the variant space that we are switching to is the default one.
const variantSpaceUrl = variantSpace.url;
if (!active && currentPathname.startsWith(`${currentSpacePath}/`)) {
return variantSpaceUrl;
}
@@ -44,7 +57,7 @@ export function SpacesDropdownMenuItem(props: {
currentSpacePath: string;
}) {
const { variantSpace, active, currentSpacePath } = props;
const variantHref = useVariantSpaceHref(variantSpace.url, currentSpacePath, active);
const variantHref = useVariantSpaceHref(variantSpace, currentSpacePath, active);
return (
<DropdownMenuItem key={variantSpace.id} href={variantHref} active={active}>
@@ -1,4 +1,4 @@
@media (max-width: 1279px) {
@container header (width < 1280px) {
.containerHeaderlinks {
container-type: inline-size;
container-name: headerlinks;

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