Compare commits

..

88 Commits

Author SHA1 Message Date
Zeno Kapitein 4228625852 Test 2026-02-10 18:23:19 +01:00
Zeno Kapitein e9c8ec79d2 Add inline set-claim action support 2026-02-10 18:13:07 +01:00
Greg Bergé 6ede6ee7c6 Add Korean translations (#3986) 2026-02-10 15:41:28 +01:00
Nolann B. 7a11861d36 Add support for custom cookie banner registration (#3925) 2026-02-10 11:28:24 +01:00
Greg Bergé 10754fbaf4 Fix GitBook icon not displayed in trademark (#3984) 2026-02-10 10:58:30 +01:00
Zeno Kapitein 8f32f4fb1e Better handle huge section groups (#3978) 2026-02-09 15:13:01 +01:00
Nolann B. 717ad0b300 Fix OpenAPI try it button justify (#3975) 2026-02-05 11:16:58 +01:00
Nolann B. a3eed52f24 Refine OpenAPI schema alternatives/allOf handling (#3973) 2026-02-05 11:00:59 +01:00
Greg Bergé 27a34b7ee2 Support defaultExpanded in Expandable block (#3974) 2026-02-04 11:49:14 +01:00
Zeno Kapitein bf3ad28c7f Add margin to TOC items after page group (#3972) 2026-02-03 16:10:32 +01:00
Zeno Kapitein 70c2e90e4b Handle empty page section search results (#3971) 2026-02-03 16:10:13 +01:00
Greg Bergé d0a378cdb6 Disallow Markdown pages crawling (#3970) 2026-02-03 09:40:17 +01:00
Viktor Renkema 81f8ddcf27 Fix flaky Updates blocks in visual tests (#3969) 2026-02-02 17:32:46 +01:00
Zeno Kapitein 9d6457f814 OpenAPI styling tweaks (#3968) 2026-01-30 19:02:30 +01:00
Zeno Kapitein 07c02ebcd2 Header & TOC styling tweaks (#3967) 2026-01-30 15:23:16 +01:00
Samy Pessé 4d3172d13f Rollout to 100% the dereferencing of reusable content at the API level (#3966) 2026-01-30 09:01:12 +01:00
Zeno Kapitein 0aa34cc9a8 Fix Embed options in React (#3964) 2026-01-29 17:32:56 +01:00
Greg Bergé 551d30cfa4 Fix loading of images in PDF export (#3958)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2026-01-29 15:55:42 +01:00
Samy Pessé 844a0a8ea7 Fix CI for visual testing 2026-01-29 15:26:41 +01:00
Samy Pessé eb8ba66a66 Ensure deployments to preview start when approval job is skipped 2026-01-29 14:06:48 +01:00
Hirotaka Miyagi d738e118a0 Fix: Prevent form submission during IME composition (#3942)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Addison <42930383+addisonschultz@users.noreply.github.com>
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2026-01-29 12:46:52 +00:00
Samy Pessé c16d08f5d5 Add approval step for external contributors' PRs (#3963) 2026-01-29 11:33:13 +01:00
Samy Pessé 61ec9b25b9 Enable approved PR deployments for preview workflows (#3954) 2026-01-29 11:16:00 +01:00
Samy Pessé b57561d395 Fix rendering of images in reusable content from an external space (#3960) 2026-01-29 10:42:25 +01:00
Zeno Kapitein e00d1c5bbd Support code themes customization option, refactor built-in code themes (#3949) 2026-01-28 13:48:56 +01:00
conico974 9063b56c89 Fix for the assistant (#3948) 2026-01-28 11:16:50 +01:00
Greg Bergé 020d3668d7 Make the button icon validation safer (#3955) 2026-01-28 10:44:52 +01:00
Greg Bergé 4d30cbf058 Fix social links not appearing on desktop (#3950) 2026-01-28 10:44:40 +01:00
Samy Pessé d3f6c38f05 Update scalar dependencies (#3956) 2026-01-28 10:29:56 +01:00
Samy Pessé 6f1db32682 Simplify local development and contributions (#3952) 2026-01-28 09:32:56 +01:00
Samy Pessé 4ad6b2c067 Increase rollout of reusable content deref in the API to 80% (#3951) 2026-01-28 09:22:19 +01:00
Viktor Renkema 07449443a0 Fix rendering of Expandables within Update blocks (#3947) 2026-01-27 20:26:33 +01:00
Greg Bergé b09cc64fec Fix GIF rendering (#3946) 2026-01-27 17:34:02 +01:00
Greg Bergé 1eed2004f6 Avoid keyboard-shortcut flicker by adding OS class and using CSS (#3944) 2026-01-27 17:02:49 +01:00
Greg Bergé 91539b8187 Fix navigation loader (#3945) 2026-01-27 17:01:06 +01:00
Samy Pessé 16cfde87ee Increase rollout of reusable content deref in the API to 50% (#3943) 2026-01-27 12:22:09 +01:00
conico974 58b4a978ff Update Next.js dependency to version 15.4.11 (#3940) 2026-01-27 09:26:11 +01:00
Viktor Renkema e34be0e89b Fix logo sizing bug during navigation (#3928) 2026-01-27 08:46:37 +01:00
Samy Pessé e60c754343 Increase rollout of reusable content deref in the API to 30% (#3939) 2026-01-26 16:22:13 +01:00
Nolann B. 48ca21e471 Add OpenAPI 3.1 nullable array support (#3938) 2026-01-26 14:05:14 +01:00
Nolann B. 3ba9e46f2b Improve OpenAPI server URL validation (#3937) 2026-01-26 13:45:03 +01:00
Greg Bergé 58f0cc8287 Fix search field clear button (#3936) 2026-01-24 16:26:03 +01:00
Nolann B. 29e2b22b63 Fix ordered list item index calculation (#3935) 2026-01-23 16:01:33 +01:00
Greg Bergé 3c7cb09c1b Respect DPR to ensure a good image quality (#3931) 2026-01-23 11:45:00 +01:00
Greg Bergé 0f1cd6745d Refactor search (#3932) 2026-01-23 11:13:20 +01:00
Samy Pessé e90f57b138 Deference reusable content at the API level (#3847) 2026-01-23 10:34:44 +01:00
conico974 7bd5939247 Bust data cache (#3927) 2026-01-22 13:59:35 +01:00
Zeno Kapitein 89de966e7a Support social accounts customization option (#3916) 2026-01-22 13:57:40 +01:00
Greg Bergé 4e2d86318e Fix OpenAPI example not showing (#3930)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-22 12:11:55 +01:00
Nolann B. f647af2003 Prevent duplicate Content-Type headers in OpenAPI code samples (#3924) 2026-01-21 14:51:46 +01:00
Greg Bergé 96e24a1f84 Fix JWT token usage in GitBook embed SDK (#3922) 2026-01-21 12:05:14 +01:00
Viktor Renkema 2a0f37f4b3 Always display page actions even if there is no toc/title/description (#3921) 2026-01-21 10:54:42 +01:00
Zeno Kapitein 17d935dd4c Upgrade API to 0.158.0 (#3918) 2026-01-20 17:55:47 +01:00
Greg Bergé df07fbe242 Remove development from package.exports before publishing (#3917) 2026-01-20 17:22:24 +01:00
Zeno Kapitein 1e53376151 Add SideSheet component, refactor TOC and AIChat to use it (#3835) 2026-01-19 21:19:54 +01:00
Samy Pessé f294818775 Serve markdown version of the page depending on the accept type (#3914) 2026-01-19 15:11:02 -05:00
Greg Bergé eed386a133 Fix custom tools usage with GitBook Embed SDK (#3913) 2026-01-19 13:14:23 +01:00
Zeno Kapitein e172b25ba8 Grow InlineActionButton to available space on line (#3912) 2026-01-19 12:55:03 +01:00
Zeno Kapitein f7f6f9d06d Set <html lang> based on translated language (#3909) 2026-01-16 12:00:39 +01:00
Claire Chabas 9e01676fe9 Remove grid from framed images (#3907) 2026-01-15 14:06:26 +01:00
Zeno Kapitein f6cf62a686 Standardise toggle chevrons across the app (#3904) 2026-01-14 13:46:47 +01:00
Zeno Kapitein a5ef44ab2a Better conditional display of page actions (#3900)
Co-authored-by: Claire Chabas <claire.chabas@gmail.com>
2026-01-14 13:21:47 +01:00
Zeno Kapitein b53cb1d2b3 Align styling of PageLinkItem and PageDocumentItem (#3903) 2026-01-14 13:07:45 +01:00
Samy Pessé 7eeff73080 Version Packages (#3902)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-13 16:13:41 +00:00
Greg Bergé bf63045319 Fix OpenAPI JSX compilation (#3901) 2026-01-13 16:56:51 +01:00
Samy Pessé 71c2e7f8b4 Version Packages (#3886)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-13 13:40:37 +00:00
Samy Pessé 985c1b45b4 Fix OpenAPI dereferencing for $ref to itself (#3899) 2026-01-13 13:34:08 +00:00
Zeno Kapitein 35efc9d6d8 Fix TOC icon backgrounds on gradient and bold themes (#3898) 2026-01-12 15:45:41 +01:00
Nolann B. 6b1fd43179 Lazy load OpenAPI styles and remove Tailwind Typography (#3883) 2026-01-12 15:09:02 +01:00
Zeno Kapitein 7517e987a5 Clean up button and input across the app (#3897) 2026-01-12 12:41:57 +01:00
Zeno Kapitein 21851d3340 Round page cover corners on extrawide screens (#3892) 2026-01-12 11:58:17 +01:00
Samy Pessé ca682362dd Fix 404 when accessing rss.xml for the root page (#3896) 2026-01-09 16:05:31 +01:00
David Hughes 56fb754e22 Update published-site.png (#3894) 2026-01-08 09:47:37 +08:00
Zeno Kapitein 99d788ed9e Update page cover styling (#3890) 2026-01-07 11:55:17 +01:00
Taran Vohra ebf3b2beb7 Fix an issue where content refs to spaces outside of site was resolving with share links (#3889) 2026-01-07 11:59:02 +05:30
Zeno Kapitein 7b7231602f Add e2e tests for embed frame (#3830) 2026-01-06 17:30:14 +01:00
Addison adfb118285 Merge branch 'main' of github.com:GitbookIO/gitbook 2026-01-06 13:22:44 +08:00
Zeno Kapitein a7f58ab829 Fix embed tab button size regression (#3887) 2026-01-05 14:01:02 +01:00
Zeno Kapitein 360b525b4b Fix FontAwesome 7 icon overflows (#3885) 2026-01-05 13:56:08 +01:00
Zeno Kapitein 695e99f466 Update button sizing (#3868) 2026-01-05 11:18:00 +01:00
Zeno Kapitein aa87699915 Fix block alignment (#3880) 2025-12-18 21:26:10 +01:00
Zeno Kapitein d2eb3d9f5a Fix disabled button from not showing up (#3879) 2025-12-18 21:13:14 +01:00
Samy Pessé 77bc339fca Version Packages (#3878)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-18 14:47:36 +01:00
Zeno Kapitein e7fb77116e Fix @gitbook/icons icon rendering (#3877) 2025-12-18 14:27:34 +01:00
Samy Pessé 050ca23ac4 Version Packages (#3818)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-18 11:10:53 +00:00
Zeno Kapitein 9063f97dba Switch main DocumentView to flex layout (#3875) 2025-12-18 09:43:42 +01:00
Zeno Kapitein ed9e8fe93a Support button actions (#3866) 2025-12-18 09:29:08 +01:00
Addison b9ad997fcb Add blob to style source for integrations 2025-09-22 17:01:16 +09:00
228 changed files with 6022 additions and 1481 deletions
@@ -2,4 +2,4 @@
"gitbook": patch
---
Fix search results cursor
OpenAPI styling tweaks
-5
View File
@@ -1,5 +0,0 @@
---
'@gitbook/react-openapi': patch
---
Fix missing properties in allOf/oneOf
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Prevent form submission during IME composition
+6
View File
@@ -0,0 +1,6 @@
---
"gitbook": minor
"@gitbook/react-openapi": patch
---
Support code themes customization
-5
View File
@@ -1,5 +0,0 @@
---
'@gitbook/react-openapi': patch
---
Enhance discriminator handling in OpenAPISchema
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Set <html lang> based on translated language
-6
View File
@@ -1,6 +0,0 @@
---
'@gitbook/openapi-parser': patch
'@gitbook/react-openapi': patch
---
Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Support social links
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/embed": patch
---
Fix signed JWT token usage
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Fix OpenAPI example not showing
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix overflowing section groups
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Serve a markdown version of the page when the "Accept" header contains "text/markdown".
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Align styling of PageLinkItem and PageDocumentItem
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/openapi-parser": patch
---
Update scalar dependencies.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Support `defaultExpanded` in Expandable block
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Better conditional display of page actions
-6
View File
@@ -1,6 +0,0 @@
---
"@gitbook/embed": minor
"gitbook": patch
---
Improve Docs Embed with separate Assistant and Docs tabs
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Improve OpenAPI server URL validation
@@ -2,4 +2,4 @@
"gitbook": patch
---
Add Input component
Header & TOC tweaks
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/icons": minor
---
Update to FontAwesome 7.1.0
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix ordered list item index calculation
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Fix Scalar Try it button justification
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Standardise toggle chevrons across the app
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Add customizable suggested questions
-5
View File
@@ -1,5 +0,0 @@
---
'@gitbook/react-openapi': patch
---
Fix OpenAPI basic auth placeholder
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix rendering of images in reusable content from an external space with the new dereferencing in the API.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix links to other spaces and root page in embeddable view.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Support `greeting` and fix suggested questions if there are no custom ones defined
-5
View File
@@ -1,5 +0,0 @@
---
'@gitbook/react-openapi': patch
---
Fix OpenAPI oneOf/allOf merge
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Grow InlineActionButton to take available width on line
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/icons": patch
---
Simplify installation by using public fontawesome mirror.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": minor
---
Add a RSS feed to all pages with update blocks.
-5
View File
@@ -1,5 +0,0 @@
---
'gitbook': patch
---
Remove hidden site spaces from navigation
-5
View File
@@ -1,5 +0,0 @@
---
'gitbook': patch
---
Fix hidden section not found
+6
View File
@@ -0,0 +1,6 @@
---
"@gitbook/browser-types": patch
"gitbook": patch
---
Add support for custom cookie banner registration
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Better handle huge section groups
+6
View File
@@ -0,0 +1,6 @@
---
"gitbook": patch
"@gitbook/embed": patch
---
Fix Embed options in React
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Add OpenAPI 3.1 nullable array support
-6
View File
@@ -1,6 +0,0 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Improve OpenAPI circular references
+2 -7
View File
@@ -5,8 +5,6 @@ on:
branches:
- main
- changeset-release/main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
format:
runs-on: ubuntu-latest
@@ -36,13 +34,10 @@ jobs:
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- run: bun unit
build-oss:
# CI to check that the repository builds correctly on a machine without the credentials
build:
runs-on: ubuntu-latest
name: Build (Open Source)
name: Build
timeout-minutes: 6
env:
BUN_NPM_TOKEN: ''
steps:
- name: Checkout
uses: actions/checkout@v4
+38 -7
View File
@@ -1,15 +1,25 @@
name: Preview
on:
pull_request:
pull_request_target:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
branches:
- main
jobs:
approval:
# Ensure that external contributors' PRs are manually approved before deployment
name: Approve Preview Deployment
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.fork }}
environment:
name: preview-approval
steps:
- name: Approval gate
run: echo "Preview deployment approved."
deploy-v2-vercel:
name: Deploy v2 to Vercel (preview)
runs-on: ubuntu-latest
needs: approval
if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }}
environment:
name: 2v-preview
url: ${{ steps.deploy.outputs.deployment-url }}
@@ -18,6 +28,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Deploy to Vercel
id: deploy
uses: ./.github/composite/deploy-vercel
@@ -31,6 +43,8 @@ jobs:
deploy-v2-cloudflare:
name: Deploy v2 to Cloudflare Worker (preview)
runs-on: ubuntu-latest
needs: approval
if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }}
environment:
name: 2c-preview
url: ${{ steps.deploy.outputs.deployment-url }}
@@ -39,6 +53,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Deploy to Cloudflare
id: deploy
uses: ./.github/composite/deploy-cloudflare
@@ -65,7 +81,7 @@ jobs:
comment-deployments:
runs-on: ubuntu-latest
name: Comment Deployments (preview)
if: always() && !startsWith(github.ref, 'refs/heads/main')
if: always() && github.event_name != 'push'
needs:
- deploy-v2-vercel
- deploy-v2-cloudflare
@@ -102,10 +118,13 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing v2
needs: deploy-v2-vercel
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
@@ -123,10 +142,13 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing v2 (Cloudflare)
needs: deploy-v2-cloudflare
if: ${{ always() && needs.deploy-v2-cloudflare.result == 'success' }}
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
@@ -144,10 +166,13 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing Customers v2
needs: deploy-v2-vercel
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
@@ -165,10 +190,13 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing Customers v2 (Cloudflare)
needs: deploy-v2-cloudflare
if: ${{ always() && needs.deploy-v2-cloudflare.result == 'success' }}
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
@@ -186,9 +214,12 @@ jobs:
runs-on: ubuntu-latest
name: Browserless Testing v2 (Vercel)
needs: deploy-v2-vercel
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
@@ -217,4 +248,4 @@ jobs:
# run: cd ./packages/gitbook && bun e2e-browserless
# env:
# BASE_URL: ${{needs.deploy-v2-cloudflare.outputs.deployment-url}}
# SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
# SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
-2
View File
@@ -3,8 +3,6 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (production)
-2
View File
@@ -3,8 +3,6 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (staging)
-3
View File
@@ -4,9 +4,6 @@ on:
push:
branches:
- main
env:
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
+1 -17
View File
@@ -77,23 +77,7 @@ All pull-requests will be tested against both visual and performances testing to
## Fonts and Icons
GitBook Open uses fontawesome. During development, your local environment will use the free version. However, only the pro version will be accepted by CI. If you see the following error:
```
The GitBook icon is missing. It indicates that the dependencies were installed without the correct font-awesome package. These changes have probably been persisted in the Bun lockfile. Read the README for more information.
```
It means that you've changed the GBO dependencies and bundled in the free version. Only GitBook staff can help with this - if you're not on the GitBook team, please ping us in the PR and we'll help get things moving.
If you are GitBook staff, you'll need our NPM token in your local environment.
```
.env.local
BUN_NPM_TOKEN=xxx
```
and then reinstall dependencies.
GitBook Open uses fontawesome. For self-hosting and local development, for licensing reasons, only the icons from the free version should be used.
## Contributing
Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 KiB

After

Width:  |  Height:  |  Size: 491 KiB

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

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