mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-15 07:05:18 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5d1bbfafe | |||
| dac5e9d444 | |||
| f647af2003 | |||
| 96e24a1f84 | |||
| 2a0f37f4b3 | |||
| 17d935dd4c | |||
| df07fbe242 | |||
| 1e53376151 | |||
| f294818775 | |||
| eed386a133 | |||
| e172b25ba8 | |||
| f7f6f9d06d | |||
| 9e01676fe9 | |||
| f6cf62a686 | |||
| a5ef44ab2a | |||
| b53cb1d2b3 | |||
| 7eeff73080 | |||
| bf63045319 | |||
| 71c2e7f8b4 | |||
| 985c1b45b4 | |||
| 35efc9d6d8 | |||
| 6b1fd43179 | |||
| 7517e987a5 | |||
| 21851d3340 | |||
| ca682362dd | |||
| 56fb754e22 | |||
| 99d788ed9e | |||
| ebf3b2beb7 | |||
| 7b7231602f | |||
| adfb118285 | |||
| a7f58ab829 | |||
| 360b525b4b | |||
| 695e99f466 | |||
| aa87699915 | |||
| d2eb3d9f5a | |||
| 77bc339fca | |||
| e7fb77116e | |||
| 050ca23ac4 | |||
| 9063f97dba | |||
| ed9e8fe93a | |||
| b9ad997fcb |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Fix missing properties in allOf/oneOf
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add Input component
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Enhance discriminator handling in OpenAPISchema
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Set <html lang> based on translated language
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/embed": patch
|
||||
---
|
||||
|
||||
Fix signed JWT token usage
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix overflowing section groups
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Serve a markdown version of the page when the "Accept" header contains "text/markdown".
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Align styling of PageLinkItem and PageDocumentItem
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix search results cursor
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Better conditional display of page actions
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@gitbook/embed": minor
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Improve Docs Embed with separate Assistant and Docs tabs
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@gitbook/icons": minor
|
||||
---
|
||||
|
||||
Update to FontAwesome 7.1.0
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Standardise toggle chevrons across the app
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add customizable suggested questions
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Fix OpenAPI basic auth placeholder
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix links to other spaces and root page in embeddable view.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Support `greeting` and fix suggested questions if there are no custom ones defined
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Fix OpenAPI oneOf/allOf merge
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Grow InlineActionButton to take available width on line
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Add a RSS feed to all pages with update blocks.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Remove hidden site spaces from navigation
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Fix hidden section not found
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve OpenAPI circular references
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 622 KiB After Width: | Height: | Size: 491 KiB |
@@ -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.4",
|
||||
"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,6 +157,7 @@
|
||||
"micromark-extension-frontmatter": "^2.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"motion": "^12.23.24",
|
||||
"negotiator": "^1.0.0",
|
||||
"next": "15.4.10",
|
||||
"next-themes": "^0.4.6",
|
||||
"nuqs": "^2.2.3",
|
||||
@@ -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,7 +220,7 @@
|
||||
},
|
||||
"packages/icons": {
|
||||
"name": "@gitbook/icons",
|
||||
"version": "0.3.4",
|
||||
"version": "0.4.2",
|
||||
"bin": {
|
||||
"gitbook-icons": "./bin/gitbook-icons.js",
|
||||
},
|
||||
@@ -242,11 +244,11 @@
|
||||
},
|
||||
"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.8.10",
|
||||
"@scalar/openapi-parser": "^0.23.12",
|
||||
"@scalar/openapi-types": "^0.5.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "catalog:",
|
||||
@@ -259,7 +261,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 +303,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 +331,7 @@
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react": ">=17",
|
||||
"react-dom": "*",
|
||||
},
|
||||
},
|
||||
@@ -346,7 +348,7 @@
|
||||
"react-dom": "catalog:",
|
||||
},
|
||||
"catalog": {
|
||||
"@gitbook/api": "0.154.0",
|
||||
"@gitbook/api": "0.158.0",
|
||||
"@scalar/api-client-react": "^1.3.46",
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@tsconfig/strictest": "^2.0.6",
|
||||
@@ -539,21 +541,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 +563,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=="],
|
||||
|
||||
@@ -727,7 +729,7 @@
|
||||
|
||||
"@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.158.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-oWrUY0gQ4rVKyNciHaXQrvi+EoiLCWHhAD0QyAhsHJp9+ndaLRMV6WfK2KbiGQmH8kiWwwBmwmQfMkZFmm1eyQ=="],
|
||||
|
||||
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
|
||||
|
||||
@@ -1287,23 +1289,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.6", "", {}, "sha512-A471YFBCj7ZOlGIkAYnU8oYgeyts82ZNX+4UicrlmKv3eAQ+kwboN3Dy0R6u1lcA/+I/zzeXi/fBObsT7P9qTA=="],
|
||||
|
||||
"@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.8.10", "", { "dependencies": { "@scalar/helpers": "0.2.6", "yaml": "^2.8.0" } }, "sha512-TWdKQ/hcy4erFQDp2MVlFoPesFep2VY96Q69cjLHmx5hxM0ZUBfmNB4lA8Uh3klgx5JmCDfSNIGjPFIpxlosUw=="],
|
||||
|
||||
"@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.23.12", "", { "dependencies": { "@scalar/json-magic": "0.8.10", "@scalar/openapi-types": "0.5.3", "@scalar/openapi-upgrader": "0.1.6", "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-Znq9J8sXUe20YdbK2myoyeFNXgWTeZlyQUJeQu+wNsXAfWHGT68bF7aZzl99TsPHHVl3WmKDeSrWiuexfJ1u6A=="],
|
||||
|
||||
"@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.6", "", { "dependencies": { "@scalar/openapi-types": "0.5.3" } }, "sha512-XdrNZUr0ASLfR89OS2zP6enbq9f7UGQQxov+a3WF1Wz9DClniAL2ChJ2fbGOrqL5F2kjbV6Fw/iO3bsBTMyLZA=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -1479,8 +1481,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 +1535,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=="],
|
||||
@@ -2245,7 +2247,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 +2325,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 +2341,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=="],
|
||||
@@ -2915,19 +2913,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.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.4", "turbo-darwin-arm64": "2.7.4", "turbo-linux-64": "2.7.4", "turbo-linux-arm64": "2.7.4", "turbo-windows-64": "2.7.4", "turbo-windows-arm64": "2.7.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bkO4AddmDishzJB2ze7aYYPaejMoJVfS0XnaR6RCdXFOY8JGJfQE+l9fKiV7uDPa5Ut44gmOWJL3894CIMeH9g=="],
|
||||
|
||||
"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.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xDR30ltfkSsRfGzABBckvl1nz1cZ3ssTujvdj+TPwOweeDRvZ0e06t5DS0rmRBvyKpgGs42K/EK6Mn2qLlFY9A=="],
|
||||
|
||||
"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.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P7sjqXtOL/+nYWPvcDGWhi8wf8M8mZHHB8XEzw2VX7VJrS8IGHyJHGD1AYfDvhAEcr7pnk3gGifz3/xyhI655w=="],
|
||||
|
||||
"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.4", "", { "os": "linux", "cpu": "x64" }, "sha512-GofFOxRO/IhG8BcPyMSSB3Y2+oKQotsaYbHxL9yD6JPb20/o35eo+zUSyazOtilAwDHnak5dorAJFoFU8MIg2A=="],
|
||||
|
||||
"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.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+RQKgNjksVPxYAyAgmDV7w/1qj++qca+nSNTAOKGOfJiDtSvRKoci89oftJ6anGs00uamLKVEQ712TI/tfNAIw=="],
|
||||
|
||||
"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.4", "", { "os": "win32", "cpu": "x64" }, "sha512-rfak1+g+ON3czs1mDYsCS4X74ZmK6gOgRQTXjDICtzvR4o61paqtgAYtNPofcVsMWeF4wvCajSeoAkkeAnQ1kg=="],
|
||||
|
||||
"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.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-1ZgBNjNRbDu/fPeqXuX9i26x3CJ/Y1gcwUpQ+Vp7kN9Un6RZ9kzs164f/knrjcu5E+szCRexVjRSJay1k5jApA=="],
|
||||
|
||||
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
||||
|
||||
@@ -3063,7 +3061,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 +3607,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 +3715,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 +3891,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 +3911,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 +3935,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 +4039,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 +4127,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 +4785,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 +4925,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 +4957,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 +4977,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 +5353,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=="],
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"turbo": "^2.6.1",
|
||||
"@changesets/cli": "^2.29.8",
|
||||
"turbo": "^2.7.4",
|
||||
"vercel": "^39.4.2"
|
||||
},
|
||||
"packageManager": "bun@1.3.2",
|
||||
@@ -41,7 +41,7 @@
|
||||
"catalog": {
|
||||
"@tsconfig/strictest": "^2.0.6",
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@gitbook/api": "0.154.0",
|
||||
"@gitbook/api": "0.158.0",
|
||||
"@scalar/api-client-react": "^1.3.46",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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 +1,2 @@
|
||||
export * from './client';
|
||||
export * from './standalone';
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
CustomizationSidebarListStyle,
|
||||
CustomizationThemeMode,
|
||||
} from '@gitbook/api';
|
||||
import type { GitBookStandalone } from '@gitbook/embed';
|
||||
import { expect } from '@playwright/test';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
@@ -40,7 +41,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 +273,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 +295,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 +307,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 +677,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',
|
||||
@@ -1291,7 +1228,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 +1254,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 +1923,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);
|
||||
|
||||
@@ -355,6 +355,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 +418,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 +456,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(() => {
|
||||
|
||||
@@ -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:",
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { MotionConfig } from 'motion/react';
|
||||
import { useCheckForContentUpdate } from '../AutoRefreshContent';
|
||||
import { useVisitorSession } from '../Insights';
|
||||
import { useCurrentPagePath } from '../hooks';
|
||||
import { DateRelative } from '../primitives';
|
||||
import { HideToolbarButton } from './HideToolbarButton';
|
||||
import { IframeWrapper } from './IframeWrapper';
|
||||
import { RefreshContentButton } from './RefreshContentButton';
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
type ToolbarControlsContextValue,
|
||||
ToolbarControlsProvider,
|
||||
} from './ToolbarControlsContext';
|
||||
import { ToolbarDate } from './ToolbarDate';
|
||||
import type { AdminToolbarClientProps, AdminToolbarContext } from './types';
|
||||
import { useToolbarVisibility } from './utils';
|
||||
|
||||
@@ -132,7 +132,7 @@ function ChangeRequestToolbar(props: ToolbarViewProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<Toolbar minified={minified} onMinifiedChange={onMinifiedChange} label="Site preview">
|
||||
<Toolbar minified={minified} onMinifiedChange={onMinifiedChange}>
|
||||
<ToolbarBody>
|
||||
<ToolbarTitle
|
||||
prefix={`Change #${changeRequest.number}:`}
|
||||
@@ -141,7 +141,7 @@ function ChangeRequestToolbar(props: ToolbarViewProps) {
|
||||
<ToolbarSubtitle
|
||||
subtitle={
|
||||
<>
|
||||
<DateRelative value={changeRequest.updatedAt} /> by {author}
|
||||
<ToolbarDate value={changeRequest.updatedAt} /> by {author}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -207,13 +207,13 @@ function RevisionToolbar(props: ToolbarViewProps) {
|
||||
const gitProvider = isGitHub ? 'GitHub' : 'GitLab';
|
||||
|
||||
return (
|
||||
<Toolbar minified={minified} onMinifiedChange={onMinifiedChange} label="Site preview">
|
||||
<Toolbar minified={minified} onMinifiedChange={onMinifiedChange}>
|
||||
<ToolbarBody>
|
||||
<ToolbarTitle prefix="Site version" suffix={context.site.title} />
|
||||
<ToolbarSubtitle
|
||||
subtitle={
|
||||
<>
|
||||
Created <DateRelative value={revision.createdAt} />
|
||||
Created <ToolbarDate value={revision.createdAt} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -280,20 +280,10 @@ function AuthenticatedUserToolbar(props: ToolbarViewProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
minified={minified}
|
||||
onMinifiedChange={onMinifiedChange}
|
||||
label="Only visible to your GitBook organization"
|
||||
>
|
||||
<Toolbar minified={minified} onMinifiedChange={onMinifiedChange}>
|
||||
<ToolbarBody>
|
||||
<ToolbarTitle suffix={context.site.title} />
|
||||
<ToolbarSubtitle
|
||||
subtitle={
|
||||
<>
|
||||
Updated <DateRelative value={revision.createdAt} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ToolbarSubtitle subtitle={<ToolbarDate value={revision.createdAt} />} />
|
||||
</ToolbarBody>
|
||||
<ToolbarSeparator />
|
||||
<ToolbarActions>
|
||||
@@ -305,13 +295,13 @@ function AuthenticatedUserToolbar(props: ToolbarViewProps) {
|
||||
|
||||
{/* Open site in GitBook */}
|
||||
<ToolbarButton
|
||||
title="Open site in GitBook"
|
||||
title="View site configuration"
|
||||
href={getToolbarHref({
|
||||
href: site.urls.app,
|
||||
siteId: site.id,
|
||||
buttonId: 'site',
|
||||
})}
|
||||
icon="gears"
|
||||
icon="folder-gear"
|
||||
/>
|
||||
|
||||
{/* Customize in GitBook */}
|
||||
@@ -361,13 +351,13 @@ function EditPageButton(props: {
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
title="Edit in GitBook"
|
||||
title="Edit this page"
|
||||
href={getToolbarHref({
|
||||
href: `${href}${pagePath.startsWith('/') ? pagePath.slice(1) : pagePath}`,
|
||||
siteId,
|
||||
buttonId: 'edit',
|
||||
})}
|
||||
icon="pencil"
|
||||
icon="pen-to-square"
|
||||
motionValues={motionValues}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -8,10 +8,20 @@ import { ToolbarButton, type ToolbarButtonProps } from './Toolbar';
|
||||
import styles from './Toolbar.module.css';
|
||||
import { useToolbarControls } from './ToolbarControlsContext';
|
||||
|
||||
const ARC_DURATION_SECONDS = 0.4;
|
||||
const ARC_STAGGER_MS = 80;
|
||||
const BASE_ROTATION_DEG = 95;
|
||||
const ROTATION_STEP_DEG = 18;
|
||||
const ARC_PARAMS = {
|
||||
arcWidth: 505,
|
||||
arcHeight: 400,
|
||||
arcRadius: 34,
|
||||
startDistance: -240,
|
||||
spreadDistance: 45,
|
||||
fromDistance: -286,
|
||||
fromSpread: 0,
|
||||
durationSeconds: 0.6,
|
||||
staggerMs: 80,
|
||||
baseRotationDeg: 95,
|
||||
rotationStepDeg: 18,
|
||||
offsetAnchorY: 40,
|
||||
} as const;
|
||||
|
||||
interface HideToolbarButtonProps {
|
||||
motionValues?: ToolbarButtonProps['motionValues'];
|
||||
@@ -23,30 +33,75 @@ interface HideToolbarButtonProps {
|
||||
export function HideToolbarButton(props: HideToolbarButtonProps) {
|
||||
const { motionValues } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [closing, setClosing] = React.useState(false);
|
||||
const [shouldMirror, setShouldMirror] = React.useState(false);
|
||||
const [shouldFlipVertical, setShouldFlipVertical] = React.useState(false);
|
||||
const controls = useToolbarControls();
|
||||
const closingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const close = React.useCallback(() => {
|
||||
if (!open || closing) return;
|
||||
setClosing(true);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (closingTimeoutRef.current) {
|
||||
clearTimeout(closingTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Wait for the exit animation to complete before unmounting
|
||||
const totalDuration = ARC_PARAMS.durationSeconds * 1000 + 3 * ARC_PARAMS.staggerMs;
|
||||
closingTimeoutRef.current = setTimeout(() => {
|
||||
setOpen(false);
|
||||
setClosing(false);
|
||||
closingTimeoutRef.current = null;
|
||||
}, totalDuration);
|
||||
}, [open, closing]);
|
||||
|
||||
// Clean up timeout on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (closingTimeoutRef.current) {
|
||||
clearTimeout(closingTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClickOutsideArcMenu = (event: Event) => {
|
||||
// Don't close the arc if we are clicking on the button itself
|
||||
if (buttonRef.current?.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
close();
|
||||
};
|
||||
// @ts-expect-error wrong type for ref
|
||||
useOnClickOutside(ref, handleClickOutsideArcMenu);
|
||||
|
||||
// Close arc menu on scroll
|
||||
// Measure button position to decide arc direction when opening
|
||||
React.useEffect(() => {
|
||||
if (open && buttonRef.current) {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
const distanceFromRight = window.innerWidth - rect.right;
|
||||
setShouldMirror(distanceFromRight < 250);
|
||||
setShouldFlipVertical(rect.top < 250);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// Close arc menu on scroll or resize
|
||||
React.useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
const handleScroll = () => setOpen(false);
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
const handleClose = () => close();
|
||||
window.addEventListener('scroll', handleClose, { passive: true });
|
||||
window.addEventListener('resize', handleClose, { passive: true });
|
||||
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, [open]);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleClose);
|
||||
window.removeEventListener('resize', handleClose);
|
||||
};
|
||||
}, [open, close]);
|
||||
|
||||
const items = [
|
||||
controls?.minimize
|
||||
@@ -84,36 +139,80 @@ export function HideToolbarButton(props: HideToolbarButtonProps) {
|
||||
return (
|
||||
<ToolbarButton
|
||||
ref={buttonRef}
|
||||
title={open ? 'Hide options' : 'Hide toolbar'}
|
||||
title={open ? undefined : 'Hide toolbar'}
|
||||
className={
|
||||
open || closing
|
||||
? 'border-[0.5px] border-neutral-5 border-solid dark:border-neutral-8'
|
||||
: undefined
|
||||
}
|
||||
onClick={() => {
|
||||
setOpen((v) => !v);
|
||||
if (open || closing) {
|
||||
close();
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
motionValues={motionValues}
|
||||
icon="eye-slash"
|
||||
icon="gear"
|
||||
>
|
||||
{/* Expanding arc menu */}
|
||||
{open && (
|
||||
{(open || closing) && (
|
||||
<motion.div
|
||||
className={tcls('pointer-events-none absolute inset-0', styles.arcMenu)}
|
||||
style={sharedMotionStyle as React.CSSProperties | undefined}
|
||||
style={
|
||||
{
|
||||
...sharedMotionStyle,
|
||||
'--arc-width': `${ARC_PARAMS.arcWidth}px`,
|
||||
'--arc-height': `${ARC_PARAMS.arcHeight}px`,
|
||||
'--arc-radius': `${ARC_PARAMS.arcRadius}%`,
|
||||
'--start-distance': `${ARC_PARAMS.startDistance}px`,
|
||||
'--spread-distance': `${ARC_PARAMS.spreadDistance}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
'pointer-events-none absolute left-0 overflow-visible',
|
||||
'pointer-events-none absolute overflow-visible',
|
||||
shouldMirror ? '' : 'left-0',
|
||||
styles.arcMenuPath
|
||||
)}
|
||||
style={
|
||||
{
|
||||
...(shouldMirror
|
||||
? {
|
||||
'--arc-mirror-offset': '-345px',
|
||||
'--spread-distance': '37px',
|
||||
right: 'var(--arc-mirror-offset)',
|
||||
}
|
||||
: undefined),
|
||||
...(shouldFlipVertical
|
||||
? {
|
||||
bottom: 'auto',
|
||||
top: 'calc(var(--arc-height) / -2)',
|
||||
transform: 'scaleY(-1)',
|
||||
}
|
||||
: undefined),
|
||||
} as React.CSSProperties
|
||||
}
|
||||
ref={ref}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<ArcToolbarButton
|
||||
index={index}
|
||||
staggerIndex={items.length - 1 - index}
|
||||
key={item.icon}
|
||||
staggerIndex={closing ? index : items.length - 1 - index}
|
||||
key={item.id}
|
||||
mirrored={shouldMirror}
|
||||
flippedVertical={shouldFlipVertical}
|
||||
closing={closing}
|
||||
{...item}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
item.onClick?.();
|
||||
}}
|
||||
onClick={
|
||||
item.isLabel
|
||||
? undefined
|
||||
: () => {
|
||||
close();
|
||||
item.onClick?.();
|
||||
}
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -127,13 +226,17 @@ type ArcMenuItem = {
|
||||
id: string;
|
||||
icon: IconName;
|
||||
label: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
onClick?: () => void;
|
||||
isLabel?: boolean;
|
||||
};
|
||||
|
||||
type ArcToolbarButtonProps = Pick<ArcMenuItem, 'label' | 'icon' | 'onClick'> & {
|
||||
type ArcToolbarButtonProps = Pick<ArcMenuItem, 'label' | 'icon' | 'onClick' | 'isLabel'> & {
|
||||
index: number;
|
||||
staggerIndex?: number;
|
||||
mirrored?: boolean;
|
||||
flippedVertical?: boolean;
|
||||
closing?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
@@ -143,40 +246,41 @@ export function ArcToolbarButton(props: ArcToolbarButtonProps) {
|
||||
const {
|
||||
index,
|
||||
staggerIndex = index,
|
||||
mirrored = false,
|
||||
flippedVertical = false,
|
||||
closing = false,
|
||||
label,
|
||||
disabled,
|
||||
className,
|
||||
onClick = () => {},
|
||||
onClick,
|
||||
icon,
|
||||
iconClassName,
|
||||
isLabel = false,
|
||||
} = props;
|
||||
|
||||
const targetOffset = `calc(var(--start-distance) + ${index} * var(--spread-distance))`;
|
||||
const fromOffset = `calc(${ARC_PARAMS.fromDistance}px + ${index} * ${ARC_PARAMS.fromSpread}px)`;
|
||||
|
||||
// Calculate rotation based on position along the arc
|
||||
const calculateRotation = () => {
|
||||
return BASE_ROTATION_DEG - index * ROTATION_STEP_DEG;
|
||||
};
|
||||
const itemRotation = ARC_PARAMS.baseRotationDeg - index * ARC_PARAMS.rotationStepDeg;
|
||||
|
||||
const itemRotation = calculateRotation();
|
||||
const Tag = isLabel ? 'div' : 'button';
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onClick();
|
||||
}}
|
||||
<Tag
|
||||
{...(Tag === 'button' ? { type: 'button' as const } : {})}
|
||||
onClick={onClick ? () => onClick() : undefined}
|
||||
style={
|
||||
{
|
||||
'--from-offset-distance': fromOffset,
|
||||
'--target-offset-distance': targetOffset,
|
||||
'--arc-duration': `${ARC_DURATION_SECONDS}s`,
|
||||
'--arc-delay': `${(staggerIndex ?? 0) * ARC_STAGGER_MS}ms`,
|
||||
'--arc-duration': `${ARC_PARAMS.durationSeconds}s`,
|
||||
'--arc-delay': `${(staggerIndex ?? 0) * ARC_PARAMS.staggerMs}ms`,
|
||||
'--rotation-offset': `${itemRotation}deg`,
|
||||
offsetPath: 'border-box',
|
||||
offsetDistance: targetOffset,
|
||||
offsetAnchor: '0% 40%',
|
||||
offsetRotate: `auto ${itemRotation}deg`,
|
||||
offsetDistance: fromOffset,
|
||||
offsetAnchor: `0% ${ARC_PARAMS.offsetAnchorY}%`,
|
||||
offsetRotate: 'auto 90deg',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={tcls(
|
||||
@@ -185,49 +289,61 @@ export function ArcToolbarButton(props: ArcToolbarButtonProps) {
|
||||
'top-0',
|
||||
'left-0',
|
||||
'w-40',
|
||||
'opacity-0',
|
||||
'pointer-events-auto',
|
||||
'flex',
|
||||
'items-center',
|
||||
'gap-2',
|
||||
styles.arcMenuItem,
|
||||
mirrored ? 'flex-row-reverse' : '',
|
||||
closing ? styles.arcMenuItemExit : styles.arcMenuItem,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
'flex shrink-0 items-center justify-center gap-1',
|
||||
'h-8 w-8 rounded-full border',
|
||||
'truncate text-sm',
|
||||
'cursor-pointer transition-colors',
|
||||
'group-hover:-rotate-5 group-hover:scale-105',
|
||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||
'text-tint-1 dark:text-tint-12',
|
||||
'bg-[linear-gradient(110deg,rgba(51,53,57,1)_0%,rgba(50,52,56,1)_100%)]',
|
||||
'dark:[background:linear-gradient(110deg,rgba(255,255,255,1)_0%,rgba(240,246,248,1)_100%)]',
|
||||
'border border-solid dark:border-[rgba(256,_256,_256,_0.06)]'
|
||||
)}
|
||||
style={{
|
||||
background: 'linear-gradient(rgb(51, 53, 57), rgb(50, 52, 56))',
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
style={flippedVertical ? { transform: 'scaleY(-1)' } : undefined}
|
||||
>
|
||||
<Icon
|
||||
icon={icon as IconName}
|
||||
iconStyle={IconStyle.Solid}
|
||||
className={tcls('size-4 shrink-0 group-hover:scale-110', iconClassName)}
|
||||
/>
|
||||
{isLabel ? (
|
||||
<div className="flex items-center gap-2 rounded-lg bg-[rgba(79,139,255,0.1)] px-2 py-1 text-[#4F8BFF] backdrop-blur-sm">
|
||||
<Icon
|
||||
icon={icon as IconName}
|
||||
iconStyle={IconStyle.Solid}
|
||||
className="size-3.5 shrink-0"
|
||||
/>
|
||||
<span className="whitespace-nowrap font-normal text-sm">{label}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={tcls(
|
||||
'flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center gap-1 truncate rounded-full text-sm transition-colors',
|
||||
'border-[0.5px] border-neutral-5 border-solid dark:border-neutral-8',
|
||||
'group-hover:scale-105',
|
||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||
'bg-[var(--toolbar-bg)] text-tint-7 hover:text-tint-1 dark:text-tint-12',
|
||||
'group-hover:bg-[color-mix(in_srgb,var(--toolbar-bg)_90%,white)]'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={icon as IconName}
|
||||
iconStyle={IconStyle.Solid}
|
||||
className={tcls(
|
||||
'size-3.5 shrink-0 group-hover:scale-110',
|
||||
iconClassName
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={tcls(
|
||||
'whitespace-nowrap rounded-lg border-[0.5px] border-neutral-5 border-solid bg-[var(--toolbar-bg)] px-3 py-1 font-normal text-neutral-1 text-sm transition-[background-color,transform] group-hover:scale-105 group-hover:bg-[color-mix(in_srgb,var(--toolbar-bg)_90%,white)] dark:border-neutral-8 dark:text-neutral-12',
|
||||
closing ? styles.arcLabelFadeOut : styles.arcLabelFadeIn
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={tcls(
|
||||
'whitespace-nowrap rounded-lg px-3 py-1 font-normal text-sm transition-transform',
|
||||
'group-hover:rotate-2 group-hover:scale-105',
|
||||
'text-neutral-1 dark:text-neutral-12',
|
||||
'bg-[linear-gradient(110deg,rgba(51,53,57,1)_0%,rgba(50,52,56,1)_100%)]'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
</Tag>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
.arcMenuItem {
|
||||
animation-name: hide-toolbar-arc-enter;
|
||||
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
animation-fill-mode: forwards;
|
||||
animation-fill-mode: both;
|
||||
animation-duration: var(--arc-duration, 0.4s);
|
||||
animation-delay: var(--arc-delay, 0s);
|
||||
transform-origin: center left;
|
||||
@@ -28,13 +28,101 @@
|
||||
|
||||
@keyframes hide-toolbar-arc-enter {
|
||||
from {
|
||||
offset-distance: var(--start-distance);
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
offset-distance: var(--from-offset-distance);
|
||||
offset-rotate: auto 90deg;
|
||||
}
|
||||
to {
|
||||
offset-distance: var(--target-offset-distance);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
offset-rotate: auto var(--rotation-offset);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hide-toolbar-arc-exit {
|
||||
from {
|
||||
offset-distance: var(--target-offset-distance);
|
||||
offset-rotate: auto var(--rotation-offset);
|
||||
}
|
||||
to {
|
||||
offset-distance: var(--from-offset-distance);
|
||||
offset-rotate: auto 90deg;
|
||||
}
|
||||
}
|
||||
|
||||
.arcMenuItemExit {
|
||||
animation-name: hide-toolbar-arc-exit;
|
||||
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
animation-fill-mode: both;
|
||||
animation-duration: var(--arc-duration, 0.3s);
|
||||
animation-delay: var(--arc-delay, 0s);
|
||||
transform-origin: center left;
|
||||
offset-path: border-box;
|
||||
offset-anchor: 0% 0%;
|
||||
offset-rotate: auto var(--rotation-offset, 0deg);
|
||||
}
|
||||
|
||||
/* ── Arc label fade ── */
|
||||
|
||||
@keyframes arc-label-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes arc-label-fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
.arcLabelFadeIn {
|
||||
animation: arc-label-fade-in 0.3s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.arcLabelFadeOut {
|
||||
animation: arc-label-fade-out 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
/* ── Glass effect for the toolbar pill ── */
|
||||
|
||||
.glassLayer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.glassLayer::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -50%;
|
||||
pointer-events: none;
|
||||
-webkit-backdrop-filter: blur(9px);
|
||||
backdrop-filter: blur(9px);
|
||||
filter: url(#glass-distortion);
|
||||
}
|
||||
|
||||
.glassLayer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
background: rgba(39, 39, 39, 0.55);
|
||||
box-shadow:
|
||||
inset 2px 2px 1px rgba(255, 255, 255, 0.08),
|
||||
inset -1px -1px 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
:global(.dark) .glassLayer::after {
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
box-shadow:
|
||||
inset 2px 2px 1px rgba(255, 255, 255, 0.05),
|
||||
inset -1px -1px 1px rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.glassLayer::before {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,39 +2,71 @@
|
||||
import {
|
||||
AnimatePresence,
|
||||
type MotionValue,
|
||||
animate,
|
||||
motion,
|
||||
useMotionValue,
|
||||
useReducedMotion,
|
||||
useSpring,
|
||||
} from 'motion/react';
|
||||
import React, { isValidElement } from 'react';
|
||||
import { AnimatedLogo } from './AnimatedLogo';
|
||||
import { useToolbarControls } from './ToolbarControlsContext';
|
||||
import {
|
||||
getStoredPosition,
|
||||
getVisibilityHintDismissed,
|
||||
setStoredPosition,
|
||||
setVisibilityHintDismissed,
|
||||
} from './utils';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon, type IconName, IconStyle } from '@gitbook/icons';
|
||||
import { Tooltip } from '../primitives';
|
||||
// import styles from './Toolbar.module.css';
|
||||
import { getCopyVariants, toolbarEasings } from './transitions';
|
||||
import { useMagnificationEffect } from './useMagnificationEffect';
|
||||
|
||||
const DEBUG = true;
|
||||
|
||||
const DURATION_LOGO_APPEARANCE = 2000;
|
||||
const DELAY_BETWEEN_LOGO_AND_CONTENT = 100;
|
||||
|
||||
const ToolbarDraggingContext = React.createContext(false);
|
||||
|
||||
interface ToolbarProps {
|
||||
label: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
minified: boolean;
|
||||
onMinifiedChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function Toolbar(props: ToolbarProps) {
|
||||
const { children, label, minified, onMinifiedChange } = props;
|
||||
const { children, minified, onMinifiedChange } = props;
|
||||
const controls = useToolbarControls();
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
const autoExpandTriggeredRef = React.useRef(false);
|
||||
const constraintsRef = React.useRef<HTMLDivElement>(null);
|
||||
const innerRef = React.useRef<HTMLDivElement>(null);
|
||||
const isDraggingRef = React.useRef(false);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
const prevWidthRef = React.useRef<number | undefined>(undefined);
|
||||
const [hintDismissed, setHintDismissed] = React.useState(() =>
|
||||
typeof window !== 'undefined' ? getVisibilityHintDismissed() : false
|
||||
);
|
||||
|
||||
const shouldAutoExpand = Boolean(controls?.shouldAutoExpand);
|
||||
const [shouldAnimateLogo, setShouldAnimateLogo] = React.useState(shouldAutoExpand);
|
||||
|
||||
// Restore saved drag position (synchronous read — no flash)
|
||||
const storedPos = React.useMemo(
|
||||
() => (typeof window !== 'undefined' ? getStoredPosition() : null),
|
||||
[]
|
||||
);
|
||||
const x = useMotionValue(storedPos?.x ?? 0);
|
||||
const y = useMotionValue(storedPos?.y ?? 0);
|
||||
|
||||
const savePosition = React.useCallback(() => {
|
||||
setStoredPosition({ x: x.get(), y: y.get() });
|
||||
}, [x, y]);
|
||||
|
||||
// Wait for page to be ready, then show the toolbar
|
||||
React.useEffect(() => {
|
||||
const handleLoad = () => {
|
||||
@@ -80,58 +112,432 @@ export function Toolbar(props: ToolbarProps) {
|
||||
React.useEffect(() => {
|
||||
if (!minified) {
|
||||
// Any manual expansion should stop the logo animation so the icon stays in its
|
||||
// “settled” state once the toolbar is open.
|
||||
// "settled" state once the toolbar is open.
|
||||
setShouldAnimateLogo(false);
|
||||
}
|
||||
}, [minified]);
|
||||
|
||||
// Detect pinning state based on actual viewport position
|
||||
const debugRef = React.useRef<HTMLDivElement>(null);
|
||||
const debugLeftEdgeRef = React.useRef<HTMLDivElement>(null);
|
||||
const debugRightEdgeRef = React.useRef<HTMLDivElement>(null);
|
||||
const debugArrowRef = React.useRef<HTMLDivElement>(null);
|
||||
const lastDecisionRef = React.useRef<'left' | 'right' | 'center'>('center');
|
||||
|
||||
const computePinState = React.useCallback(
|
||||
(overrideWidth?: number) => {
|
||||
if (!innerRef.current) return 'center' as const;
|
||||
const rect = innerRef.current.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth;
|
||||
const edgeThreshold = 80; // px from viewport edge
|
||||
|
||||
// When overrideWidth is provided (e.g. prevWidth), reconstruct where the
|
||||
// edges WERE before the resize. The center stays the same because x hasn't
|
||||
// been compensated yet and flexbox centering is width-independent.
|
||||
let left = rect.left;
|
||||
let right = rect.right;
|
||||
if (overrideWidth !== undefined) {
|
||||
const center = (rect.left + rect.right) / 2;
|
||||
left = center - overrideWidth / 2;
|
||||
right = center + overrideWidth / 2;
|
||||
}
|
||||
|
||||
const pin =
|
||||
right > viewportWidth - edgeThreshold
|
||||
? ('right' as const)
|
||||
: left < edgeThreshold
|
||||
? ('left' as const)
|
||||
: ('center' as const);
|
||||
|
||||
// Update debug overlays directly via DOM to avoid React re-render lag
|
||||
if (DEBUG) {
|
||||
// Text overlay
|
||||
if (debugRef.current) {
|
||||
const el = debugRef.current;
|
||||
el.textContent = '';
|
||||
const decisionColor = lastDecisionRef.current === 'center' ? '#facc15' : '#4ade80';
|
||||
const liveColor = pin === 'center' ? '#facc15' : '#4ade80';
|
||||
const preInfo =
|
||||
overrideWidth !== undefined
|
||||
? ` | pre: L${Math.round(left)} R${Math.round(right)}`
|
||||
: '';
|
||||
el.innerHTML = `decision: <span style="color:${decisionColor}">${lastDecisionRef.current}</span> | live: <span style="color:${liveColor}">${pin}</span> | x: ${Math.round(x.get())} y: ${Math.round(y.get())} | rect: L${Math.round(rect.left)} R${Math.round(rect.right)} (vw: ${viewportWidth})${preInfo}`;
|
||||
}
|
||||
|
||||
// Edge anchor indicators on the toolbar pill (track live state)
|
||||
if (debugLeftEdgeRef.current) {
|
||||
debugLeftEdgeRef.current.style.backgroundColor =
|
||||
pin === 'left' ? '#4ade80' : '#ffffff20';
|
||||
debugLeftEdgeRef.current.style.boxShadow =
|
||||
pin === 'left' ? '0 0 8px #4ade80' : 'none';
|
||||
}
|
||||
if (debugRightEdgeRef.current) {
|
||||
debugRightEdgeRef.current.style.backgroundColor =
|
||||
pin === 'right' ? '#4ade80' : '#ffffff20';
|
||||
debugRightEdgeRef.current.style.boxShadow =
|
||||
pin === 'right' ? '0 0 8px #4ade80' : 'none';
|
||||
}
|
||||
|
||||
// Arrow showing expansion direction (tracks live state so it
|
||||
// updates as you drag, showing what WOULD happen on click)
|
||||
if (debugArrowRef.current) {
|
||||
debugArrowRef.current.textContent =
|
||||
pin === 'left'
|
||||
? '→'
|
||||
: pin === 'right'
|
||||
? '←'
|
||||
: '↔';
|
||||
debugArrowRef.current.style.color =
|
||||
pin === 'center' ? '#facc15' : '#4ade80';
|
||||
}
|
||||
}
|
||||
|
||||
return pin;
|
||||
},
|
||||
[x, y]
|
||||
);
|
||||
|
||||
// Update debug display reactively when x/y change (direct DOM updates, no re-renders)
|
||||
React.useEffect(() => {
|
||||
if (!DEBUG) return;
|
||||
const unsubX = x.on('change', () => computePinState());
|
||||
const unsubY = y.on('change', () => computePinState());
|
||||
// Initial computation
|
||||
computePinState();
|
||||
return () => {
|
||||
unsubX();
|
||||
unsubY();
|
||||
};
|
||||
}, [x, y, computePinState]);
|
||||
|
||||
// Compensate drag x position when the toolbar width changes (expand/collapse) so the
|
||||
// pinned edge stays anchored. Uses viewport-aware edge detection instead of a fixed
|
||||
// x-threshold so it works correctly at any viewport width.
|
||||
React.useLayoutEffect(() => {
|
||||
if (!innerRef.current) return;
|
||||
|
||||
const newWidth = innerRef.current.offsetWidth;
|
||||
const prevWidth = prevWidthRef.current;
|
||||
|
||||
if (prevWidth !== undefined && prevWidth !== newWidth) {
|
||||
const delta = newWidth - prevWidth;
|
||||
const currentX = x.get();
|
||||
const currentY = y.get();
|
||||
const expanding = newWidth > prevWidth;
|
||||
|
||||
// Grab the raw post-resize rect for logging
|
||||
const rawRect = innerRef.current.getBoundingClientRect();
|
||||
const center = (rawRect.left + rawRect.right) / 2;
|
||||
const preResizeLeft = center - prevWidth / 2;
|
||||
const preResizeRight = center + prevWidth / 2;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const edgeThreshold = 80;
|
||||
|
||||
// Use prevWidth to reconstruct where the edges WERE before the resize.
|
||||
const pin = computePinState(prevWidth);
|
||||
lastDecisionRef.current = pin;
|
||||
// Refresh debug overlay so "decision" label updates immediately
|
||||
computePinState(prevWidth);
|
||||
|
||||
let compensation = 0;
|
||||
if (pin === 'right') {
|
||||
compensation = -delta / 2;
|
||||
} else if (pin === 'left') {
|
||||
compensation = delta / 2;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console -- temporary debug logging
|
||||
console.warn(
|
||||
`[Toolbar Pin] ${expanding ? 'EXPAND' : 'COLLAPSE'}\n` +
|
||||
` Action: ${expanding ? 'minified → expanded' : 'expanded → minified'}\n` +
|
||||
` Width: ${prevWidth}px → ${newWidth}px (delta: ${delta}px)\n` +
|
||||
` Motion: x=${Math.round(currentX)}, y=${Math.round(currentY)}\n` +
|
||||
` Viewport: ${viewportWidth}px, edgeThreshold: ${edgeThreshold}px\n` +
|
||||
` Post-resize rect (raw): L=${Math.round(rawRect.left)} R=${Math.round(rawRect.right)}\n` +
|
||||
` Pre-resize rect (reconstructed): L=${Math.round(preResizeLeft)} R=${Math.round(preResizeRight)}\n` +
|
||||
` Center: ${Math.round(center)}\n` +
|
||||
` Pin check: left=${Math.round(preResizeLeft)} < ${edgeThreshold}? ${preResizeLeft < edgeThreshold} | right=${Math.round(preResizeRight)} > ${viewportWidth - edgeThreshold}? ${preResizeRight > viewportWidth - edgeThreshold}\n` +
|
||||
` → Decision: ${pin}\n` +
|
||||
` → Compensation: ${compensation}px (x: ${Math.round(currentX)} → ${Math.round(currentX + compensation)})`
|
||||
);
|
||||
|
||||
if (compensation !== 0) {
|
||||
animate(x, currentX + compensation, {
|
||||
type: 'spring',
|
||||
stiffness: 200,
|
||||
damping: 30,
|
||||
mass: 1,
|
||||
}).then(savePosition);
|
||||
}
|
||||
}
|
||||
|
||||
prevWidthRef.current = newWidth;
|
||||
}, [minified, x, savePosition, computePinState]);
|
||||
|
||||
// Don't render anything until page is ready
|
||||
if (!isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<motion.div className="-translate-x-1/2 fixed bottom-5 left-1/2 z-40 w-auto max-w-xl transform px-4">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
onClick={() => {
|
||||
if (minified) {
|
||||
setShouldAnimateLogo(false);
|
||||
onMinifiedChange(false);
|
||||
}
|
||||
}}
|
||||
layout
|
||||
transition={toolbarEasings.spring}
|
||||
className={tcls(
|
||||
minified ? 'cursor-pointer px-2' : 'pr-2 pl-3.5',
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'min-h-11',
|
||||
'min-w-12',
|
||||
'h-12',
|
||||
'py-2',
|
||||
'backdrop-blur-sm',
|
||||
'origin-center',
|
||||
'border-[0.5px] border-neutral-5 border-solid dark:border-neutral-8',
|
||||
'bg-[linear-gradient(45deg,rgba(39,39,39,0.8)_100%,rgba(39,39,39,0.4)_80%)]',
|
||||
'dark:bg-[linear-gradient(45deg,rgba(39,39,39,0.5)_100%,rgba(39,39,39,0.3)_80%)]'
|
||||
)}
|
||||
style={{
|
||||
borderRadius: '100px', // This is set on `style` so Framer Motion can correct for distortions
|
||||
}}
|
||||
>
|
||||
{/* Logo with stroke segments animation in blue-tints */}
|
||||
<motion.div layout>
|
||||
<AnimatedLogo shouldAnimate={shouldAnimateLogo} />
|
||||
<ToolbarDraggingContext.Provider value={isDragging}>
|
||||
{/* Hidden SVG filter for glass distortion */}
|
||||
<svg aria-hidden="true" className="pointer-events-none fixed size-0">
|
||||
<defs>
|
||||
<filter id="glass-distortion">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.016 0.012"
|
||||
numOctaves={3}
|
||||
seed={39}
|
||||
result="noise"
|
||||
/>
|
||||
<feGaussianBlur in="noise" stdDeviation={6} result="softNoise" />
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="softNoise"
|
||||
scale={160}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
ref={constraintsRef}
|
||||
className="pointer-events-none fixed inset-2 z-40 flex items-end justify-center"
|
||||
>
|
||||
<motion.div
|
||||
drag
|
||||
dragConstraints={constraintsRef}
|
||||
dragElastic={0.04}
|
||||
dragTransition={{
|
||||
power: 0.2,
|
||||
timeConstant: 200,
|
||||
bounceStiffness: 800,
|
||||
bounceDamping: 60,
|
||||
}}
|
||||
style={{ x, y }}
|
||||
onDragStart={() => {
|
||||
isDraggingRef.current = true;
|
||||
setIsDragging(true);
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
setIsDragging(false);
|
||||
requestAnimationFrame(() => {
|
||||
isDraggingRef.current = false;
|
||||
});
|
||||
}}
|
||||
onDragTransitionEnd={savePosition}
|
||||
className="pointer-events-auto relative w-auto max-w-xl cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
{/* Visibility peek label — rendered as a sibling before the pill so it paints behind it */}
|
||||
{!hintDismissed && (
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{
|
||||
y: !minified && !isDragging ? 0 : 10,
|
||||
opacity: !minified && !isDragging ? 1 : 0,
|
||||
}}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 25,
|
||||
}}
|
||||
className="-translate-x-1/2 pointer-events-none absolute bottom-full left-1/2 flex items-center gap-1.5 rounded-t-xl border border-[#eaeaea] border-b-0 border-solid bg-white px-3"
|
||||
style={{ paddingBlock: '2px' }}
|
||||
>
|
||||
<span className="whitespace-nowrap text-[11px] text-neutral-9">
|
||||
Only visible to your GitBook organization
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-auto cursor-pointer rounded border border-tint-5 bg-tint-2 px-1 py-px text-[10px] text-tint-12 transition-colors hover:scale-102 hover:bg-tint-3 dark:border-tint-11/50 dark:bg-white dark:text-tint-1 dark:hover:bg-tint-11/20"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setVisibilityHintDismissed();
|
||||
setHintDismissed(true);
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{!minified ? children : null}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</Tooltip>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
ref={innerRef}
|
||||
onClick={() => {
|
||||
if (isDraggingRef.current) return;
|
||||
if (minified) {
|
||||
setShouldAnimateLogo(false);
|
||||
onMinifiedChange(false);
|
||||
}
|
||||
}}
|
||||
layout
|
||||
transition={toolbarEasings.spring}
|
||||
className={tcls(
|
||||
minified ? 'cursor-pointer' : 'pr-2 pl-3.5',
|
||||
'relative',
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'min-h-11',
|
||||
'min-w-12',
|
||||
'h-12',
|
||||
'py-2',
|
||||
'origin-center',
|
||||
'border-[0.5px] border-neutral-5 border-solid dark:border-neutral-8',
|
||||
'bg-[var(--toolbar-bg)]'
|
||||
)}
|
||||
style={
|
||||
{
|
||||
'--toolbar-bg': '#1f1d1b',
|
||||
borderRadius: '100px', // This is set on `style` so Framer Motion can correct for distortions
|
||||
zIndex: 1, // Ensure pill stacks above the peek label sibling
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{/* Glass effect layer (disabled for now) */}
|
||||
{/* <div className={styles.glassLayer} /> */}
|
||||
|
||||
{/* Debug: anchor edge indicators */}
|
||||
{DEBUG && (
|
||||
<>
|
||||
<div
|
||||
ref={debugLeftEdgeRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -1,
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
width: 3,
|
||||
borderRadius: 2,
|
||||
backgroundColor: '#ffffff20',
|
||||
transition: 'background-color 0.2s, box-shadow 0.2s',
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
ref={debugRightEdgeRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: -1,
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
width: 3,
|
||||
borderRadius: 2,
|
||||
backgroundColor: '#ffffff20',
|
||||
transition: 'background-color 0.2s, box-shadow 0.2s',
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
ref={debugArrowRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -20,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Logo — double-click to minimize without opening the arc menu */}
|
||||
<motion.div
|
||||
layout
|
||||
onDoubleClick={(e) => {
|
||||
if (isDraggingRef.current || minified) return;
|
||||
e.stopPropagation();
|
||||
onMinifiedChange(true);
|
||||
}}
|
||||
>
|
||||
<AnimatedLogo shouldAnimate={shouldAnimateLogo} />
|
||||
</motion.div>
|
||||
|
||||
{!minified ? children : null}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
|
||||
{/* Debug overlay for pinning state — updated via DOM ref to avoid re-render lag */}
|
||||
{DEBUG && (
|
||||
<>
|
||||
<div
|
||||
ref={debugRef}
|
||||
className="pointer-events-none whitespace-nowrap rounded bg-black/80 px-2 py-1 font-mono text-[10px] text-white"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 9999,
|
||||
}}
|
||||
/>
|
||||
{/* Viewport edge threshold zones */}
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 80,
|
||||
height: '100vh',
|
||||
backgroundColor: 'rgba(74, 222, 128, 0.06)',
|
||||
borderRight: '1px dashed rgba(74, 222, 128, 0.3)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 39,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 80,
|
||||
right: 4,
|
||||
fontSize: 9,
|
||||
color: 'rgba(74, 222, 128, 0.5)',
|
||||
writingMode: 'vertical-rl',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
pin-left zone (80px)
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 80,
|
||||
height: '100vh',
|
||||
backgroundColor: 'rgba(74, 222, 128, 0.06)',
|
||||
borderLeft: '1px dashed rgba(74, 222, 128, 0.3)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 39,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 80,
|
||||
left: 4,
|
||||
fontSize: 9,
|
||||
color: 'rgba(74, 222, 128, 0.5)',
|
||||
writingMode: 'vertical-rl',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
pin-right zone (80px)
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ToolbarDraggingContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,63 +603,53 @@ export const ToolbarButton = React.forwardRef<HTMLDivElement, ToolbarButtonProps
|
||||
children,
|
||||
} = props;
|
||||
const reduceMotion = useReducedMotion();
|
||||
const isDragging = React.useContext(ToolbarDraggingContext);
|
||||
|
||||
const anchor = (
|
||||
<motion.a
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={
|
||||
reduceMotion
|
||||
? undefined
|
||||
: {
|
||||
scale: motionValues?.scale,
|
||||
x: motionValues?.x,
|
||||
transformOrigin: 'bottom center',
|
||||
zIndex: motionValues?.scale ? 10 : 'auto',
|
||||
...style,
|
||||
}
|
||||
}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 400,
|
||||
damping: 30,
|
||||
}}
|
||||
className={tcls(
|
||||
'toolbar-button',
|
||||
className,
|
||||
'relative flex size-8 cursor-pointer items-center justify-center gap-1 truncate rounded-full text-sm transition-colors',
|
||||
'text-tint-7 hover:text-tint-1',
|
||||
'dark:text-tint-12',
|
||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||
'bg-[var(--toolbar-bg)]',
|
||||
'hover:bg-[color-mix(in_srgb,var(--toolbar-bg)_90%,white)]'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={icon}
|
||||
iconStyle={IconStyle.Solid}
|
||||
className={tcls('size-3.5 shrink-0 group-hover:scale-110', iconClassName)}
|
||||
/>
|
||||
</motion.a>
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div variants={toolbarEasings.staggeringChild} className="relative" ref={ref}>
|
||||
{children ? children : null}
|
||||
<Tooltip label={title}>
|
||||
<motion.a
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={
|
||||
reduceMotion
|
||||
? undefined
|
||||
: {
|
||||
scale: motionValues?.scale,
|
||||
x: motionValues?.x,
|
||||
transformOrigin: 'bottom center',
|
||||
zIndex: motionValues?.scale ? 10 : 'auto',
|
||||
...style,
|
||||
}
|
||||
}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 400,
|
||||
damping: 30,
|
||||
}}
|
||||
className={tcls(
|
||||
'toolbar-button',
|
||||
className,
|
||||
'flex',
|
||||
'relative',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'gap-1',
|
||||
'text-sm',
|
||||
'rounded-full',
|
||||
'truncate',
|
||||
'text-tint-1',
|
||||
'dark:text-tint-12',
|
||||
'cursor-pointer',
|
||||
'transition-colors',
|
||||
'size-8',
|
||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||
'border border-[rgba(256,_256,_256,_0.06)] border-solid',
|
||||
'bg-[linear-gradient(45deg,rgba(51,53,57,1)_0%,rgba(50,52,56,1)_100%)]'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={icon}
|
||||
iconStyle={IconStyle.Solid}
|
||||
className={tcls(
|
||||
'size-4 shrink-0 group-hover:scale-110 group-hover:text-tint-3',
|
||||
iconClassName
|
||||
)}
|
||||
/>
|
||||
</motion.a>
|
||||
</Tooltip>
|
||||
{title && !isDragging ? <Tooltip label={title}>{anchor}</Tooltip> : anchor}
|
||||
</motion.div>
|
||||
);
|
||||
});
|
||||
@@ -339,7 +735,7 @@ export function ToolbarSubtitle(props: { subtitle: React.ReactNode }) {
|
||||
return (
|
||||
<motion.span
|
||||
{...getCopyVariants(1)}
|
||||
className="text-neutral-1/80 text-xxs dark:text-neutral-12/80"
|
||||
className="inline-flex items-center gap-1 text-neutral-1/80 text-xxs dark:text-neutral-12/80"
|
||||
>
|
||||
{props.subtitle}
|
||||
</motion.span>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
|
||||
type DateFormat = 'relative' | 'weekday' | 'full';
|
||||
|
||||
const DATE_FORMATS: DateFormat[] = ['relative', 'weekday', 'full'];
|
||||
|
||||
/**
|
||||
* Toolbar-specific date display that cycles between relative, weekday, and full formats on click.
|
||||
*/
|
||||
export function ToolbarDate(props: { value: string }) {
|
||||
const { value } = props;
|
||||
const language = useLanguage();
|
||||
const [now, setNow] = React.useState<number>(Date.now());
|
||||
const [formatIndex, setFormatIndex] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
setNow(Date.now());
|
||||
},
|
||||
30 * 60 * 1000
|
||||
);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const date = new Date(value);
|
||||
const format = DATE_FORMATS[formatIndex] as DateFormat;
|
||||
|
||||
const formatted = React.useMemo(() => {
|
||||
return formatDateValue(format, language.locale, now, date);
|
||||
}, [format, language.locale, now, date]);
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center gap-1">
|
||||
{/* Vertical dots indicating cycleable formats */}
|
||||
<div className="flex flex-col items-center gap-[1px]">
|
||||
{DATE_FORMATS.map((fmt, i) => (
|
||||
<motion.span
|
||||
key={fmt}
|
||||
animate={{
|
||||
opacity: i === formatIndex ? 1 : 0.3,
|
||||
scale: i === formatIndex ? 1 : 0.75,
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
className="block size-[3px] rounded-full bg-current"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<time
|
||||
data-visual-test="transparent"
|
||||
suppressHydrationWarning={true}
|
||||
dateTime={value}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setFormatIndex((i) => (i + 1) % DATE_FORMATS.length);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setFormatIndex((i) => (i + 1) % DATE_FORMATS.length);
|
||||
}
|
||||
}}
|
||||
className="relative inline-flex min-w-24 cursor-pointer overflow-hidden font-semibold transition-colors hover:text-white"
|
||||
>
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
<motion.span
|
||||
key={formatIndex}
|
||||
initial={{ y: '-100%', opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: '100%', opacity: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
>
|
||||
{formatted}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
</time>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDateValue(format: DateFormat, locale: string, now: number, date: Date): string {
|
||||
switch (format) {
|
||||
case 'relative':
|
||||
return formatRelative(locale, now - date.getTime());
|
||||
case 'weekday':
|
||||
return date.toLocaleDateString(locale, {
|
||||
weekday: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
case 'full':
|
||||
return date.toLocaleString(locale, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatRelative(locale: string, diff: number) {
|
||||
if (typeof Intl === 'undefined' || typeof Intl.RelativeTimeFormat === 'undefined') {
|
||||
const days = Math.floor(diff / 24 / 60 / 60 / 1000);
|
||||
return `${days} days ago`;
|
||||
}
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(locale, { style: 'long' });
|
||||
|
||||
if (diff < 60 * 60 * 1000) {
|
||||
const minutes = Math.floor(diff / 60 / 1000);
|
||||
return rtf.format(-minutes, 'minute');
|
||||
}
|
||||
|
||||
if (diff < 24 * 60 * 60 * 1000) {
|
||||
const hours = Math.floor(diff / 60 / 60 / 1000);
|
||||
return rtf.format(-hours, 'hour');
|
||||
}
|
||||
|
||||
if (diff < 30 * 24 * 60 * 60 * 1000) {
|
||||
const days = Math.floor(diff / 24 / 60 / 60 / 1000);
|
||||
return rtf.format(-days, 'day');
|
||||
}
|
||||
|
||||
if (diff < 365 * 24 * 60 * 60 * 1000) {
|
||||
const months = Math.floor(diff / 30 / 24 / 60 / 60 / 1000);
|
||||
return rtf.format(-months, 'month');
|
||||
}
|
||||
|
||||
const years = Math.floor(diff / 365 / 24 / 60 / 60 / 1000);
|
||||
return rtf.format(-years, 'year');
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
const STORAGE_KEY = 'gitbook_toolbar_closed';
|
||||
const SESSION_STORAGE_KEY = 'gitbook_toolbar_session_closed';
|
||||
const SESSION_MINIFIED_KEY = 'gitbook_toolbar_minified';
|
||||
const SESSION_POSITION_KEY = 'gitbook_toolbar_position';
|
||||
const VISIBILITY_HINT_DISMISSED_KEY = 'gitbook_toolbar_hint_dismissed';
|
||||
|
||||
type SessionHideReason = 'session' | 'persistent';
|
||||
|
||||
@@ -57,6 +59,35 @@ export const setStoredMinified = (value: boolean) => {
|
||||
setSessionStorageItem(SESSION_MINIFIED_KEY, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the last drag position from session storage. Returns `null` when no position has been
|
||||
* stored, meaning the toolbar should use its default center-bottom placement.
|
||||
*/
|
||||
export const getStoredPosition = (): { x: number; y: number } | null => {
|
||||
return getSessionStorageItem<{ x: number; y: number } | null>(SESSION_POSITION_KEY, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist the current drag position for the ongoing session.
|
||||
*/
|
||||
export const setStoredPosition = (position: { x: number; y: number }) => {
|
||||
setSessionStorageItem(SESSION_POSITION_KEY, position);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the user has dismissed the "only you can see this" hint.
|
||||
*/
|
||||
export const getVisibilityHintDismissed = (): boolean => {
|
||||
return getLocalStorageItem(VISIBILITY_HINT_DISMISSED_KEY, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist that the user dismissed the visibility hint.
|
||||
*/
|
||||
export const setVisibilityHintDismissed = () => {
|
||||
setLocalStorageItem(VISIBILITY_HINT_DISMISSED_KEY, true);
|
||||
};
|
||||
|
||||
interface UseToolbarVisibilityOptions {
|
||||
onPersistentClose?: () => void;
|
||||
onSessionClose?: () => void;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -61,10 +61,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 +77,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"
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 { useDebounceCallback } from 'usehooks-ts';
|
||||
@@ -183,7 +183,7 @@ function CodeBlockExpandable(props: {
|
||||
</div>
|
||||
<div className="pointer-events-none absolute bottom-0 flex w-full justify-center">
|
||||
<Button
|
||||
icon={isExpanded ? 'chevron-up' : 'chevron-down'}
|
||||
icon={<ToggleChevron open={isExpanded} />}
|
||||
size="xsmall"
|
||||
variant="blank"
|
||||
type="button"
|
||||
|
||||
@@ -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';
|
||||
@@ -46,14 +47,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'
|
||||
)}
|
||||
|
||||
@@ -96,36 +96,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 ?? ''}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { useAI, useAIChatController, useAIChatState } from '../AI';
|
||||
import { useSearch } from '../Search';
|
||||
import { Button, type ButtonProps, Input } from '../primitives';
|
||||
|
||||
export function InlineActionButton(
|
||||
props: { action: 'ask' | 'search'; query?: string } & { buttonProps: ButtonProps } // TODO: Type this properly: Pick<api.DocumentInlineButton, 'action' | 'query'> & { buttonProps: ButtonProps }
|
||||
) {
|
||||
const { action, query, buttonProps } = props;
|
||||
|
||||
const { assistants } = useAI();
|
||||
const chatController = useAIChatController();
|
||||
const chatState = useAIChatState();
|
||||
const [, setSearchState] = useSearch();
|
||||
const language = useLanguage();
|
||||
|
||||
const handleSubmit = (value: string) => {
|
||||
if (action === 'ask') {
|
||||
chatController.open();
|
||||
if (value ?? query) {
|
||||
chatController.postMessage({ message: value ?? query });
|
||||
}
|
||||
} else if (action === 'search') {
|
||||
setSearchState((prev) => ({
|
||||
...prev,
|
||||
ask: null,
|
||||
scope: 'default',
|
||||
query: value ?? query,
|
||||
open: true,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const icon =
|
||||
action === 'ask' && buttonProps.icon === 'gitbook-assistant' && assistants.length > 0
|
||||
? assistants[0]?.icon
|
||||
: buttonProps.icon;
|
||||
|
||||
if (!query) {
|
||||
return (
|
||||
<Input
|
||||
inline
|
||||
label={buttonProps.label as string}
|
||||
sizing="medium"
|
||||
className="inline-flex max-w-full grow"
|
||||
submitButton={{
|
||||
label: tString(language, action === 'ask' ? 'send' : 'search'),
|
||||
}}
|
||||
clearButton={{
|
||||
className: 'text-[1em]',
|
||||
}}
|
||||
maxLength={action === 'ask' ? 2048 : 512}
|
||||
disabled={action === 'ask' && chatState.loading}
|
||||
aria-busy={action === 'ask' && chatState.loading}
|
||||
leading={icon}
|
||||
keyboardShortcut={false}
|
||||
onSubmit={(value) => handleSubmit(value as string)}
|
||||
containerStyle={{
|
||||
width: `${buttonProps.label ? buttonProps.label.toString().length + 10 : 20}ch`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const label = action === 'ask' ? `Ask "${query}"` : `Search for "${query}"`;
|
||||
|
||||
const button = (
|
||||
<Button {...buttonProps} onClick={() => handleSubmit(query)} label={label}>
|
||||
{label !== buttonProps.label ? buttonProps.label : null}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return button;
|
||||
}
|
||||
@@ -1,44 +1,81 @@
|
||||
import { resolveContentRef, resolveContentRefFallback } 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 && 'query' in inline.data.action) {
|
||||
return (
|
||||
<InlineActionButton
|
||||
action={inline.data.action.action}
|
||||
query={inline.data.action.query ?? ''}
|
||||
buttonProps={buttonProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ('ref' in inline.data) {
|
||||
return <InlineLinkButton {...props} buttonProps={buttonProps} />;
|
||||
}
|
||||
|
||||
return <Button {...buttonProps} disabled />;
|
||||
};
|
||||
|
||||
const 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 { inline, context, buttonProps } = props;
|
||||
|
||||
if (!('ref' in inline.data)) return;
|
||||
|
||||
const resolved =
|
||||
context.contentContext && inline.data.ref
|
||||
? await resolveContentRef(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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,8 @@ import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
|
||||
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
|
||||
import type { BlockProps } from '../Block';
|
||||
import { getOpenAPIContext } from './context';
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './scalar.css';
|
||||
import './style.css';
|
||||
import { DEFAULT_LOCALE, getSpaceLocale } from '@/intl/server';
|
||||
import type { GitBookAnyContext } from '@/lib/context';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@reference "../../RootLayout/globals.css";
|
||||
@import './scalar.css';
|
||||
|
||||
/* Layout Components */
|
||||
.openapi-operation,
|
||||
@@ -81,7 +82,7 @@
|
||||
|
||||
/* Markdown Base */
|
||||
.openapi-markdown {
|
||||
@apply prose text-tint-strong prose-p:my-2! dark:prose-invert max-w-none whitespace-normal;
|
||||
@apply prose [&_p]:my-2! text-tint-strong dark:prose-invert max-w-none whitespace-normal;
|
||||
}
|
||||
|
||||
.openapi-markdown > *:first-child {
|
||||
@@ -160,7 +161,7 @@
|
||||
}
|
||||
|
||||
.openapi-schema-root-description.openapi-markdown {
|
||||
@apply prose-sm text-balance mt-1.5 text-[0.813rem]! text-tint overflow-hidden font-normal! select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-balance mt-1.5 text-[0.813rem]! text-tint overflow-hidden font-normal! select-text [&_strong]:font-semibold [&_strong]:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description {
|
||||
@@ -279,7 +280,7 @@
|
||||
|
||||
/* Schema Description */
|
||||
.openapi-schema-description.openapi-markdown {
|
||||
@apply prose-sm text-tint overflow-hidden text-pretty font-normal! select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint overflow-hidden text-pretty font-normal! select-text [&_strong]:font-semibold [&_strong]:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-schema-description.openapi-markdown pre:has(code) {
|
||||
@@ -322,7 +323,7 @@
|
||||
|
||||
.openapi-securities-oauth-content,
|
||||
.openapi-securities-scopes {
|
||||
@apply prose *:!prose-sm *:text-tint;
|
||||
@apply prose *:prose-sm! *:text-tint;
|
||||
}
|
||||
|
||||
.openapi-securities-oauth-content {
|
||||
@@ -346,7 +347,7 @@
|
||||
}
|
||||
|
||||
.openapi-securities-description.openapi-markdown {
|
||||
@apply prose-sm text-tint font-normal! select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint font-normal! select-text text-pretty;
|
||||
}
|
||||
|
||||
.openapi-securities-label {
|
||||
@@ -377,7 +378,7 @@
|
||||
}
|
||||
|
||||
.openapi-requestbody-description.openapi-markdown {
|
||||
@apply prose-sm text-tint font-normal! text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply prose-sm text-tint font-normal! text-pretty select-text [&_strong]:font-semibold [&_strong]:text-inherit;
|
||||
}
|
||||
|
||||
/* Responses */
|
||||
@@ -395,7 +396,7 @@
|
||||
}
|
||||
|
||||
.openapi-response-description.openapi-markdown {
|
||||
@apply text-left prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
|
||||
@apply text-left prose-sm leading-tight text-tint select-text [&_strong]:font-semibold [&_strong]:text-inherit;
|
||||
}
|
||||
|
||||
.openapi-disclosure-group-trigger[aria-expanded="false"] .openapi-response-description.openapi-markdown {
|
||||
|
||||
@@ -9,8 +9,12 @@ import { getTextAlignment } from './utils';
|
||||
export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
|
||||
// InlineActionButtons use flex-grow to take the available width. This requires the parent to be a flex container.
|
||||
const inlineButtonStyle =
|
||||
'has-[.button,input]:flex has-[.button,input]:flex-wrap has-[.button,input]:gap-2 has-[.button,input]:items-center';
|
||||
|
||||
return (
|
||||
<p className={tcls(style, getTextAlignment(block.data?.align))}>
|
||||
<p className={tcls(inlineButtonStyle, style, getTextAlignment(block.data?.align))}>
|
||||
<Inlines {...contextProps} nodes={block.nodes} ancestorInlines={[]} />
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -9,11 +9,11 @@ export function getTextAlignment(textAlignment: TextAlignment | undefined): Clas
|
||||
switch (textAlignment) {
|
||||
case undefined:
|
||||
case TextAlignment.Start:
|
||||
return ['text-start', 'justify-self-start'];
|
||||
return ['text-start', 'self-start', 'justify-start'];
|
||||
case TextAlignment.Center:
|
||||
return ['text-center', 'justify-self-center'];
|
||||
return ['text-center', 'self-center', 'justify-center'];
|
||||
case TextAlignment.End:
|
||||
return ['text-end', 'justify-self-end'];
|
||||
return ['text-end', 'self-end', 'justify-end'];
|
||||
default:
|
||||
return nullIfNever(textAlignment);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
|
||||
/>
|
||||
<EmbeddableIframeButtons />
|
||||
</EmbeddableFrameSidebar>
|
||||
<EmbeddableFrameMain>
|
||||
<EmbeddableFrameMain data-testid="ai-chat">
|
||||
<EmbeddableFrameHeader>
|
||||
{!tabsRef.current ? (
|
||||
<AIChatDynamicIcon
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function EmbeddableDocsPage(
|
||||
/>
|
||||
<EmbeddableIframeButtons />
|
||||
</EmbeddableFrameSidebar>
|
||||
<EmbeddableFrameMain>
|
||||
<EmbeddableFrameMain data-testid="embed-docs-page">
|
||||
<div className="relative flex not-hydrated:animate-blur-in-slow flex-col">
|
||||
<EmbeddableFrameHeader>
|
||||
<HeaderMobileMenu className="-ml-2 page-no-toc:hidden" />
|
||||
@@ -80,8 +80,9 @@ export async function EmbeddableDocsPage(
|
||||
className="not-hydrated:animate-blur-in-slow"
|
||||
contentClassName="p-4"
|
||||
leading={{ fade: !context.sections, button: true }}
|
||||
trailing={{ fade: false, button: true }}
|
||||
>
|
||||
<TableOfContents context={context} />
|
||||
<TableOfContents context={context} withTrademark={false} />
|
||||
<PageBody
|
||||
context={context}
|
||||
page={page}
|
||||
|
||||
@@ -10,12 +10,12 @@ export function EmbeddableDocsPageControlButtons(props: { href: string }) {
|
||||
return (
|
||||
<Button
|
||||
icon="arrow-up-right-from-square"
|
||||
data-testid="embed-docs-page-open-in-new-tab"
|
||||
label={tString(language, 'open_in_new_tab')}
|
||||
href={href}
|
||||
target="_blank"
|
||||
iconOnly
|
||||
variant="blank"
|
||||
size="default"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,14 @@ export const EmbeddableFrame = React.forwardRef<HTMLDivElement, EmbeddableFrameP
|
||||
}
|
||||
);
|
||||
|
||||
export function EmbeddableFrameMain(props: { children: React.ReactNode }) {
|
||||
const { children } = props;
|
||||
export function EmbeddableFrameMain(props: React.ComponentProps<'div'>) {
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return <div className="flex flex-1 flex-col overflow-hidden">{children}</div>;
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function EmbeddableFrameHeader(props: {
|
||||
@@ -92,7 +96,7 @@ export function EmbeddableFrameSidebar(props: { children: React.ReactNode }) {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<div className="flex w-13 shrink-0 origin-top not-hydrated:animate-blur-in-slow flex-col gap-2 overflow-hidden border-tint-solid/3 border-r bg-tint-solid/1 p-2 transition-all transition-discrete duration-300 empty:hidden empty:w-0 empty:px-0">
|
||||
<div className="flex w-15 shrink-0 origin-top not-hydrated:animate-blur-in-slow flex-col gap-2 overflow-hidden border-tint-solid/3 border-r bg-tint-solid/1 p-2 transition-all transition-discrete duration-300 empty:hidden empty:w-0 empty:px-0">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { GitBookEmbeddableConfiguration, ParentToFrameMessage } from '@gitbook/embed';
|
||||
import { createChannel } from 'bidc';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { useAI, useAIChatController } from '@/components/AI';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
@@ -19,6 +19,12 @@ const embeddableConfiguration = createStore<GitBookEmbeddableConfiguration>(() =
|
||||
tools: [],
|
||||
}));
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: expected
|
||||
function log(...data: any[]) {
|
||||
// biome-ignore lint/suspicious/noConsole: expected
|
||||
console.log(...data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the API to communicate with the parent window.
|
||||
*/
|
||||
@@ -30,24 +36,32 @@ export function EmbeddableIframeAPI(props: {
|
||||
const router = useRouter();
|
||||
const chatController = useAIChatController();
|
||||
|
||||
// Live ref to avoid adding them as dependencies
|
||||
const refs = useRef({ router, chatController, baseURL });
|
||||
useEffect(() => {
|
||||
refs.current = { router, chatController, baseURL };
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
return chatController.on('open', () => {
|
||||
const { baseURL, router } = refs.current;
|
||||
router.push(`${baseURL}/assistant`);
|
||||
});
|
||||
}, [router, baseURL, chatController]);
|
||||
}, [chatController]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (window.parent === window) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[gitbook] create channel with parent window');
|
||||
log('[gitbook] create channel with parent window');
|
||||
const channel = createChannel();
|
||||
|
||||
channel.receive((payload) => {
|
||||
const { baseURL, router, chatController } = refs.current;
|
||||
const message = payload as ParentToFrameMessage;
|
||||
|
||||
console.log('[gitbook] received message', message);
|
||||
log('[gitbook] received message', message);
|
||||
|
||||
switch (message.type) {
|
||||
case 'clearChat': {
|
||||
@@ -79,10 +93,10 @@ export function EmbeddableIframeAPI(props: {
|
||||
});
|
||||
|
||||
return () => {
|
||||
console.log('[gitbook] cleanup');
|
||||
log('[gitbook] cleanup');
|
||||
channel.cleanup();
|
||||
};
|
||||
}, [chatController, router, baseURL]);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -112,8 +126,9 @@ export function EmbeddableIframeButtons() {
|
||||
)}
|
||||
{actions.map((action, index) => (
|
||||
<Button
|
||||
data-testid="embed-action"
|
||||
key={action.label}
|
||||
size="default"
|
||||
size="large"
|
||||
variant="blank"
|
||||
icon={action?.icon ?? 'square-question'}
|
||||
label={action?.label}
|
||||
@@ -195,7 +210,7 @@ export function EmbeddableIframeTabs(props: {
|
||||
key={tab.key}
|
||||
data-testid={`embed-tab-${tab.key}`}
|
||||
label={tab.label}
|
||||
size="default"
|
||||
size="large"
|
||||
variant="blank"
|
||||
icon={tab.icon}
|
||||
active={tab.key === active}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { VisitorAuthClaims } from '@/lib/adaptive';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { SiteInsightsTrademarkPlacement } from '@gitbook/api';
|
||||
import { SpaceLayoutServerContext } from '../SpaceLayout';
|
||||
import { TrademarkLink } from '../TableOfContents/Trademark';
|
||||
import { Trademark } from '../TableOfContents/Trademark';
|
||||
import { NavigationLoader } from '../primitives/NavigationLoader';
|
||||
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';
|
||||
|
||||
@@ -57,8 +57,8 @@ export async function EmbeddableRootLayout({
|
||||
<div className="fixed inset-0 flex flex-col">
|
||||
{children}
|
||||
{context.customization.trademark.enabled ? (
|
||||
<TrademarkLink
|
||||
className="border-tint-solid/3 border-t bg-tint-solid/1 px-4 py-2.5 text-tint/8 ring-0"
|
||||
<Trademark
|
||||
className="rounded-none! border-x-0 border-t border-b-0 bg-tint-solid/1 depth-flat:bg-tint-solid/1 px-4 py-2.5 text-tint/8"
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Embed}
|
||||
/>
|
||||
|
||||
@@ -70,6 +70,7 @@ export function Header(props: {
|
||||
>
|
||||
<div className="transition-all duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
|
||||
<div
|
||||
id="header-content"
|
||||
className={tcls(
|
||||
'gap-4',
|
||||
'lg:gap-6',
|
||||
@@ -211,7 +212,7 @@ export function Header(props: {
|
||||
) ?? siteSpace
|
||||
}
|
||||
siteSpaces={variants.translations}
|
||||
className="my-2 ml-2 self-start"
|
||||
className="my-1.5 ml-2 self-start"
|
||||
/>
|
||||
) : null}
|
||||
</SiteSectionTabs>
|
||||
|
||||
@@ -11,10 +11,9 @@ import assertNever from 'assert-never';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Button, Link } from '../primitives';
|
||||
import { Button, Link, ToggleChevron } from '../primitives';
|
||||
import {
|
||||
type DropdownButtonProps,
|
||||
DropdownChevron,
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
} from '../primitives/DropdownMenu';
|
||||
@@ -177,7 +176,7 @@ function HeaderItemLink(props: Omit<HeaderLinkNavItemProps, 'linkStyle'>) {
|
||||
{...rest}
|
||||
>
|
||||
{title}
|
||||
{isDropdown ? <DropdownChevron /> : null}
|
||||
{isDropdown ? <ToggleChevron /> : null}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -195,7 +194,7 @@ function HeaderItemDropdown(
|
||||
{...rest}
|
||||
>
|
||||
{title}
|
||||
<DropdownChevron />
|
||||
<ToggleChevron />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,8 @@ import type React from 'react';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import {
|
||||
DropdownChevron,
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
DropdownSubMenu,
|
||||
} from '../primitives/DropdownMenu';
|
||||
import { ToggleChevron } from '../primitives';
|
||||
import { DropdownMenu, DropdownMenuItem, DropdownSubMenu } from '../primitives/DropdownMenu';
|
||||
import styles from './headerLinks.module.css';
|
||||
|
||||
/**
|
||||
@@ -44,7 +40,7 @@ export function HeaderLinkMore(props: {
|
||||
>
|
||||
<span className="sr-only">{label}</span>
|
||||
<Icon icon="ellipsis" className={tcls('size-4')} />
|
||||
<DropdownChevron />
|
||||
<ToggleChevron />
|
||||
</button>
|
||||
);
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ export function HeaderMobileMenu(props: ButtonProps) {
|
||||
|
||||
return (
|
||||
<Button
|
||||
data-testid="toc-button"
|
||||
icon="bars"
|
||||
iconOnly
|
||||
variant="blank"
|
||||
size="default"
|
||||
label={tString(language, 'table_of_contents_button_label')}
|
||||
onClick={() => {
|
||||
document.body.classList.toggle(globalClassName);
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useMemo } from 'react';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { getSiteSpaceURL } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Button, type ButtonProps } from '../primitives';
|
||||
import { DropdownChevron, DropdownMenu } from '../primitives/DropdownMenu';
|
||||
import { Button, type ButtonProps, ToggleChevron } from '../primitives';
|
||||
import { DropdownMenu } from '../primitives/DropdownMenu';
|
||||
import { SpacesDropdownMenuItems } from './SpacesDropdownMenuItem';
|
||||
|
||||
// Memoized regex for checking if a string starts with an emoji
|
||||
@@ -35,9 +35,9 @@ export function SpacesDropdown(props: {
|
||||
<Button
|
||||
icon={icon}
|
||||
data-testid="space-dropdown-button"
|
||||
size="medium"
|
||||
size="small"
|
||||
variant={variant}
|
||||
trailing={<DropdownChevron />}
|
||||
trailing={<ToggleChevron />}
|
||||
className={tcls('bg-tint-base', className)}
|
||||
>
|
||||
<span className="button-content">{siteSpace.title}</span>
|
||||
@@ -77,7 +77,7 @@ export function TranslationsDropdown(props: {
|
||||
siteSpaces={siteSpaces}
|
||||
variant="blank"
|
||||
className={tcls(
|
||||
'-mx-2 bg-transparent px-2 md:py-1 lg:max-w-64 max-md:[&_.button-content]:hidden',
|
||||
'-mx-3 bg-transparent lg:max-w-64 max-md:[&_.button-content]:hidden',
|
||||
hasEmojiPrefix
|
||||
? 'md:[&_.button-leading-icon]:hidden' // If the title starts with an emoji, don't show the icon (on desktop)
|
||||
: '',
|
||||
|
||||
@@ -15,7 +15,7 @@ import { notFound } from 'next/navigation';
|
||||
import * as React from 'react';
|
||||
|
||||
import { DocumentView } from '@/components/DocumentView';
|
||||
import { TrademarkLink } from '@/components/TableOfContents/Trademark';
|
||||
import { Trademark } from '@/components/TableOfContents/Trademark';
|
||||
import type { PolymorphicComponentProp } from '@/components/utils/types';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { tString } from '@/intl/translate';
|
||||
@@ -148,7 +148,7 @@ export async function PDFPage(props: {
|
||||
total={total}
|
||||
trademark={
|
||||
customization.trademark.enabled ? (
|
||||
<TrademarkLink
|
||||
<Trademark
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Pdf}
|
||||
/>
|
||||
|
||||
@@ -387,7 +387,7 @@ function PageActionWrapper(props: {
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
label={label ?? shortLabel}
|
||||
className="bg-tint-base text-sm"
|
||||
className="bg-tint-base"
|
||||
onClick={onClick}
|
||||
href={href}
|
||||
target={href ? target : undefined}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Button, ButtonGroup } from '@/components/primitives/Button';
|
||||
import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu';
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import type { GitSyncState, SiteCustomizationSettings } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import React, { useRef } from 'react';
|
||||
import { useAI } from '../AI';
|
||||
import { ToggleChevron } from '../primitives';
|
||||
import {
|
||||
ActionCopyMCPURL,
|
||||
ActionCopyMarkdown,
|
||||
@@ -51,23 +51,18 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) {
|
||||
return defaultAction || dropdownActions.length > 0 ? (
|
||||
<ButtonGroup ref={ref} className={props.className}>
|
||||
{defaultAction}
|
||||
{dropdownActions.length > 0 ? (
|
||||
{!defaultAction || dropdownActions.length > 1 ? (
|
||||
<DropdownMenu
|
||||
align="end"
|
||||
className="!min-w-60 max-w-max"
|
||||
button={
|
||||
<Button
|
||||
icon={
|
||||
<Icon
|
||||
icon="chevron-down"
|
||||
className="size-3 transition-transform group-data-[state=open]/button:rotate-180"
|
||||
/>
|
||||
}
|
||||
icon={<ToggleChevron className="size-text-sm" />}
|
||||
label={tString(language, defaultAction ? 'more' : 'actions')}
|
||||
iconOnly={!!defaultAction}
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
className="bg-tint-base text-sm"
|
||||
className="bg-tint-base"
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -43,11 +43,6 @@ export function PageAside(props: {
|
||||
'opacity-0',
|
||||
|
||||
'xl:flex',
|
||||
'xl:page-no-toc:fixed',
|
||||
'xl:page-no-toc:mr-[max(0px,calc((100vw-96rem)/2-20rem))]',
|
||||
'xl:page-no-toc:right-8',
|
||||
'xl:page-no-toc:bottom-0',
|
||||
'z-20',
|
||||
|
||||
'overflow-hidden',
|
||||
|
||||
@@ -107,7 +102,7 @@ export function PageAside(props: {
|
||||
className={tcls(
|
||||
'flex flex-col',
|
||||
'min-w-56 shrink-0',
|
||||
'page-has-toc:overflow-hidden',
|
||||
'overflow-hidden',
|
||||
'w-full',
|
||||
'xl:max-2xl:rounded-corners:page-api-block:rounded-md',
|
||||
'xl:max-2xl:circular-corners:page-api-block:rounded-xl',
|
||||
@@ -213,7 +208,7 @@ async function PageAsideFooter(props: { context: GitBookSiteContext }) {
|
||||
<div
|
||||
className={tcls(
|
||||
'sticky bottom-0 z-10 mt-auto flex flex-col',
|
||||
// 'bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
|
||||
'bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
|
||||
'border-tint-subtle xl:max-2xl:page-api-block:border-t xl:max-2xl:page-api-block:p-2',
|
||||
'pt-4'
|
||||
)}
|
||||
|
||||
@@ -65,6 +65,8 @@ export function PageBody(props: {
|
||||
(page) => page.type !== 'document' || (page.type === 'document' && !page.hidden)
|
||||
).length > 0;
|
||||
|
||||
const pageHasToc = page.layout.tableOfContents && hasVisibleTOCItems;
|
||||
|
||||
return (
|
||||
<CurrentPageProvider page={{ spaceId: context.space.id, pageId: page.id }}>
|
||||
<main
|
||||
@@ -74,14 +76,12 @@ export function PageBody(props: {
|
||||
// Allow words to break if they are too long.
|
||||
'break-anywhere',
|
||||
'@container',
|
||||
pageWidthWide ? 'page-width-wide max-w-[92rem]' : 'page-width-default',
|
||||
pageWidthWide ? 'page-width-wide 3xl:px-8' : 'page-width-default',
|
||||
siteWidthWide ? 'site-width-wide' : 'site-width-default',
|
||||
page.layout.tableOfContents && hasVisibleTOCItems
|
||||
? 'page-has-toc'
|
||||
: 'page-no-toc'
|
||||
pageHasToc ? 'page-has-toc' : 'page-no-toc'
|
||||
)}
|
||||
>
|
||||
<PreservePageLayout siteWidthWide={siteWidthWide} />
|
||||
<PreservePageLayout siteWidthWide={siteWidthWide} pageHasToc={pageHasToc} />
|
||||
{page.cover && page.layout.cover && page.layout.coverSize === 'hero' ? (
|
||||
<PageCover as="hero" page={page} cover={page.cover} context={context} />
|
||||
) : null}
|
||||
@@ -105,7 +105,7 @@ export function PageBody(props: {
|
||||
<SuspenseLoadedHint />
|
||||
<DocumentView
|
||||
document={document}
|
||||
style="grid [&>*+*]:mt-5"
|
||||
style="flex flex-col [&>*+*]:mt-5"
|
||||
blockStyle="page-api-block:ml-0"
|
||||
context={{
|
||||
mode: 'default',
|
||||
|
||||
@@ -85,23 +85,36 @@ export async function PageCover(props: {
|
||||
'overflow-hidden',
|
||||
// Negative margin to balance the container padding
|
||||
'-mx-4',
|
||||
'transition-all',
|
||||
as === 'full'
|
||||
? [
|
||||
'sm:-mx-6',
|
||||
'md:-mx-8',
|
||||
'lg:-mr-8',
|
||||
'lg:-ml-12',
|
||||
!page.layout.tableOfContents &&
|
||||
context.customization.header.preset !== 'none'
|
||||
? 'xl:-ml-76'
|
||||
: null,
|
||||
? [
|
||||
'xl:-ml-76',
|
||||
// Round the bottom corners once the page is wider than the image
|
||||
'2xl:circular-corners:rounded-b-3xl 2xl:rounded-corners:rounded-b-xl',
|
||||
]
|
||||
: [
|
||||
'lg:-ml-12',
|
||||
// Round the bottom left corner once the sidebar is shown next to it
|
||||
'lg:rounded-corners:rounded-bl-xl',
|
||||
'lg:circular-corners:rounded-bl-3xl',
|
||||
// Round the bottom right corner once the page is wider than the image
|
||||
'2xl:rounded-corners:rounded-br-xl',
|
||||
'2xl:circular-corners:rounded-br-3xl',
|
||||
],
|
||||
]
|
||||
: [
|
||||
'sm:mx-auto',
|
||||
'max-w-3xl ',
|
||||
'page-width-wide:max-w-screen-2xl',
|
||||
'sm:rounded-md',
|
||||
'sm:rounded-corners:rounded-xl',
|
||||
'sm:circular-corners:rounded-3xl',
|
||||
'mb-8',
|
||||
'max-sm:-mt-8',
|
||||
]
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -22,12 +22,31 @@ export async function PageHeader(props: {
|
||||
const { context, page, ancestors, withRSSFeed } = props;
|
||||
const { revision, linker } = context;
|
||||
|
||||
if (!page.layout.title && !page.layout.description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasAncestors = ancestors.length > 0;
|
||||
|
||||
// Show page actions if *any* of the actions are enabled
|
||||
const hasPageActions = [
|
||||
...Object.values(context.customization.pageActions),
|
||||
context.customization.pdf.enabled,
|
||||
context.customization.git.showEditLink,
|
||||
withRSSFeed,
|
||||
].some(Boolean);
|
||||
|
||||
/* When title and description are hidden, only display the page actions if there are any. */
|
||||
if (!page.layout.title && !page.layout.description) {
|
||||
if (!hasPageActions) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PageActionsDropdown
|
||||
siteTitle={context.site.title}
|
||||
urls={getPageActionsURLs({ context, page, withRSSFeed })}
|
||||
actions={context.customization.pageActions}
|
||||
className="absolute top-8 right-0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<header
|
||||
className={tcls(
|
||||
@@ -41,8 +60,7 @@ export async function PageHeader(props: {
|
||||
hasAncestors ? 'page-has-ancestors' : 'page-no-ancestors'
|
||||
)}
|
||||
>
|
||||
{page.layout.tableOfContents ? (
|
||||
// Show page actions if *any* of the actions are enabled
|
||||
{hasPageActions ? (
|
||||
<PageActionsDropdown
|
||||
siteTitle={context.site.title}
|
||||
urls={getPageActionsURLs({ context, page, withRSSFeed })}
|
||||
|
||||
@@ -11,9 +11,10 @@ import * as React from 'react';
|
||||
* 3. Page 2 with full width block: `body:has(.site-width-wide)` is true
|
||||
*
|
||||
* This component ensures that the layout is preserved while transitioning between the 2 page states (in step 2).
|
||||
* It also preserves the page TOC state (page-has-toc/page-no-toc) to prevent logo sizing issues during navigation.
|
||||
*/
|
||||
export function PreservePageLayout(props: { siteWidthWide: boolean }) {
|
||||
const { siteWidthWide } = props;
|
||||
export function PreservePageLayout(props: { siteWidthWide: boolean; pageHasToc: boolean }) {
|
||||
const { siteWidthWide, pageHasToc } = props;
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
// We use the header as it's an element preserved between page transitions
|
||||
@@ -28,7 +29,15 @@ export function PreservePageLayout(props: { siteWidthWide: boolean }) {
|
||||
} else {
|
||||
header.classList.remove('site-width-wide');
|
||||
}
|
||||
}, [siteWidthWide]);
|
||||
|
||||
if (pageHasToc) {
|
||||
header.classList.add('page-has-toc');
|
||||
header.classList.remove('page-no-toc');
|
||||
} else {
|
||||
header.classList.add('page-no-toc');
|
||||
header.classList.remove('page-has-toc');
|
||||
}
|
||||
}, [siteWidthWide, pageHasToc]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,13 +23,12 @@ import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { type FontData, getFontData } from '@/fonts';
|
||||
import { fontNotoColorEmoji, fonts } from '@/fonts/default';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getSpaceLanguage, getSpaceLocale } from '@/intl/server';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { RootLayoutClientContexts } from './RootLayoutClientContexts';
|
||||
|
||||
import '@gitbook/icons/style.css';
|
||||
import './globals.css';
|
||||
import type { GitBookAnyContext } from '@/lib/context';
|
||||
import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@/lib/env';
|
||||
@@ -69,6 +68,7 @@ export async function CustomizationRootLayout(props: {
|
||||
const customization =
|
||||
'customization' in context ? context.customization : defaultCustomization();
|
||||
|
||||
const locale = getSpaceLocale(context);
|
||||
const language = getSpaceLanguage(context);
|
||||
const tintColor = getTintColor(customization);
|
||||
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
|
||||
@@ -91,7 +91,7 @@ export async function CustomizationRootLayout(props: {
|
||||
return (
|
||||
<html
|
||||
suppressHydrationWarning
|
||||
lang={customization.internationalization.locale}
|
||||
lang={locale}
|
||||
className={tcls(
|
||||
customization.styling.corners && `${customization.styling.corners}-corners`,
|
||||
'theme' in customization.styling && `theme-${customization.styling.theme}`,
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
@config '../../../tailwind.config.ts';
|
||||
|
||||
@import './prose.css';
|
||||
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentcolor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
|
||||
@@ -0,0 +1,849 @@
|
||||
/* ===================================================================
|
||||
TW-PROSE - CSS-only Typography for Tailwind CSS v4
|
||||
Compatible with @utility directives and Tailwind v4 variables
|
||||
=================================================================== */
|
||||
|
||||
|
||||
/* Base */
|
||||
@utility prose {
|
||||
color: var(--tw-prose-body);
|
||||
max-width: 65ch;
|
||||
font-size: var(--text-base);
|
||||
line-height: calc(var(--leading-normal) + 0.25);
|
||||
|
||||
--tw-prose-body: oklch(37.3% .034 259.733);
|
||||
--tw-prose-headings: oklch(21% .034 264.665);
|
||||
--tw-prose-lead: oklch(44.6% .03 256.802);
|
||||
--tw-prose-links: oklch(21% .034 264.665);
|
||||
--tw-prose-bold: oklch(21% .034 264.665);
|
||||
--tw-prose-counters: oklch(55.1% .027 264.364);
|
||||
--tw-prose-bullets: oklch(87.2% .01 258.338);
|
||||
--tw-prose-hr: oklch(92.8% .006 264.531);
|
||||
--tw-prose-quotes: oklch(21% .034 264.665);
|
||||
--tw-prose-quote-borders: oklch(92.8% .006 264.531);
|
||||
--tw-prose-captions: oklch(55.1% .027 264.364);
|
||||
--tw-prose-kbd: oklch(21% .034 264.665);
|
||||
--tw-prose-kbd-shadows: NaN NaN NaN;
|
||||
--tw-prose-code: oklch(21% .034 264.665);
|
||||
--tw-prose-pre-code: oklch(92.8% .006 264.531);
|
||||
--tw-prose-pre-bg: oklch(27.8% .033 256.848);
|
||||
--tw-prose-th-borders: oklch(87.2% .01 258.338);
|
||||
--tw-prose-td-borders: oklch(92.8% .006 264.531);
|
||||
--tw-prose-invert-body: oklch(87.2% .01 258.338);
|
||||
--tw-prose-invert-headings: #fff;
|
||||
--tw-prose-invert-lead: oklch(70.7% .022 261.325);
|
||||
--tw-prose-invert-links: #fff;
|
||||
--tw-prose-invert-bold: #fff;
|
||||
--tw-prose-invert-counters: oklch(70.7% .022 261.325);
|
||||
--tw-prose-invert-bullets: oklch(44.6% .03 256.802);
|
||||
--tw-prose-invert-hr: oklch(37.3% .034 259.733);
|
||||
--tw-prose-invert-quotes: oklch(96.7% .003 264.542);
|
||||
--tw-prose-invert-quote-borders: oklch(37.3% .034 259.733);
|
||||
--tw-prose-invert-captions: oklch(70.7% .022 261.325);
|
||||
--tw-prose-invert-kbd: #fff;
|
||||
--tw-prose-invert-kbd-shadows: 255 255 255;
|
||||
--tw-prose-invert-code: #fff;
|
||||
--tw-prose-invert-pre-code: oklch(87.2% .01 258.338);
|
||||
--tw-prose-invert-pre-bg: #00000080;
|
||||
--tw-prose-invert-th-borders: oklch(44.6% .03 256.802);
|
||||
--tw-prose-invert-td-borders: oklch(37.3% .034 259.733);
|
||||
|
||||
& p {
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
& [class~='lead'] {
|
||||
color: var(--tw-prose-lead);
|
||||
font-size: 1.25em;
|
||||
line-height: 1.6;
|
||||
margin-top: 1.2em;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--tw-prose-links);
|
||||
text-decoration: underline;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
& strong {
|
||||
color: inherit;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
& a strong {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& blockquote strong {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& thead th strong {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& ol {
|
||||
list-style-type: decimal;
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
padding-inline-start: 1.625em;
|
||||
}
|
||||
|
||||
& ol[type='A'] {
|
||||
list-style-type: upper-alpha;
|
||||
}
|
||||
|
||||
& ol[type='a'] {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
& ol[type='A' s] {
|
||||
list-style-type: upper-alpha;
|
||||
}
|
||||
|
||||
& ol[type='a' s] {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
& ol[type='I'] {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
|
||||
& ol[type='i'] {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
& ol[type='I' s] {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
|
||||
& ol[type='i' s] {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
& ol[type='1'] {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
& ul {
|
||||
list-style-type: disc;
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
padding-inline-start: 1.625em;
|
||||
}
|
||||
|
||||
& ol > li::marker {
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--tw-prose-counters);
|
||||
}
|
||||
|
||||
& ul > li::marker {
|
||||
color: var(--tw-prose-bullets);
|
||||
}
|
||||
|
||||
& dt {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
& hr {
|
||||
border-color: var(--tw-prose-hr);
|
||||
border-top-width: 1px;
|
||||
margin-top: 3em;
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
& blockquote {
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-style: italic;
|
||||
color: var(--tw-prose-quotes);
|
||||
border-inline-start-width: 0.25rem;
|
||||
border-inline-start-color: var(--tw-prose-quote-borders);
|
||||
quotes: '\201C' '\201D' '\2018' '\2019';
|
||||
margin-top: 1.6em;
|
||||
margin-bottom: 1.6em;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
& blockquote p:first-of-type::before {
|
||||
content: open-quote;
|
||||
}
|
||||
|
||||
& blockquote p:last-of-type::after {
|
||||
content: close-quote;
|
||||
}
|
||||
|
||||
& h1 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-extrabold);
|
||||
font-size: 2.25em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.8888889em;
|
||||
line-height: 1.1111111;
|
||||
}
|
||||
|
||||
& h1 strong {
|
||||
font-weight: var(--font-weight-black);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& h2 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: 1.5em;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.3333333;
|
||||
}
|
||||
|
||||
& h2 strong {
|
||||
font-weight: var(--font-weight-extrabold);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& h3 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: 1.25em;
|
||||
margin-top: 1.6em;
|
||||
margin-bottom: 0.6em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
& h3 strong {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& h4 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
& h4 strong {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& img {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
& picture {
|
||||
display: block;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
& video {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
& kbd {
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: inherit;
|
||||
color: var(--tw-prose-kbd);
|
||||
box-shadow:
|
||||
0 0 0 1px oklch(from var(--tw-prose-kbd-shadows) l c h / 10%),
|
||||
0 3px 0 oklch(from var(--tw-prose-kbd-shadows) l c h / 10%);
|
||||
font-size: 0.875em;
|
||||
border-radius: 0.3125rem;
|
||||
padding-top: 0.1875em;
|
||||
padding-inline-end: 0.375em;
|
||||
padding-bottom: 0.1875em;
|
||||
padding-inline-start: 0.375em;
|
||||
}
|
||||
|
||||
& code {
|
||||
color: var(--tw-prose-code);
|
||||
font-weight: var(--font-weight-normal);
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
& code::before {
|
||||
content: '`';
|
||||
}
|
||||
|
||||
& code::after {
|
||||
content: '`';
|
||||
}
|
||||
|
||||
& a code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& h1 code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& h2 code {
|
||||
color: inherit;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
& h3 code {
|
||||
color: inherit;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
& h4 code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& blockquote code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& thead th code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
& pre {
|
||||
color: var(--tw-prose-pre-code);
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
overflow-x: auto;
|
||||
font-weight: var(--font-weight-normal);
|
||||
font-size: 0.875em;
|
||||
line-height: 1.7142857;
|
||||
margin-top: 1.7142857em;
|
||||
margin-bottom: 1.7142857em;
|
||||
border-radius: var(--radius-md);
|
||||
padding-top: 0.8571429em;
|
||||
padding-inline-end: 1.1428571em;
|
||||
padding-bottom: 0.8571429em;
|
||||
padding-inline-start: 1.1428571em;
|
||||
}
|
||||
|
||||
& pre:has(code) {
|
||||
color: var(--tw-prose-pre-code);
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
}
|
||||
|
||||
& pre code {
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
& pre code::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
& pre code::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
& table {
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.7142857;
|
||||
}
|
||||
|
||||
& thead {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--tw-prose-th-borders);
|
||||
}
|
||||
|
||||
& thead th {
|
||||
color: var(--tw-prose-headings);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
vertical-align: bottom;
|
||||
padding-inline-end: 0.5714286em;
|
||||
padding-bottom: 0.5714286em;
|
||||
padding-inline-start: 0.5714286em;
|
||||
}
|
||||
|
||||
& tbody tr {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--tw-prose-td-borders);
|
||||
}
|
||||
|
||||
& tbody tr:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
& tbody td {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
& tfoot {
|
||||
border-top-width: 1px;
|
||||
border-top-color: var(--tw-prose-th-borders);
|
||||
}
|
||||
|
||||
& tfoot td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
& th, & td {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
& figure > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& figcaption {
|
||||
color: var(--tw-prose-captions);
|
||||
font-size: 0.875em;
|
||||
line-height: 1.4285714;
|
||||
margin-top: 0.8571429em;
|
||||
}
|
||||
|
||||
& picture > img {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& li {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
& ol > li {
|
||||
padding-inline-start: 0.375em;
|
||||
}
|
||||
|
||||
& ul > li {
|
||||
padding-inline-start: 0.375em;
|
||||
}
|
||||
|
||||
& > ul > li p {
|
||||
margin-top: 0.75em;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
& > ul > li > p:first-child {
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
& > ul > li > p:last-child {
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
& > ol > li > p:first-child {
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
& > ol > li > p:last-child {
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
& ul ul, & ul ol, & ol ul, & ol ol {
|
||||
margin-top: 0.75em;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
& dl {
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
& dd {
|
||||
margin-top: 0.5em;
|
||||
padding-inline-start: 1.625em;
|
||||
}
|
||||
|
||||
& hr + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h2 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h3 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h4 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& thead th:first-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
& thead th:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
& tbody td, & tfoot td {
|
||||
padding-top: 0.5714286em;
|
||||
padding-inline-end: 0.5714286em;
|
||||
padding-bottom: 0.5714286em;
|
||||
padding-inline-start: 0.5714286em;
|
||||
}
|
||||
|
||||
& tbody td:first-child, & tfoot td:first-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
& tbody td:last-child, & tfoot td:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
& figure {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
& > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@utility prose-sm {
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.71429;
|
||||
|
||||
&.prose {
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.71429;
|
||||
}
|
||||
|
||||
& p,
|
||||
&.prose p {
|
||||
margin-top: 1.1428571em;
|
||||
margin-bottom: 1.1428571em;
|
||||
}
|
||||
|
||||
& [class~='lead'],
|
||||
&.prose [class~='lead'] {
|
||||
font-size: 1.2857143em;
|
||||
line-height: 1.5555556;
|
||||
margin-top: 0.8888889em;
|
||||
margin-bottom: 0.8888889em;
|
||||
}
|
||||
|
||||
& blockquote,
|
||||
&.prose blockquote {
|
||||
margin-top: 1.3333333em;
|
||||
margin-bottom: 1.3333333em;
|
||||
padding-inline-start: 1.1111111em;
|
||||
}
|
||||
|
||||
& h1,
|
||||
&.prose h1 {
|
||||
font-size: 2.1428571em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.8em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
& h2,
|
||||
&.prose h2 {
|
||||
font-size: 1.4285714em;
|
||||
margin-top: 1.6em;
|
||||
margin-bottom: 0.8em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
& h3,
|
||||
&.prose h3 {
|
||||
font-size: 1.2857143em;
|
||||
margin-top: 1.5555556em;
|
||||
margin-bottom: 0.4444444em;
|
||||
line-height: 1.5555556;
|
||||
}
|
||||
|
||||
& h4,
|
||||
&.prose h4 {
|
||||
margin-top: 1.4285714em;
|
||||
margin-bottom: 0.5714286em;
|
||||
line-height: 1.4285714;
|
||||
}
|
||||
|
||||
& img,
|
||||
&.prose img {
|
||||
margin-top: 1.7142857em;
|
||||
margin-bottom: 1.7142857em;
|
||||
}
|
||||
|
||||
& picture,
|
||||
&.prose picture {
|
||||
margin-top: 1.7142857em;
|
||||
margin-bottom: 1.7142857em;
|
||||
}
|
||||
|
||||
& picture > img,
|
||||
&.prose picture > img {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& video,
|
||||
&.prose video {
|
||||
margin-top: 1.7142857em;
|
||||
margin-bottom: 1.7142857em;
|
||||
}
|
||||
|
||||
& kbd,
|
||||
&.prose kbd {
|
||||
font-size: 0.8571429em;
|
||||
border-radius: 0.3125rem;
|
||||
padding-top: 0.1428571em;
|
||||
padding-inline-end: 0.3571429em;
|
||||
padding-bottom: 0.1428571em;
|
||||
padding-inline-start: 0.3571429em;
|
||||
}
|
||||
|
||||
& code,
|
||||
&.prose code {
|
||||
font-size: 0.8571429em;
|
||||
}
|
||||
|
||||
& h2 code,
|
||||
&.prose h2 code {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
& h3 code,
|
||||
&.prose h3 code {
|
||||
font-size: 0.8888889em;
|
||||
}
|
||||
|
||||
& pre,
|
||||
&.prose pre {
|
||||
font-size: 0.8571429em;
|
||||
line-height: 1.6666667;
|
||||
margin-top: 1.6666667em;
|
||||
margin-bottom: 1.6666667em;
|
||||
border-radius: var(--radius-sm);
|
||||
padding-top: 0.6666667em;
|
||||
padding-inline-end: 1em;
|
||||
padding-bottom: 0.6666667em;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
& ol,
|
||||
&.prose ol {
|
||||
margin-top: 1.1428571em;
|
||||
margin-bottom: 1.1428571em;
|
||||
padding-inline-start: 1.5714286em;
|
||||
}
|
||||
|
||||
& ul,
|
||||
&.prose ul {
|
||||
margin-top: 1.1428571em;
|
||||
margin-bottom: 1.1428571em;
|
||||
padding-inline-start: 1.5714286em;
|
||||
}
|
||||
|
||||
& li,
|
||||
&.prose li {
|
||||
margin-top: 0.2857143em;
|
||||
margin-bottom: 0.2857143em;
|
||||
}
|
||||
|
||||
& ol > li,
|
||||
&.prose ol > li {
|
||||
padding-inline-start: 0.4285714em;
|
||||
}
|
||||
|
||||
& ul > li,
|
||||
&.prose ul > li {
|
||||
padding-inline-start: 0.4285714em;
|
||||
}
|
||||
|
||||
& > ul > li p,
|
||||
&.prose > ul > li p {
|
||||
margin-top: 0.5714286em;
|
||||
margin-bottom: 0.5714286em;
|
||||
}
|
||||
|
||||
& > ul > li > p:first-child,
|
||||
&.prose > ul > li > p:first-child {
|
||||
margin-top: 1.1428571em;
|
||||
}
|
||||
|
||||
& > ul > li > p:last-child,
|
||||
&.prose > ul > li > p:last-child {
|
||||
margin-bottom: 1.1428571em;
|
||||
}
|
||||
|
||||
& > ol > li > p:first-child,
|
||||
&.prose > ol > li > p:first-child {
|
||||
margin-top: 1.1428571em;
|
||||
}
|
||||
|
||||
& > ol > li > p:last-child,
|
||||
&.prose > ol > li > p:last-child {
|
||||
margin-bottom: 1.1428571em;
|
||||
}
|
||||
|
||||
& ul ul,
|
||||
& ul ol,
|
||||
& ol ul,
|
||||
& ol ol,
|
||||
&.prose ul ul,
|
||||
&.prose ul ol,
|
||||
&.prose ol ul,
|
||||
&.prose ol ol {
|
||||
margin-top: 0.5714286em;
|
||||
margin-bottom: 0.5714286em;
|
||||
}
|
||||
|
||||
& dl,
|
||||
&.prose dl {
|
||||
margin-top: 1.1428571em;
|
||||
margin-bottom: 1.1428571em;
|
||||
}
|
||||
|
||||
& dt,
|
||||
&.prose dt {
|
||||
margin-top: 1.1428571em;
|
||||
}
|
||||
|
||||
& dd,
|
||||
&.prose dd {
|
||||
margin-top: 0.2857143em;
|
||||
padding-inline-start: 1.5714286em;
|
||||
}
|
||||
|
||||
& hr,
|
||||
&.prose hr {
|
||||
margin-top: 2.8571429em;
|
||||
margin-bottom: 2.8571429em;
|
||||
}
|
||||
|
||||
& hr + *,
|
||||
&.prose hr + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h2 + *,
|
||||
&.prose h2 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h3 + *,
|
||||
&.prose h3 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& h4 + *,
|
||||
&.prose h4 + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& table,
|
||||
&.prose table {
|
||||
font-size: 0.8571429em;
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
& thead th,
|
||||
&.prose thead th {
|
||||
padding-inline-end: 1em;
|
||||
padding-bottom: 0.6666667em;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
& thead th:first-child,
|
||||
&.prose thead th:first-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
& thead th:last-child,
|
||||
&.prose thead th:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
& tbody td,
|
||||
& tfoot td,
|
||||
&.prose tbody td,
|
||||
&.prose tfoot td {
|
||||
padding-top: 0.6666667em;
|
||||
padding-inline-end: 1em;
|
||||
padding-bottom: 0.6666667em;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
& tbody td:first-child,
|
||||
& tfoot td:first-child,
|
||||
&.prose tbody td:first-child,
|
||||
&.prose tfoot td:first-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
& tbody td:last-child,
|
||||
& tfoot td:last-child,
|
||||
&.prose tbody td:last-child,
|
||||
&.prose tfoot td:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
& figure,
|
||||
&.prose figure {
|
||||
margin-top: 1.7142857em;
|
||||
margin-bottom: 1.7142857em;
|
||||
}
|
||||
|
||||
& figure > *,
|
||||
&.prose figure > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& figcaption,
|
||||
&.prose figcaption {
|
||||
font-size: 0.8571429em;
|
||||
line-height: 1.3333333;
|
||||
margin-top: 0.6666667em;
|
||||
}
|
||||
|
||||
& > :first-child,
|
||||
&.prose > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& > :last-child,
|
||||
&.prose > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Invert (for dark mode) */
|
||||
@utility prose-invert {
|
||||
--tw-prose-body: var(--tw-prose-invert-body);
|
||||
--tw-prose-headings: var(--tw-prose-invert-headings);
|
||||
--tw-prose-lead: var(--tw-prose-invert-lead);
|
||||
--tw-prose-links: var(--tw-prose-invert-links);
|
||||
--tw-prose-bold: var(--tw-prose-invert-bold);
|
||||
--tw-prose-counters: var(--tw-prose-invert-counters);
|
||||
--tw-prose-bullets: var(--tw-prose-invert-bullets);
|
||||
--tw-prose-hr: var(--tw-prose-invert-hr);
|
||||
--tw-prose-quotes: var(--tw-prose-invert-quotes);
|
||||
--tw-prose-quote-borders: var(--tw-prose-invert-quote-borders);
|
||||
--tw-prose-captions: var(--tw-prose-invert-captions);
|
||||
--tw-prose-kbd: var(--tw-prose-invert-kbd);
|
||||
--tw-prose-kbd-shadows: var(--tw-prose-invert-kbd-shadows);
|
||||
--tw-prose-code: var(--tw-prose-invert-code);
|
||||
--tw-prose-pre-code: var(--tw-prose-invert-pre-code);
|
||||
--tw-prose-pre-bg: var(--tw-prose-invert-pre-bg);
|
||||
--tw-prose-th-borders: var(--tw-prose-invert-th-borders);
|
||||
--tw-prose-td-borders: var(--tw-prose-invert-td-borders);
|
||||
}
|
||||
@@ -58,14 +58,14 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
containerRef={containerRef as React.RefObject<HTMLDivElement | null>}
|
||||
sizing="medium"
|
||||
label={tString(language, withAI ? 'search_or_ask' : 'search')}
|
||||
className="@max-2xl:absolute inset-y-0 right-0 z-30 @max-2xl:max-w-9.5 grow theme-bold:border-header-link/4 theme-bold:bg-header-link/1 @max-2xl:px-2.5 theme-bold:text-header-link theme-bold:shadow-none! theme-bold:backdrop-blur-xl @max-2xl:focus-within:w-56 @max-2xl:focus-within:max-w-[calc(100vw-5rem)] theme-bold:focus-within:border-header-link/6 theme-bold:focus-within:ring-header-link/5 theme-bold:hover:border-header-link/5 @max-2xl:has-[input[aria-expanded=true]]:w-56 @max-2xl:has-[input[aria-expanded=true]]:max-w-[calc(100vw-5rem)] @max-2xl:[&_input]:opacity-0 theme-bold:[&_input]:placeholder:text-header-link/8 @max-2xl:focus-within:[&_input]:opacity-11 @max-2xl:has-[input[aria-expanded=true]]:[&_input]:opacity-11"
|
||||
className="@max-2xl:absolute inset-y-0 right-0 z-30 @max-2xl:max-w-9.5 grow theme-bold:border-header-link/4 theme-bold:bg-header-link/1 @max-2xl:px-2.5 theme-bold:text-header-link theme-bold:shadow-none! theme-bold:backdrop-blur-xl @max-2xl:focus-within:w-56 @max-2xl:focus-within:max-w-[calc(100vw-5rem)] theme-bold:focus-within:border-header-link/6 theme-bold:focus-within:ring-header-link/5 theme-bold:hover:border-header-link/5 theme-bold:hover:not-focus-within:bg-header-link/2 @max-2xl:has-[input[aria-expanded=true]]:w-56 @max-2xl:has-[input[aria-expanded=true]]:max-w-[calc(100vw-5rem)] @max-2xl:[&_input]:opacity-0 theme-bold:[&_input]:placeholder:text-header-link/8 @max-2xl:focus-within:[&_input]:opacity-11 @max-2xl:has-[input[aria-expanded=true]]:[&_input]:opacity-11"
|
||||
placeholder={`${tString(language, withAI ? 'search_or_ask' : 'search')}…`}
|
||||
onFocus={onFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
leading={
|
||||
<Icon
|
||||
icon="search"
|
||||
className="size-4 shrink-0 text-tint theme-bold:text-header-link/8"
|
||||
className="size-text-lg shrink-0 text-tint theme-bold:text-header-link/8"
|
||||
/>
|
||||
}
|
||||
onChange={(event) => {
|
||||
@@ -78,8 +78,7 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={value && isOpen ? 'true' : 'false'}
|
||||
clearButton={{
|
||||
className:
|
||||
'theme-bold:text-header-link theme-bold:hover:bg-header-link/3 text-base',
|
||||
className: 'theme-bold:text-header-link theme-bold:hover:bg-header-link/3',
|
||||
}}
|
||||
keyboardShortcut={{
|
||||
className:
|
||||
|
||||
@@ -63,9 +63,15 @@ export const SearchResultItem = React.forwardRef(function SearchResultItem(
|
||||
</div>
|
||||
<div className="grow">{children}</div>
|
||||
{active ? (
|
||||
<Button label={action} iconOnly variant="primary" icon="arrow-turn-down-left" />
|
||||
<Button
|
||||
label={action}
|
||||
iconOnly
|
||||
variant="primary"
|
||||
size="small"
|
||||
icon="arrow-turn-down-left"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex size-8 shrink-0 items-center justify-center text-tint-subtle">
|
||||
<div className="flex size-9 shrink-0 items-center justify-center text-tint-subtle">
|
||||
<Icon icon="chevron-right" className="size-3" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { t, tString, useLanguage } from '@/intl/client';
|
||||
import type { SiteSection } from '@gitbook/api';
|
||||
import { Button, DropdownChevron, DropdownMenu, DropdownMenuItem } from '../primitives';
|
||||
import { Button, DropdownMenu, DropdownMenuItem, ToggleChevron } from '../primitives';
|
||||
import { useSearch } from './useSearch';
|
||||
|
||||
interface SearchScopeControlProps {
|
||||
@@ -56,21 +56,16 @@ function SearchScopeSectionControl(props: SearchScopeControlProps & { isExtended
|
||||
button={
|
||||
<Button
|
||||
variant="blank"
|
||||
size="medium"
|
||||
className="px-2 text-tint-strong"
|
||||
size="small"
|
||||
className="text-tint-strong"
|
||||
icon={isExtended ? undefined : section?.icon}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{t(
|
||||
language,
|
||||
isExtended
|
||||
? 'search_scope_section_all'
|
||||
: 'search_scope_section_current',
|
||||
section?.title ?? ''
|
||||
)}
|
||||
<DropdownChevron />
|
||||
</div>
|
||||
</Button>
|
||||
label={tString(
|
||||
language,
|
||||
isExtended ? 'search_scope_section_all' : 'search_scope_section_current',
|
||||
section?.title ?? ''
|
||||
)}
|
||||
trailing={<ToggleChevron />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
@@ -120,20 +115,16 @@ function SearchScopeVariantControl(props: SearchScopeControlProps & { isExtended
|
||||
return (
|
||||
<DropdownMenu
|
||||
button={
|
||||
<Button variant="blank" size="medium" className="px-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-tint-strong">
|
||||
{t(
|
||||
language,
|
||||
isExtended
|
||||
? 'search_scope_variant_all'
|
||||
: 'search_scope_variant_current',
|
||||
spaceTitle ?? ''
|
||||
)}
|
||||
</span>
|
||||
<DropdownChevron />
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
variant="blank"
|
||||
size="small"
|
||||
label={tString(
|
||||
language,
|
||||
isExtended ? 'search_scope_variant_all' : 'search_scope_variant_current',
|
||||
spaceTitle ?? ''
|
||||
)}
|
||||
trailing={<ToggleChevron />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import type { IconName } from '@gitbook/icons';
|
||||
import { motion } from 'motion/react';
|
||||
import React from 'react';
|
||||
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
import { findSectionInGroup } from '@/lib/utils';
|
||||
import { useToggleAnimation } from '../hooks';
|
||||
import { Link } from '../primitives';
|
||||
import { Link, ToggleChevron } from '../primitives';
|
||||
import { ScrollContainer } from '../primitives/ScrollContainer';
|
||||
import { SectionIcon } from './SectionIcon';
|
||||
import type {
|
||||
@@ -199,21 +199,18 @@ export function SiteSectionGroupItem(props: {
|
||||
isActiveGroup && 'hover:bg-tint-hover'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
<ToggleChevron
|
||||
open={isOpen}
|
||||
orientation="right-to-down"
|
||||
className={tcls(
|
||||
'grid',
|
||||
'shrink-0',
|
||||
'size-3',
|
||||
'm-1',
|
||||
'transition-opacity',
|
||||
'text-current',
|
||||
'transition-transform',
|
||||
'opacity-6',
|
||||
'group-hover:opacity-11',
|
||||
'contrast-more:opacity-11',
|
||||
|
||||
isOpen ? 'rotate-90' : 'rotate-0'
|
||||
'contrast-more:opacity-11'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IconName } from '@gitbook/icons';
|
||||
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
|
||||
import React from 'react';
|
||||
|
||||
import { Button, DropdownChevron, Link } from '@/components/primitives';
|
||||
import { Button, Link, ToggleChevron } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { findSectionInGroup } from '@/lib/utils';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
@@ -187,17 +187,17 @@ const SectionTab = React.forwardRef(function SectionTab(
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
size="medium"
|
||||
size="small"
|
||||
variant="blank"
|
||||
{...rest}
|
||||
icon={icon ? <SectionIcon isActive={isActive} icon={icon} /> : null}
|
||||
label={title}
|
||||
trailing={isGroup ? <DropdownChevron /> : null}
|
||||
trailing={isGroup ? <ToggleChevron /> : null}
|
||||
active={isActive}
|
||||
className={tcls(
|
||||
'group/dropdown relative my-2 overflow-visible px-3 py-1',
|
||||
'group/dropdown relative my-1.5 overflow-visible',
|
||||
isActive
|
||||
? 'after:contents-[] after:-bottom-2 bg-transparent text-primary-subtle after:absolute after:inset-x-3 after:h-0.5 after:bg-primary-9'
|
||||
? 'after:contents-[] after:-bottom-1.5 bg-transparent text-primary-subtle after:absolute after:inset-x-3 after:h-0.5 after:bg-primary-9'
|
||||
: ''
|
||||
)}
|
||||
href={url}
|
||||
|
||||
@@ -39,14 +39,8 @@ export function PageDocumentItem(props: { page: ClientTOCPageDocument }) {
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{page.emoji || page.icon ? (
|
||||
<span className="flex items-center gap-3">
|
||||
<TOCPageIcon page={page} />
|
||||
{page.title}
|
||||
</span>
|
||||
) : (
|
||||
page.title
|
||||
)}
|
||||
<TOCPageIcon page={page} />
|
||||
{page.title}
|
||||
</ToggleableLinkItem>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -12,11 +12,13 @@ import { TOCPageIcon } from './TOCPageIcon';
|
||||
export function PageLinkItem(props: { page: ClientTOCPageLink }) {
|
||||
const { page } = props;
|
||||
|
||||
const isExternal = page.target.kind === 'url';
|
||||
|
||||
return (
|
||||
<li className={tcls('flex', 'flex-col')}>
|
||||
<Link
|
||||
href={page.href ?? '#'}
|
||||
classNames={['PageLinkItemStyles']}
|
||||
classNames={['ToggleableLinkItemStyles']}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
link: {
|
||||
@@ -27,21 +29,24 @@ export function PageLinkItem(props: { page: ClientTOCPageLink }) {
|
||||
>
|
||||
<TOCPageIcon page={page} />
|
||||
{page.title}
|
||||
<Icon
|
||||
icon="arrow-up-right-from-square"
|
||||
className={tcls(
|
||||
'size-3',
|
||||
'mr-1',
|
||||
'mt-1',
|
||||
'place-self-start',
|
||||
'shrink-0',
|
||||
'text-current',
|
||||
'transition-colors',
|
||||
'[&>path]:transition-opacity',
|
||||
'[&>path]:opacity-[0.4]',
|
||||
'group-hover:[&>path]:opacity-11'
|
||||
)}
|
||||
/>
|
||||
{isExternal ? (
|
||||
<Icon
|
||||
icon="arrow-up-right"
|
||||
className={tcls(
|
||||
'size-3',
|
||||
'ml-auto',
|
||||
'mr-1',
|
||||
'mt-1',
|
||||
'place-self-start',
|
||||
'shrink-0',
|
||||
'text-current',
|
||||
'transition-all',
|
||||
'opacity-6',
|
||||
'group-hover/toclink:opacity-11',
|
||||
'contrast-more:opacity-11'
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -19,17 +19,12 @@ export function TOCPageIcon({ page }: { page: Pick<RevisionPage, 'emoji' | 'icon
|
||||
|
||||
!page.emoji
|
||||
? [
|
||||
'theme-gradient:bg-linear-to-b',
|
||||
'theme-bold:bg-linear-to-b',
|
||||
'theme-gradient:bg-fixed',
|
||||
'theme-bold:bg-fixed',
|
||||
'theme-bold-no-tint:text-primary-subtle',
|
||||
'theme-gradient-no-tint:text-primary-subtle',
|
||||
'theme-bold-tint:text-tint-subtle',
|
||||
'theme-gradient-tint:text-tint-subtle',
|
||||
|
||||
'no-tint:from-primary-7',
|
||||
'no-tint:to-primary-10',
|
||||
'tint:from-tint-7',
|
||||
'tint:to-tint-10',
|
||||
|
||||
'group-aria-current-page/toclink:bg-none!',
|
||||
'group-aria-current-page/toclink:text-inherit!',
|
||||
]
|
||||
: '',
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@ export async function TableOfContents(props: {
|
||||
context: GitBookSiteContext;
|
||||
header?: React.ReactNode; // Displayed outside the scrollable TOC as a sticky header
|
||||
innerHeader?: React.ReactNode; // Displayed outside the scrollable TOC, directly above the page list
|
||||
withTrademark?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const { innerHeader, context, header, className } = props;
|
||||
const { innerHeader, context, header, className, withTrademark = true } = props;
|
||||
const { customization, revision } = context;
|
||||
|
||||
const pages = await encodeClientTableOfContents(context, revision.pages, revision.pages);
|
||||
@@ -31,7 +32,7 @@ export async function TableOfContents(props: {
|
||||
data-testid="table-of-contents"
|
||||
id="table-of-contents"
|
||||
toggleClass="navigation-open"
|
||||
withScrim={true}
|
||||
withOverlay={true}
|
||||
withCloseButton={true}
|
||||
className={tcls(
|
||||
'group/table-of-contents',
|
||||
@@ -40,13 +41,11 @@ export async function TableOfContents(props: {
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
|
||||
'w-72',
|
||||
'lg:max-xl:w-56',
|
||||
'lg:page-no-toc:w-56',
|
||||
'4xl:page-no-toc:w-72',
|
||||
'lg:page-no-toc:fixed',
|
||||
'lg:page-no-toc:left-8',
|
||||
'lg:page-no-toc:ml-[max(0px,calc((100vw-96rem)/2-20rem))]',
|
||||
'w-4/5',
|
||||
'md:w-1/2',
|
||||
'lg:w-72',
|
||||
'basis-72',
|
||||
'lg:page-no-toc:basis-56',
|
||||
|
||||
'max-lg:not-sidebar-filled:bg-tint-base',
|
||||
'max-lg:not-sidebar-filled:border-r',
|
||||
@@ -56,8 +55,7 @@ export async function TableOfContents(props: {
|
||||
'lg:animate-none!',
|
||||
'lg:sticky',
|
||||
'lg:mr-12',
|
||||
// 'lg:z-0!',
|
||||
'lg:z-20',
|
||||
'lg:z-0!',
|
||||
|
||||
// Server-side static positioning
|
||||
'lg:top-0',
|
||||
@@ -80,18 +78,17 @@ export async function TableOfContents(props: {
|
||||
'lg:page-no-toc:[html[style*="--outline-height"]_&]:top-(--outline-height)!',
|
||||
|
||||
'pt-6 pb-4',
|
||||
'supports-[-webkit-touch-callout]:pb-[env(safe-area-inset-bottom)]', // Override bottom padding on iOS since we have a transparent bottom bar
|
||||
'lg:sidebar-filled:pr-6',
|
||||
'lg:page-no-toc:pr-0',
|
||||
'max-lg:pl-8',
|
||||
|
||||
'hidden',
|
||||
'navigation-open:flex!',
|
||||
'lg:flex',
|
||||
'flex-col',
|
||||
'gap-4',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{header ? header : null}
|
||||
{header}
|
||||
<div // The actual sidebar, either shown with a filled bg or transparent.
|
||||
className={tcls(
|
||||
'-ms-5',
|
||||
@@ -107,12 +104,12 @@ export async function TableOfContents(props: {
|
||||
'page-no-toc:bg-transparent!',
|
||||
'page-no-toc:border-none!',
|
||||
|
||||
'sidebar-filled:rounded-xl',
|
||||
'sidebar-filled:rounded-2xl',
|
||||
'straight-corners:rounded-none',
|
||||
'page-has-toc:[html.sidebar-filled.circular-corners_&]:rounded-3xl'
|
||||
'page-has-toc:[html.sidebar-filled.circular-corners_&]:rounded-4xl'
|
||||
)}
|
||||
>
|
||||
{innerHeader ? innerHeader : null}
|
||||
{innerHeader}
|
||||
<ScrollContainer
|
||||
data-testid="toc-scroll-container"
|
||||
orientation="vertical"
|
||||
@@ -131,10 +128,11 @@ export async function TableOfContents(props: {
|
||||
style="page-no-toc:hidden grow border-tint-subtle sidebar-list-line:border-l"
|
||||
/>
|
||||
</ScrollContainer>
|
||||
{customization.trademark.enabled ? (
|
||||
{withTrademark && customization.trademark.enabled ? (
|
||||
<Trademark
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Sidebar}
|
||||
className="m-2 mt-auto sidebar-default:mr-4"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user