mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-14 06:35:17 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1424f431a9 | |||
| c2401e9a78 | |||
| 57625cd365 | |||
| 0f330d5e00 | |||
| 91b9c7955b | |||
| fc2c708ba8 |
@@ -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": patch
|
||||
---
|
||||
|
||||
Add sidesheet component, use it for TOC and AIChat
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Enhance discriminator handling in OpenAPISchema
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix overflowing section groups
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix search results cursor
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@gitbook/icons": minor
|
||||
---
|
||||
|
||||
Update to FontAwesome 7.1.0
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add customizable suggested questions
|
||||
@@ -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
|
||||
---
|
||||
|
||||
Fix CORS error when using embed script.js directly
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "gitbook",
|
||||
@@ -126,7 +125,7 @@
|
||||
"@gitbook/react-openapi": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "1.17.5",
|
||||
"@opennextjs/aws": "^3.8.5",
|
||||
"@opennextjs/cloudflare": "^1.14.4",
|
||||
"@opennextjs/cloudflare": "^1.11.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
@@ -157,7 +156,7 @@
|
||||
"micromark-extension-frontmatter": "^2.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"motion": "^12.23.24",
|
||||
"next": "15.4.10",
|
||||
"next": "15.4.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"nuqs": "^2.2.3",
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -223,8 +222,8 @@
|
||||
"gitbook-icons": "./bin/gitbook-icons.js",
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "catalog:",
|
||||
@@ -234,7 +233,7 @@
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@gitbook/fontawesome-pro": "1.0.16",
|
||||
"@gitbook/fontawesome-pro": "1.0.15",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@@ -346,7 +345,7 @@
|
||||
"react-dom": "catalog:",
|
||||
},
|
||||
"catalog": {
|
||||
"@gitbook/api": "0.154.0",
|
||||
"@gitbook/api": "0.153.0",
|
||||
"@scalar/api-client-react": "^1.3.46",
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@tsconfig/strictest": "^2.0.6",
|
||||
@@ -356,8 +355,8 @@
|
||||
"bidc": "^0.0.2",
|
||||
"bun-types": "^1.1.20",
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tsdown": "^0.15.6",
|
||||
"typescript": "^5.5.3",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
@@ -385,25 +384,25 @@
|
||||
|
||||
"@argos-ci/util": ["@argos-ci/util@2.3.2", "", {}, "sha512-xtNHJxpWYNst/sMNn4Jv/vkODuFsJ+APr4FBeoFUdIa+Izjl4ZFHsYA2PUyu+ygIpQCkof8yZLL9U1/VpiyyIw=="],
|
||||
|
||||
"@ast-grep/napi": ["@ast-grep/napi@0.40.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.40.0", "@ast-grep/napi-darwin-x64": "0.40.0", "@ast-grep/napi-linux-arm64-gnu": "0.40.0", "@ast-grep/napi-linux-arm64-musl": "0.40.0", "@ast-grep/napi-linux-x64-gnu": "0.40.0", "@ast-grep/napi-linux-x64-musl": "0.40.0", "@ast-grep/napi-win32-arm64-msvc": "0.40.0", "@ast-grep/napi-win32-ia32-msvc": "0.40.0", "@ast-grep/napi-win32-x64-msvc": "0.40.0" } }, "sha512-tq6nO/8KwUF/mHuk1ECaAOSOlz2OB/PmygnvprJzyAHGRVzdcffblaOOWe90M9sGz5MAasXoF+PTcayQj9TKKA=="],
|
||||
"@ast-grep/napi": ["@ast-grep/napi@0.35.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.35.0", "@ast-grep/napi-darwin-x64": "0.35.0", "@ast-grep/napi-linux-arm64-gnu": "0.35.0", "@ast-grep/napi-linux-arm64-musl": "0.35.0", "@ast-grep/napi-linux-x64-gnu": "0.35.0", "@ast-grep/napi-linux-x64-musl": "0.35.0", "@ast-grep/napi-win32-arm64-msvc": "0.35.0", "@ast-grep/napi-win32-ia32-msvc": "0.35.0", "@ast-grep/napi-win32-x64-msvc": "0.35.0" } }, "sha512-3ucaaSxV6fxXoqHrE/rxAvP1THnDdY5jNzGlnvx+JvnY9C/dSRKc0jlRMRz59N3El572+/yNRUUpAV1T9aBJug=="],
|
||||
|
||||
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZMjl5yLhKjxdwbqEEdMizgQdWH2NrWsM6Px+JuGErgCDe6Aedq9yurEPV7veybGdLVJQhOah6htlSflXxjHnYA=="],
|
||||
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.35.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-T+MN4Oinc+sXjXCIHzfxDDWY7r2pKgPxM6zVeVlkMTrJV2mJtyKYBIS+CABhRM6kflps2T2I6l4DGaKV/8Ym9w=="],
|
||||
|
||||
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-f9Ol5oQKNRMBkvDtzBK1WiNn2/3eejF2Pn9xwTj7PhXuSFseedOspPYllxQo0gbwUlw/DJqGFTce/jarhR/rBw=="],
|
||||
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.35.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pEYiN6JI1HY2uWhMYJ9+3yIMyVYKuYdFzeD+dL7odA3qzK0o9N9AM3/NOt4ynU2EhufaWCJr0P5NoQ636qN6MQ=="],
|
||||
|
||||
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+tO+VW5GDhT9jGkKOK+3b8+ohKjC98WTzn7wSskd/myyhK3oYL1WTKqCm07WSYBZOJvb3z+WaX+wOUrc4bvtyQ=="],
|
||||
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NBuzQngABGKz7lhG08IQb+7nPqUx81Ol37xmS3ZhVSdSgM0mtp93rCbgFTkJcAFE8IMfCHQSg7G4g0Iotz4ABQ=="],
|
||||
|
||||
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-MS9qalLRjUnF2PCzuTKTvCMVSORYHxxe3Qa0+SSaVULsXRBmuy5C/b1FeWwMFnwNnC0uie3VDet31Zujwi8q6A=="],
|
||||
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.35.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EcvHPwyWpCL/96LuItBYGfeI5FaMTRvL+dHbO/hL5q1npqbb5qn+ppJwtNOjTPz8tayvgggxVk9T4C2O7taYA=="],
|
||||
|
||||
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BeHZVMNXhM3WV3XE2yghO0fRxhMOt8BTN972p5piYEQUvKeSHmS8oeGcs6Ahgx5znBclqqqq37ZfioYANiTqJA=="],
|
||||
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FDzNdlqmQnsiWXhnLxusw5AOfEcEM+5xtmrnAf3SBRFr86JyWD9qsynnFYC2pnP9hlMfifNH2TTmMpyGJW49Xw=="],
|
||||
|
||||
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rG1YujF7O+lszX8fd5u6qkFTuv4FwHXjWvt1CCvCxXwQLSY96LaCW88oVKg7WoEYQh54y++Fk57F+Wh9Gv9nVQ=="],
|
||||
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.35.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wlmndjfBafT8u5p4DBnoRQyoCSGNuVSz7rT3TqhvlHcPzUouRWMn95epU9B1LNLyjXvr9xHeRjSktyCN28w57Q=="],
|
||||
|
||||
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-9SqmnQqd4zTEUk6yx0TuW2ycZZs2+e569O/R0QnhSiQNpgwiJCYOe/yPS0BC9HkiaozQm6jjAcasWpFtz/dp+w=="],
|
||||
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.35.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-gkhJeYc4rrZLX2icLxalPikTLMR57DuIYLwLr9g+StHYXIsGHrbfrE6Nnbdd8Izfs34ArFCrcwdaMrGlvOPSeg=="],
|
||||
|
||||
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-0JkdBZi5l9vZhGEO38A1way0LmLRDU5Vos6MXrLIOVkymmzDTDlCdY394J1LMmmsfwWcyJg6J7Yv2dw41MCxDQ=="],
|
||||
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.35.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OdUuRa3chHCZ65y+qALfkUjz0W0Eg21YZ9TyPquV5why07M6HAK38mmYGzLxFH6294SvRQhs+FA/rAfbKeH0jA=="],
|
||||
|
||||
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Hk2IwfPqMFGZt5SRxsoWmGLxBXxprow4LRp1eG6V8EEiJCNHxZ9ZiEaIc5bNvMDBjHVSnqZAXT22dROhrcSKQg=="],
|
||||
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.35.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pcQRUHqbroTN1oQ56V982a7IZTUUySQYWa2KEyksiifHGuBuitlzcyzFGjT96ThcqD9XW0UVJMvpoF2Qjh006Q=="],
|
||||
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
@@ -721,13 +720,13 @@
|
||||
|
||||
"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="],
|
||||
|
||||
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="],
|
||||
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.7.2", "", {}, "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg=="],
|
||||
|
||||
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@7.1.0", "", {}, "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA=="],
|
||||
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@6.6.0", "", {}, "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow=="],
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA=="],
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
|
||||
|
||||
"@gitbook/api": ["@gitbook/api@0.154.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-huEgWWa2H+H4ofYEAQl9r49u0gVjDHHWVNJKhd6SgGCfefjNPHkQj9zaky/5DBQzlu/+SDmutWiOtYdrykQO8w=="],
|
||||
"@gitbook/api": ["@gitbook/api@0.153.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-ArNPFoqwId4Flicz8xPEdGqXQkzxyxP7S8Uv3wIfCX2e4cLhpcS7xCJGEHOJrqe1tNs1ovT1N2MWfpdIqzXqig=="],
|
||||
|
||||
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
|
||||
|
||||
@@ -741,7 +740,7 @@
|
||||
|
||||
"@gitbook/expr": ["@gitbook/expr@workspace:packages/expr"],
|
||||
|
||||
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.16", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^7.1.0" } }, "sha512-jypE3FpC5U7lealzpGVq1Jie5Ay5jYv/UkOLxPhOpffjCd98A+Ex08M6GyINMDO7KCBaF5p8pbdl693hf0DwNg=="],
|
||||
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.15", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.7.2" } }, "sha512-iiXDkwzvaRrRF853Vwjt8kWrd6yRJFb7BfhFyBNYvVmjVmxRfrvmstiOPUIdHS7jPaAWCWLzirhokwh8BFBTHw=="],
|
||||
|
||||
"@gitbook/fonts": ["@gitbook/fonts@workspace:packages/fonts"],
|
||||
|
||||
@@ -823,10 +822,6 @@
|
||||
|
||||
"@internationalized/string": ["@internationalized/string@3.2.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
@@ -873,23 +868,23 @@
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="],
|
||||
|
||||
"@next/env": ["@next/env@15.4.10", "", {}, "sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg=="],
|
||||
"@next/env": ["@next/env@15.4.0-canary.57", "", {}, "sha512-gDNgJA7YnuUWQ2Ei6oeMiuSSWWICyYGK0AbWQnOfub1LIyYPNXMSyvyLTkGwVyIixbLMUFdzjTgU+ZxYHil7Mg=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tUq3IBxdu1I+CfIQ7fOVHb5L0Ehu9lXmA3bcVVMDswnKnmL3V15LInWkFbQ/kuIDv87AT+UfJB4us+yJ1lNr/A=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-xla6AOfz68a6kq3gRQccWEvFC/VRGJmA/QuSLENSO7CZX5WIEkSz7r1FdXUjtGCQ1c2M+ndUAH7opdfLK1PQbw=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-zsFozes7iXAkfQYQGYi9xiDKrS7mjLCMl9DIZfm0cEmilYmJrO16rn04H4Y+NJZ5kBxJUXn+hAy4kXmToZYFHA=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-y3fmp+1Px/SJD+5ntve5QLZnGLycsxsVPkTzAc3zUiXYSOlTPqT8ynfmt6tt4fSo1tAhDPmryXpYKEAcoAPDJw=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-z5S7BWDhBp+VJUzDEVpDasJC9EebZ1Jw8R25ryEH+ojLl14kMuoDtULOdin3f4zXBEwPvJrkt1G+Aes+TSX4iQ=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-DX/L8VHzrr1CfwaVjBQr3GWCqNNFgyWJbeQ10Lx/phzbQo3JNAxUok1DZ8JHRGcL6PgMRgj6HylnLNndxn4Z6A=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-VqN0D+CdUxMSO+a1EaIEJajMKlHbsSIObd1zYiNJ3d5vWO8eg9K/f+AtBm6F4zaRlSLTURKLoZ+f6aGIbzhXFA=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-9fLAAXKAL3xEIFdKdzG5rUSvSiZTLLTCc6JKq1z04DR4zY7DbAPcRvNm3K1inVhTiQCs19ZRAgUerHiVKMZZIA=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iWQKV+kCeg0iSQR0tYm8I5K0w7YJnvy3HsdIlYHAhHYiqFEr/3JkKL3p1KduxRiaMj3dCv5ZuyZuCGV1le89ew=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-s45V7nfb5g7dbS7JK6XZDcapicVrMMvX2uYgOHP16QuKH/JA285oy6HcxlKqwUNaFY/UC6EvQ8QZUOo19cBKSA=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pm9t8LhfpNLtU5OjMny7OBjATRn3tiP+Kw4FQz/27XhgTmiwNbcqSAV4w+8TJ34e/X61xjj6vY32fsofxsQU5A=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-KjgeQyOAq7t/HzAJcWPGA8X+4WY03uSCZ2Ekk98S9OgCFsb6lfBE3dbUzUuEQAN2THbwYgFfxX2yFTCMm8Kehw=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yvx5Rj9Tb370IERMvHo9+gAxLQI6OIwmin0kELI8fvGYflaRz/cF1H2sk+a2y7WH3LY7RnKVRxoRLLVsch2WXQ=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-IldilrY7yfSlqOpTha7I7rYDDkyCC/KIMCjo2Nf0WqTLw3B5ha4islIF07YjNRbZS1Xl5WMqo9zZtx7VAE4epg=="],
|
||||
|
||||
"@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="],
|
||||
|
||||
@@ -925,9 +920,9 @@
|
||||
|
||||
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
||||
|
||||
"@opennextjs/aws": ["@opennextjs/aws@3.9.4", "", { "dependencies": { "@ast-grep/napi": "^0.40.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.3", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "^5.1.0", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.8.1" }, "peerDependencies": { "next": "< 14.2 || ^14.2 || 14.3.0-canary.0 - 14.3.0-canary.76 || ~15.0.5 || ~15.1.9 || ~15.2.6 || ~15.3.6 || ~15.4.8 || ~15.5.7 || ^16.0.7" }, "bin": { "open-next": "dist/index.js" } }, "sha512-i5gWKSP7QQGz59z85ZjOGtIfXK1ZgRLEwbt7/dkmuujhTuMBWygQqrb2EGRf8vRZH1Xvp0fYENUZx7NjgvxsIg=="],
|
||||
"@opennextjs/aws": ["@opennextjs/aws@3.8.5", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-elpMb0fJZc0a1VtymedFa7P1lYcyOmt+Pwqyacpq2C/SvbETIeBlW/Xle/vY95ICtccKUxITI8MtonrCo2+2/Q=="],
|
||||
|
||||
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.14.4", "", { "dependencies": { "@ast-grep/napi": "0.40.0", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.9.4", "cloudflare": "^4.4.1", "enquirer": "^2.4.1", "glob": "^12.0.0", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "next": "14 - 14.2 || 14.3.0-canary.0 - 14.3.0-canary.76 || ~15.0.5 || ~15.1.9 || ~15.2.6 || ~15.3.6 || ~15.4.8 || ~15.5.7 || ^16.0.7", "wrangler": "^4.49.1" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-2dODhjsRsZog1VHxX9QwUkxq76aBwaEl+L67m8c47V6zZpIgAFODHMxtj5NPM3uz6pUiT4edYGNS5bR99ck3Jg=="],
|
||||
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.11.0", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.8.5", "@types/rclone.js": "^0.6.3", "cloudflare": "^4.4.1", "enquirer": "^2.4.1", "glob": "^11.0.0", "rclone.js": "^0.6.6", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "wrangler": "^4.38.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-2QVZLjCr3hPwkcGEetLarTZvlNIHsGV3j2+i+KNsjFIBgvsnxhmRqJbT0u8J9cVMFKNh19hSOTOmUKs1T3DWig=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
@@ -1543,6 +1538,8 @@
|
||||
|
||||
"@types/parse-cache-control": ["@types/parse-cache-control@1.0.4", "", {}, "sha512-YA9hcvRn3oYBdLK0PdWRsw5ehrqmfdCwfz68sSAxi2OB6L7B3jJEhrqlLfMvxnWfsZcUdbR6QI6NfkY1Y4e2zg=="],
|
||||
|
||||
"@types/rclone.js": ["@types/rclone.js@0.6.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-BssKAAVRY//fxGKso8SatyOwiD7X0toDofNnVxZlIXmN7UHrn2UBTxldNAjgUvWA91qJyeEPfKmeJpZVhLugXg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
|
||||
@@ -1637,6 +1634,8 @@
|
||||
|
||||
"acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
|
||||
|
||||
"adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
|
||||
|
||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||
@@ -1731,10 +1730,6 @@
|
||||
|
||||
"call-bind": ["call-bind@1.0.7", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.1" } }, "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001668", "", {}, "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw=="],
|
||||
@@ -1883,8 +1878,6 @@
|
||||
|
||||
"dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
@@ -1929,8 +1922,6 @@
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.4.1", "", {}, "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
|
||||
|
||||
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
|
||||
@@ -2033,7 +2024,7 @@
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
||||
|
||||
"form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
|
||||
|
||||
@@ -2073,8 +2064,6 @@
|
||||
|
||||
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
@@ -2083,7 +2072,7 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="],
|
||||
"glob": ["glob@11.0.1", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
@@ -2235,7 +2224,7 @@
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
|
||||
"jackspeak": ["jackspeak@4.0.2", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw=="],
|
||||
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
@@ -2365,8 +2354,6 @@
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.3", "", {}, "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mathjax": ["mathjax@3.2.2", "", {}, "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw=="],
|
||||
|
||||
"mathml-tag-names": ["mathml-tag-names@2.1.3", "", {}, "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg=="],
|
||||
@@ -2489,7 +2476,7 @@
|
||||
|
||||
"miniflare": ["miniflare@4.20251008.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251008.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-sKCNYNzXG6l8qg0Oo7y8WcDKcpbgw0qwZsxNpdZilFTR4EavRow2TlcwuPSVN99jqAjhz0M4VXvTdSGdtJ2VfQ=="],
|
||||
|
||||
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
||||
"minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
@@ -2517,7 +2504,7 @@
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"next": ["next@15.4.10", "", { "dependencies": { "@next/env": "15.4.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ=="],
|
||||
"next": ["next@15.4.0", "", { "dependencies": { "@next/env": "15.4.0-canary.57", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.0", "@next/swc-darwin-x64": "15.4.0", "@next/swc-linux-arm64-gnu": "15.4.0", "@next/swc-linux-arm64-musl": "15.4.0", "@next/swc-linux-x64-gnu": "15.4.0", "@next/swc-linux-x64-musl": "15.4.0", "@next/swc-win32-arm64-msvc": "15.4.0", "@next/swc-win32-x64-msvc": "15.4.0", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-9JfEyg23carbb/AxUylRQh4zVN4dt1af9aJ+L3CFl/LKQIQkWUWN44YhB9M5JhxMjleF6rkfNOD7IqDcsb6zyQ=="],
|
||||
|
||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||
|
||||
@@ -2675,6 +2662,8 @@
|
||||
|
||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||
|
||||
"rclone.js": ["rclone.js@0.6.6", "", { "dependencies": { "adm-zip": "^0.5.9", "mri": "^1.2.0" }, "os": [ "!aix", "!android", ], "cpu": [ "!ppc", "!ia32", "!s390", "!ppc64", "!s390x", ], "bin": { "rclone": "bin/rclone.js" } }, "sha512-Dxh34cab/fNjFq5SSm0fYLNkGzG2cQSBy782UW9WwxJCEiVO4cGXkvaXcNlgv817dK8K8PuQ+NHUqSAMMhWujQ=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
|
||||
"react-aria": ["react-aria@3.44.0", "", { "dependencies": { "@internationalized/string": "^3.2.7", "@react-aria/breadcrumbs": "^3.5.29", "@react-aria/button": "^3.14.2", "@react-aria/calendar": "^3.9.2", "@react-aria/checkbox": "^3.16.2", "@react-aria/color": "^3.1.2", "@react-aria/combobox": "^3.14.0", "@react-aria/datepicker": "^3.15.2", "@react-aria/dialog": "^3.5.31", "@react-aria/disclosure": "^3.1.0", "@react-aria/dnd": "^3.11.3", "@react-aria/focus": "^3.21.2", "@react-aria/gridlist": "^3.14.1", "@react-aria/i18n": "^3.12.13", "@react-aria/interactions": "^3.25.6", "@react-aria/label": "^3.7.22", "@react-aria/landmark": "^3.0.7", "@react-aria/link": "^3.8.6", "@react-aria/listbox": "^3.15.0", "@react-aria/menu": "^3.19.3", "@react-aria/meter": "^3.4.27", "@react-aria/numberfield": "^3.12.2", "@react-aria/overlays": "^3.30.0", "@react-aria/progress": "^3.4.27", "@react-aria/radio": "^3.12.2", "@react-aria/searchfield": "^3.8.9", "@react-aria/select": "^3.17.0", "@react-aria/selection": "^3.26.0", "@react-aria/separator": "^3.4.13", "@react-aria/slider": "^3.8.2", "@react-aria/ssr": "^3.9.10", "@react-aria/switch": "^3.7.8", "@react-aria/table": "^3.17.8", "@react-aria/tabs": "^3.10.8", "@react-aria/tag": "^3.7.2", "@react-aria/textfield": "^3.18.2", "@react-aria/toast": "^3.0.8", "@react-aria/tooltip": "^3.8.8", "@react-aria/tree": "^3.1.4", "@react-aria/utils": "^3.31.0", "@react-aria/visually-hidden": "^3.8.28", "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-2Pq3GQxBgM4/2BlpKYXeaZ47a3tdIcYSW/AYvKgypE3XipxOdQMDG5Sr/NBn7zuJq+thzmtfRb0lB9bTbsmaRw=="],
|
||||
@@ -2785,12 +2774,6 @@
|
||||
|
||||
"side-channel": ["side-channel@1.0.6", "", { "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" } }, "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
@@ -2977,7 +2960,7 @@
|
||||
|
||||
"url-join": ["url-join@5.0.0", "", {}, "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="],
|
||||
|
||||
"urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
||||
"urlpattern-polyfill": ["urlpattern-polyfill@10.0.0", "", {}, "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg=="],
|
||||
|
||||
"use-callback-ref": ["use-callback-ref@1.3.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA=="],
|
||||
|
||||
@@ -3663,6 +3646,8 @@
|
||||
|
||||
"@floating-ui/vue/@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
|
||||
|
||||
"@fortawesome/fontawesome-svg-core/@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.6.0", "", {}, "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="],
|
||||
|
||||
"@hyperjump/json-schema/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
@@ -3715,12 +3700,6 @@
|
||||
|
||||
"@node-minify/core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"@opennextjs/aws/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -4029,6 +4008,8 @@
|
||||
|
||||
"@types/jsonwebtoken/@types/node": ["@types/node@20.16.11", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw=="],
|
||||
|
||||
"@types/rclone.js/@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="],
|
||||
|
||||
"@types/swagger2openapi/@types/node": ["@types/node@20.16.11", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw=="],
|
||||
|
||||
"@types/ws/@types/node": ["@types/node@20.16.11", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw=="],
|
||||
@@ -4089,8 +4070,6 @@
|
||||
|
||||
"bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
|
||||
|
||||
"call-bound/get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
@@ -4113,8 +4092,6 @@
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"dunder-proto/gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"edge-runtime/async-listen": ["async-listen@3.0.1", "", {}, "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA=="],
|
||||
|
||||
"edge-runtime/picocolors": ["picocolors@1.0.0", "", {}, "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="],
|
||||
@@ -4137,8 +4114,6 @@
|
||||
|
||||
"finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
|
||||
|
||||
"foreground-child/cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"fs-minipass/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
|
||||
@@ -4257,16 +4232,6 @@
|
||||
|
||||
"send/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"side-channel-list/object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"side-channel-map/get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"side-channel-map/object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"side-channel-weakmap/get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"side-channel-weakmap/object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"spawndamnit/cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
@@ -4811,20 +4776,6 @@
|
||||
|
||||
"@node-minify/core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@opennextjs/aws/express/body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
|
||||
|
||||
"@opennextjs/aws/express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
|
||||
|
||||
"@opennextjs/aws/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"@opennextjs/aws/express/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"@opennextjs/aws/express/router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"@opennextjs/aws/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||
|
||||
"@opennextjs/aws/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
@@ -5027,12 +4978,6 @@
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"call-bound/get-intrinsic/es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"call-bound/get-intrinsic/gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"call-bound/get-intrinsic/has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"cliui/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
@@ -5121,8 +5066,6 @@
|
||||
|
||||
"remark-gfm/mdast-util-gfm/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
|
||||
"rimraf/glob/foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
||||
|
||||
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
@@ -5131,18 +5074,6 @@
|
||||
|
||||
"send/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"side-channel-map/get-intrinsic/es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"side-channel-map/get-intrinsic/gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"side-channel-map/get-intrinsic/has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"side-channel-weakmap/get-intrinsic/es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"side-channel-weakmap/get-intrinsic/gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"side-channel-weakmap/get-intrinsic/has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"stylelint/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||
@@ -5293,16 +5224,6 @@
|
||||
|
||||
"@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"@opennextjs/aws/express/body-parser/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"@opennextjs/aws/express/qs/side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"@opennextjs/aws/express/router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"@opennextjs/aws/express/router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||
|
||||
"@opennextjs/aws/express/serve-static/send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
|
||||
|
||||
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
@@ -5389,12 +5310,6 @@
|
||||
|
||||
"@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"@opennextjs/aws/express/body-parser/raw-body/http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"@opennextjs/aws/express/qs/side-channel/object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"@opennextjs/aws/express/serve-static/send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"@scalar/draggable/vue/@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||
|
||||
"@scalar/draggable/vue/@vue/compiler-sfc/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
@@ -5420,9 +5335,5 @@
|
||||
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
|
||||
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||
|
||||
"@opennextjs/aws/express/body-parser/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"@opennextjs/aws/express/serve-static/send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@
|
||||
"catalog": {
|
||||
"@tsconfig/strictest": "^2.0.6",
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@gitbook/api": "0.154.0",
|
||||
"@gitbook/api": "0.153.0",
|
||||
"@scalar/api-client-react": "^1.3.46",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
@@ -49,8 +49,8 @@
|
||||
"bidc": "^0.0.2",
|
||||
"bun-types": "^1.1.20",
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tsdown": "^0.15.6",
|
||||
"typescript": "^5.5.3",
|
||||
"usehooks-ts": "^3.1.1"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@gitbook/react-openapi": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "1.17.5",
|
||||
"@opennextjs/aws": "^3.8.5",
|
||||
"@opennextjs/cloudflare": "^1.14.4",
|
||||
"@opennextjs/cloudflare": "^1.11.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
@@ -50,7 +50,7 @@
|
||||
"micromark-extension-frontmatter": "^2.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"motion": "^12.23.24",
|
||||
"next": "15.4.10",
|
||||
"next": "15.4.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"nuqs": "^2.2.3",
|
||||
"object-hash": "^3.0.0",
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getDynamicSiteContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <SitePage context={context} pageParams={{ pathname }} staticRoute={false} />;
|
||||
return <SitePage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateViewport(props: PageProps): Promise<Viewport> {
|
||||
|
||||
+1
-2
@@ -23,8 +23,7 @@ export default async function SiteDynamicLayout({
|
||||
|
||||
return (
|
||||
<CustomizationRootLayout
|
||||
htmlClassName="sheet-open:gutter-stable"
|
||||
bodyClassName="site-background"
|
||||
className="site-background"
|
||||
forcedTheme={forcedTheme}
|
||||
context={context}
|
||||
>
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getEmbeddableDynamicContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} staticRoute={false} />;
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: PageProps): Promise<Metadata> {
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getStaticSiteContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <SitePage context={context} pageParams={{ pathname }} staticRoute />;
|
||||
return <SitePage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateViewport(props: PageProps): Promise<Viewport> {
|
||||
|
||||
+1
-5
@@ -19,11 +19,7 @@ export default async function SiteStaticLayout({
|
||||
const withTracking = shouldTrackEvents();
|
||||
|
||||
return (
|
||||
<CustomizationRootLayout
|
||||
htmlClassName="sheet-open:gutter-stable"
|
||||
bodyClassName="site-background"
|
||||
context={context}
|
||||
>
|
||||
<CustomizationRootLayout className="site-background" context={context}>
|
||||
<SiteLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getEmbeddableStaticContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} staticRoute />;
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: PageProps): Promise<Metadata> {
|
||||
|
||||
+9
-1
@@ -7,6 +7,14 @@ import type { NextRequest } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
const EMBEDDABLE_RESPONSE_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
'Cross-Origin-Resource-Policy': 'cross-origin',
|
||||
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=604800',
|
||||
};
|
||||
|
||||
/**
|
||||
* This route is used to serve the assistant.js script.
|
||||
*/
|
||||
@@ -69,8 +77,8 @@ export async function GET(
|
||||
`,
|
||||
{
|
||||
headers: {
|
||||
...EMBEDDABLE_RESPONSE_HEADERS,
|
||||
'Content-Type': 'application/javascript',
|
||||
'Cache-Control': 'public, max-age=86400, stale-while-revalidate=604800',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@
|
||||
import { getDynamicCustomizationSettings } from '@/lib/customization';
|
||||
import type { SiteAPIToken } from '@gitbook/api';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { forbidden, notFound } from 'next/navigation';
|
||||
import { forbidden } from 'next/navigation';
|
||||
import rison from 'rison';
|
||||
|
||||
export type RouteParamMode = 'url-host' | 'url';
|
||||
@@ -80,27 +80,14 @@ export async function getDynamicSiteContext(params: RouteLayoutParams) {
|
||||
* Get the decoded page path from the params.
|
||||
*/
|
||||
export function getPagePathFromParams(params: RouteParams) {
|
||||
// If decoding the param fails, return a 404 instead of crashing
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.pagePath);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Returning 404 after failing to decode page path ${params.pagePath}: ${error}`
|
||||
);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.pagePath);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function getSiteURLFromParams(params: RouteLayoutParams) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.siteURL);
|
||||
const url = new URL(`https://${decoded}`);
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.error(`Returning 404 after failing to decode site URL ${params.siteURL}: ${error}`);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.siteURL);
|
||||
const url = new URL(`https://${decoded}`);
|
||||
return url;
|
||||
}
|
||||
|
||||
function getModeFromParams(mode: string): RouteParamMode {
|
||||
@@ -115,13 +102,6 @@ function getModeFromParams(mode: string): RouteParamMode {
|
||||
* Get the decoded site data from the params.
|
||||
*/
|
||||
function getSiteURLDataFromParams(params: RouteLayoutParams): SiteURLData {
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.siteData);
|
||||
return rison.decode(decoded);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Returning 404 after failing to decode site data ${params.siteData}: ${error}`
|
||||
);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.siteData);
|
||||
return rison.decode(decoded);
|
||||
}
|
||||
|
||||
@@ -128,10 +128,10 @@ function parseResponse<T>(
|
||||
parse: (response: AIStreamResponse) => T | undefined | Promise<T | undefined>
|
||||
): {
|
||||
stream: EventIterator<T>;
|
||||
response: Promise<{ responseId: string | null }>;
|
||||
response: Promise<{ responseId: string }>;
|
||||
} {
|
||||
let resolveResponse: (value: { responseId: string | null }) => void;
|
||||
const response = new Promise<{ responseId: string | null }>((resolve) => {
|
||||
let resolveResponse: (value: { responseId: string }) => void;
|
||||
const response = new Promise<{ responseId: string }>((resolve) => {
|
||||
resolveResponse = resolve;
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ function parseResponse<T>(
|
||||
|
||||
if (event.type === 'response_finish') {
|
||||
foundResponse = true;
|
||||
resolveResponse({ responseId: event.response.id ?? null });
|
||||
resolveResponse({ responseId: event.responseId });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,7 @@ import { useSearch } from '../Search/useSearch';
|
||||
// Unify assistants configuration context with the assistants hook in one place
|
||||
export type AIConfig = {
|
||||
aiMode: CustomizationAIMode;
|
||||
suggestions?: string[];
|
||||
trademark: boolean;
|
||||
greeting?: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type Assistant = Omit<GitBookAssistant, 'icon'> & {
|
||||
@@ -54,11 +49,8 @@ export type Assistant = Omit<GitBookAssistant, 'icon'> & {
|
||||
const AIContext = React.createContext<AIConfig | null>(null);
|
||||
|
||||
export function AIContextProvider(props: React.PropsWithChildren<AIConfig>): React.ReactElement {
|
||||
const { aiMode, trademark, suggestions, greeting, children } = props;
|
||||
const value = React.useMemo(
|
||||
() => ({ aiMode, trademark, suggestions, greeting }),
|
||||
[aiMode, trademark, suggestions, greeting]
|
||||
);
|
||||
const { aiMode, trademark, children } = props;
|
||||
const value = React.useMemo(() => ({ aiMode, trademark }), [aiMode, trademark]);
|
||||
return <AIContext.Provider value={value}>{children}</AIContext.Provider>;
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ export function AIChatProvider(props: {
|
||||
case 'response_finish': {
|
||||
globalState.setState((state) => ({
|
||||
...state,
|
||||
responseId: event.response.id ?? null,
|
||||
responseId: event.responseId,
|
||||
// Mark as not loading when the response is finished
|
||||
// Even if the stream might continue as we receive 'response_followup_suggestion'
|
||||
loading: false,
|
||||
|
||||
@@ -27,7 +27,6 @@ import { useTrackEvent } from '../Insights';
|
||||
import { useNow } from '../hooks';
|
||||
import { Button } from '../primitives';
|
||||
import { ScrollContainer } from '../primitives/ScrollContainer';
|
||||
import { SideSheet } from '../primitives/SideSheet';
|
||||
import { AIChatControlButton } from './AIChatControlButton';
|
||||
import { AIChatIcon } from './AIChatIcon';
|
||||
import { AIChatInput } from './AIChatInput';
|
||||
@@ -70,23 +69,16 @@ export function AIChat() {
|
||||
}, [chat.opened, trackEvent]);
|
||||
|
||||
return (
|
||||
<SideSheet
|
||||
side="right"
|
||||
open={chat.opened}
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
chatController.open();
|
||||
} else {
|
||||
chatController.close();
|
||||
}
|
||||
}}
|
||||
<div
|
||||
data-testid="ai-chat"
|
||||
withScrim={true}
|
||||
className={tcls(
|
||||
'ai-chat mx-auto ml-8 not-hydrated:hidden w-96 transition-[width] duration-300 ease-quint lg:max-xl:w-80'
|
||||
'ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl scroll-mt-36 px-4 py-4 transition-[width,opacity,margin,display] transition-discrete duration-300 sm:px-6 lg:fixed lg:w-80 lg:p-0 xl:w-96',
|
||||
chat.opened
|
||||
? 'lg:starting:ml-0 lg:starting:w-0 lg:starting:opacity-0'
|
||||
: 'hidden lg:ml-0 lg:w-0! lg:opacity-0'
|
||||
)}
|
||||
>
|
||||
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base">
|
||||
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base transition-all duration-300 max-lg:circular-corners:rounded-3xl max-lg:rounded-corners:rounded-md max-lg:border lg:w-80 xl:w-96">
|
||||
<EmbeddableFrameMain>
|
||||
<EmbeddableFrameHeader>
|
||||
<AIChatDynamicIcon trademark={config.trademark} />
|
||||
@@ -109,15 +101,11 @@ export function AIChat() {
|
||||
</EmbeddableFrameButtons>
|
||||
</EmbeddableFrameHeader>
|
||||
<EmbeddableFrameBody>
|
||||
<AIChatBody
|
||||
chatController={chatController}
|
||||
chat={chat}
|
||||
suggestions={config.suggestions}
|
||||
/>
|
||||
<AIChatBody chatController={chatController} chat={chat} />
|
||||
</EmbeddableFrameBody>
|
||||
</EmbeddableFrameMain>
|
||||
</EmbeddableFrame>
|
||||
</SideSheet>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,14 +189,11 @@ export function AIChatBody(props: {
|
||||
chat: AIChatState;
|
||||
welcomeMessage?: string;
|
||||
suggestions?: string[];
|
||||
greeting?: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
}) {
|
||||
const { chatController, chat, suggestions, greeting } = props;
|
||||
const { chatController, chat, suggestions } = props;
|
||||
const { trademark } = useAI().config;
|
||||
|
||||
const [input, setInput] = React.useState('');
|
||||
const language = useLanguage();
|
||||
const now = useNow(60 * 60 * 1000); // Refresh every hour for greeting
|
||||
|
||||
@@ -228,8 +213,8 @@ export function AIChatBody(props: {
|
||||
className="shrink grow basis-80 animate-fade-in-slow [container-type:size]"
|
||||
contentClassName="p-4 gutter-stable flex flex-col gap-4"
|
||||
orientation="vertical"
|
||||
trailing={{ fade: false, button: true }}
|
||||
active={`#message-group-${chat.messages.filter((message) => message.role === 'user').length - 1}`}
|
||||
fadeEdges={['leading']}
|
||||
active={`message-group-${chat.messages.filter((message) => message.role === 'user').length - 1}`}
|
||||
>
|
||||
{isEmpty ? (
|
||||
<div className="flex grow flex-col">
|
||||
@@ -244,20 +229,19 @@ export function AIChatBody(props: {
|
||||
className="size-8 text-primary [@container(min-height:400px)]:size-16"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1 [@container(min-height:400px)]:items-center">
|
||||
<div className="flex flex-col items-start [@container(min-height:400px)]:items-center">
|
||||
<h5
|
||||
className="animate-blur-in-slow font-bold text-lg text-tint-strong leading-tight [@container(min-height:400px)]:text-center"
|
||||
className="animate-blur-in-slow font-bold text-lg text-tint-strong [@container(min-height:400px)]:text-center"
|
||||
style={{ animationDelay: '.5s' }}
|
||||
data-testid="ai-chat-time-greeting"
|
||||
>
|
||||
{greeting?.title || timeGreeting}
|
||||
{timeGreeting}
|
||||
</h5>
|
||||
<p
|
||||
className="animate-blur-in-slow text-tint leading-tight [@container(min-height:400px)]:text-center"
|
||||
className="animate-blur-in-slow text-tint [@container(min-height:400px)]:text-center"
|
||||
style={{ animationDelay: '.6s' }}
|
||||
>
|
||||
{greeting?.subtitle ||
|
||||
t(language, 'ai_chat_assistant_description')}
|
||||
{t(language, 'ai_chat_assistant_description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,10 +262,13 @@ export function AIChatBody(props: {
|
||||
{chat.error ? <AIChatError chatController={chatController} /> : null}
|
||||
|
||||
<AIChatInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
loading={chat.loading}
|
||||
disabled={chat.loading || chat.error}
|
||||
onSubmit={(value) => {
|
||||
chatController.postMessage({ message: value });
|
||||
onSubmit={() => {
|
||||
chatController.postMessage({ message: input });
|
||||
setInput('');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon, IconStyle } from '@gitbook/icons';
|
||||
import type React from 'react';
|
||||
|
||||
@@ -20,18 +21,12 @@ export function AIChatIcon({
|
||||
<Icon
|
||||
icon="sparkle"
|
||||
{...props}
|
||||
className={className}
|
||||
style={{
|
||||
animation: {
|
||||
intro: 'spin 2s forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
thinking: 'spin 2s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
working: 'spin 2s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
done: '',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(
|
||||
className,
|
||||
(state === 'thinking' || state === 'working') &&
|
||||
'animate-[spin_2s_infinite_forwards_cubic-bezier(0.16,1,0.3,1)]',
|
||||
state === 'intro' && 'animate-[spin_2s_forwards_cubic-bezier(0.16,1,0.3,1)]'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -55,36 +50,28 @@ export function AIChatIcon({
|
||||
<path
|
||||
d="M12.8916 1.06265C12.921 0.979101 13.0392 0.979127 13.0685 1.06267C13.239 1.5478 13.3439 1.84646 13.516 2.1032C13.6683 2.33042 13.8578 2.53033 14.0766 2.6945C14.3239 2.88 14.6165 3.00068 15.0919 3.19671C15.1761 3.23142 15.1761 3.3506 15.0919 3.38531C14.6165 3.58134 14.3239 3.70203 14.0766 3.88752C13.8578 4.05169 13.6683 4.2516 13.516 4.47882C13.3439 4.73556 13.239 5.03423 13.0685 5.51937C13.0392 5.60291 12.921 5.60292 12.8916 5.51938C12.7212 5.03423 12.6162 4.73557 12.4442 4.47882C12.2919 4.2516 12.1023 4.05169 11.8835 3.88752C11.6363 3.70202 11.3436 3.58134 10.8682 3.38531C10.7841 3.3506 10.7841 3.23141 10.8683 3.1967C11.3436 3.00067 11.6363 2.87999 11.8835 2.6945C12.1023 2.53033 12.2919 2.33042 12.4442 2.1032C12.6162 1.84646 12.7212 1.54779 12.8916 1.06265Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={state === 'intro' ? '1.2' : '1'}
|
||||
strokeWidth="1.2"
|
||||
strokeLinejoin="round"
|
||||
shapeRendering="crispEdges"
|
||||
className="transition-opacity duration-300"
|
||||
style={{
|
||||
animation: {
|
||||
intro: 'fadeIn .5s .5s backwards, spin 2s .5s forwards cubic-bezier(0.43,1.54,0.64,1)',
|
||||
thinking: 'spin 2s 1s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
working: 'spin 2s 1s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
done: '',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
transitionDelay:
|
||||
state === 'default' || state === 'thinking' ? '.3s' : undefined,
|
||||
opacity: ['done', 'confirm', 'error'].includes(state) ? 0 : 1,
|
||||
transformOrigin: '13px 3.5px',
|
||||
}}
|
||||
className={tcls(
|
||||
state === 'intro' &&
|
||||
'animate-[fadeIn_.5s_.7s_both,spin_2s_1s_forwards_cubic-bezier(.43,1.54,.64,1)]',
|
||||
(state === 'working' || state === 'thinking') &&
|
||||
'animate-[fadeIn_.5s_.3s_both,spin_2s_1s_infinite_forwards_cubic-bezier(0.16,1,0.3,1)]',
|
||||
state === 'done' && 'animate-[fadeOut_.5s_both]',
|
||||
state === 'confirm' && 'animate-[fadeOut_.5s_both]',
|
||||
state === 'default' && 'animate-[fadeIn_0s_both]',
|
||||
state === 'error' && 'hidden'
|
||||
)}
|
||||
style={{ transformOrigin: '13px 3.5px' }}
|
||||
/>
|
||||
|
||||
{/* Error */}
|
||||
<g
|
||||
clipPath="url(#clip0_153_2034)"
|
||||
className="transition-opacity duration-300"
|
||||
style={{
|
||||
color: 'rgb(var(--danger-9))',
|
||||
opacity: state === 'error' ? 1 : 0,
|
||||
transitionDelay: state === 'error' ? '.3s' : undefined,
|
||||
}}
|
||||
className={tcls(
|
||||
'text-danger-subtle',
|
||||
state === 'error' ? 'animate-[fadeIn_.5s_.3s_both]' : 'hidden'
|
||||
)}
|
||||
>
|
||||
<path
|
||||
d="M13.0312 1.42059L13.0312 3.95184"
|
||||
@@ -107,34 +94,29 @@ export function AIChatIcon({
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="transition-opacity duration-300"
|
||||
style={{
|
||||
opacity: state === 'done' ? 1 : 0,
|
||||
transitionDelay: state === 'done' ? '.3s' : undefined,
|
||||
}}
|
||||
className={tcls(
|
||||
state === 'done'
|
||||
? 'animate-[fadeIn_.5s_.3s_both]'
|
||||
: 'animate-[fadeOut_.5s_both]',
|
||||
state === 'intro' && 'hidden',
|
||||
state === 'confirm' && 'hidden'
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Confirm */}
|
||||
<path
|
||||
className="transition-opacity duration-300"
|
||||
style={{
|
||||
fill: 'rgb(var(--primary-9))',
|
||||
opacity: state === 'confirm' ? 1 : 0,
|
||||
transitionDelay: state === 'confirm' ? '.3s' : undefined,
|
||||
animation: {
|
||||
intro: '',
|
||||
thinking: '',
|
||||
working: '',
|
||||
done: '',
|
||||
confirm: 'bounceSmall 1s infinite both',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(
|
||||
'fill-primary-9',
|
||||
state === 'confirm'
|
||||
? 'animate-[fadeIn_.5s_.3s_both,bounceSmall_1s_infinite_both]'
|
||||
: state === 'thinking'
|
||||
? 'animate-[fadeOut_.5s_both]'
|
||||
: 'hidden'
|
||||
)}
|
||||
d="M12.9463 5.24512C13.3688 5.24422 13.713 5.58625 13.7139 6.00879C13.7146 6.43114 13.3725 6.77338 12.9502 6.77441C12.5279 6.77505 12.1845 6.43408 12.1836 6.01172C12.1828 5.58953 12.5242 5.24649 12.9463 5.24512ZM13.0391 0.0751953C14.0688 0.0730893 14.9049 0.90586 14.9072 1.93555C14.9084 2.5063 14.6484 3.04679 14.2012 3.40137L13.7773 3.7373C13.6151 3.86604 13.5201 4.06239 13.5205 4.26953V4.30371C13.5211 4.62139 13.2639 4.879 12.9463 4.87988C12.6288 4.88032 12.3701 4.62417 12.3691 4.30664V4.27246C12.3679 3.71272 12.6238 3.18263 13.0625 2.83496L13.4854 2.49902C13.6565 2.36341 13.7562 2.1568 13.7559 1.93848C13.755 1.54463 13.4358 1.22503 13.042 1.22559H12.9385C12.488 1.22679 12.1225 1.59352 12.123 2.04395L12.124 2.21875C12.1245 2.53649 11.8676 2.79522 11.5498 2.7959C11.2321 2.79653 10.9746 2.53928 10.9736 2.22168L10.9727 2.04688C10.9706 0.960578 11.8493 0.0778178 12.9355 0.0751953H13.0391Z"
|
||||
/>
|
||||
|
||||
{/* Background */}
|
||||
{/* Background */}
|
||||
<path
|
||||
d="M3.5625 8.78512L7.26347 10.9219C7.88227 11.2791 8.64467 11.2791 9.26347 10.9219L14.25 8.0429C14.5833 7.85045 15 8.09101 15 8.47591V10.2777C15 10.4563 14.9047 10.6214 14.75 10.7107L9.26347 13.8784C8.64467 14.2356 7.88228 14.2356 7.26347 13.8784L3.5625 11.7416C2.70833 11.2978 1 9.93199 1 8.01949M1 8.01949C1 6.6448 1.84765 5.98698 2.62903 5.71701C3.15426 5.53555 3.71577 5.70568 4.19701 5.98353L7.26347 7.75395C7.88228 8.11122 8.64467 8.11122 9.26347 7.75395L10.9095 6.80362M1 8.01949C1 6.4945 2.03973 5.30731 2.5596 4.90434L7.37937 2.12165C7.79013 1.88449 8.26417 1.80476 8.71747 1.88245"
|
||||
stroke="currentColor"
|
||||
@@ -142,17 +124,7 @@ export function AIChatIcon({
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{
|
||||
animation: {
|
||||
intro: 'fadeIn 2s forwards',
|
||||
thinking: '',
|
||||
working: '',
|
||||
done: '',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(state === 'intro' && 'animate-[fadeIn_2s_forwards]')}
|
||||
/>
|
||||
|
||||
{/* Logo */}
|
||||
@@ -182,65 +154,45 @@ export function AIChatIcon({
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{
|
||||
animation: {
|
||||
intro: 'pathEnter 1.5s both ease-out',
|
||||
thinking: 'pathLoading 2s infinite both',
|
||||
working: 'pathLoading 2s infinite both',
|
||||
done: 'pathEnter 1s forwards ease',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(
|
||||
(state === 'thinking' || state === 'working') &&
|
||||
'animate-[pathLoading_2s_infinite_both]',
|
||||
state === 'intro' && 'animate-[pathEnter_2s_both]',
|
||||
state === 'done' && 'animate-[pathEnter_1s_forwards_ease]'
|
||||
)}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
interface AISearchIconProps extends React.SVGProps<SVGSVGElement> {
|
||||
className?: string;
|
||||
state?: 'default' | 'intro' | 'thinking' | 'working' | 'done' | 'error' | 'confirm';
|
||||
}
|
||||
|
||||
export function AISearchIcon({ className = 'size-4', state = 'default' }: AISearchIconProps) {
|
||||
export function AISearchIcon({
|
||||
className = 'size-4',
|
||||
state = 'default',
|
||||
}: Pick<AIChatIconProps, 'className' | 'state'>) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
animation: {
|
||||
intro: 'fadeIn 1s both, orbit 1s cubic-bezier(0.16,1,0.3,1)',
|
||||
thinking:
|
||||
'fadeIn 1s both, orbit 1s orbit 2s 1s infinite forwards linear ease-out,',
|
||||
working: '',
|
||||
done: '',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(
|
||||
'relative',
|
||||
state === 'intro' && 'animate-[fadeIn_1s_both,orbit_1s_cubic-bezier(0.16,1,0.3,1)]',
|
||||
state === 'thinking' || state === 'working'
|
||||
? 'animate-[fadeIn_1s_both,orbit_1s_ease-out,orbit_2s_1s_infinite_forwards_linear]'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<Icon icon="search" className={className} />
|
||||
<Icon
|
||||
icon="sparkle"
|
||||
iconStyle={IconStyle.Solid}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '15.7%',
|
||||
left: '15.6%',
|
||||
width: '50%',
|
||||
height: '50%',
|
||||
animation: {
|
||||
intro: 'spin 2s .5s forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
thinking: 'spin 2s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
working: 'spin 2s infinite forwards cubic-bezier(0.16,1,0.3,1)',
|
||||
done: '',
|
||||
confirm: '',
|
||||
default: '',
|
||||
error: '',
|
||||
}[state],
|
||||
}}
|
||||
className={tcls(
|
||||
'absolute top-[15.7%] left-[15.6%] size-[50%]',
|
||||
state === 'thinking' || state === 'working'
|
||||
? 'animate-[spin_2s_infinite_forwards_cubic-bezier(0.16,1,0.3,1)]'
|
||||
: '',
|
||||
state === 'intro'
|
||||
? 'animate-[spin_2s_.5s_forwards_cubic-bezier(0.16,1,0.3,1)]'
|
||||
: ''
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
import { t, tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useAIChatState } from '../AI/useAIChat';
|
||||
import { HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
|
||||
import { Input } from '../primitives/Input';
|
||||
import { Button, HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
|
||||
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
|
||||
|
||||
export function AIChatInput(props: {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* When true, the input is disabled
|
||||
*/
|
||||
loading: boolean;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit: (value: string) => void;
|
||||
}) {
|
||||
const { onSubmit, disabled, loading } = props;
|
||||
const { value, onChange, onSubmit, disabled, loading } = props;
|
||||
|
||||
const language = useLanguage();
|
||||
const chat = useAIChatState();
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const handleInput = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const textarea = event.currentTarget;
|
||||
onChange(textarea.value);
|
||||
|
||||
// Auto-resize
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (chat.opened && !disabled && !loading) {
|
||||
// Add a small delay to ensure the input is rendered before focusing
|
||||
@@ -45,33 +57,57 @@ export function AIChatInput(props: {
|
||||
);
|
||||
|
||||
return (
|
||||
<Input
|
||||
data-testid="ai-chat-input"
|
||||
name="ai-chat-input"
|
||||
multiline
|
||||
resize
|
||||
sizing="large"
|
||||
label="Assistant chat input"
|
||||
placeholder={tString(language, 'ai_chat_input_placeholder')}
|
||||
onSubmit={(val) => onSubmit(val as string)}
|
||||
submitButton={{
|
||||
label: tString(language, 'send'),
|
||||
}}
|
||||
className="animate-blur-in-slow bg-tint-base/9 backdrop-blur-lg contrast-more:bg-tint-base"
|
||||
rows={1}
|
||||
maxLength={2048}
|
||||
keyboardShortcut={
|
||||
!disabled && !loading
|
||||
? {
|
||||
keys: ['mod', 'i'],
|
||||
className: 'bg-tint-base group-focus-within/input:hidden',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
disabled={disabled || loading}
|
||||
aria-busy={loading}
|
||||
ref={inputRef}
|
||||
trailing={
|
||||
<div className="depth-subtle:has-[textarea:focus]:-translate-y-px relative flex animate-blur-in-slow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-xl bg-tint-base/9 depth-subtle:shadow-sm shadow-tint/6 ring-1 ring-tint-subtle backdrop-blur-lg transition-all depth-subtle:has-[textarea:focus]:shadow-lg has-[textarea:focus]:shadow-primary-subtle has-[textarea:focus]:ring-2 has-[textarea:focus]:ring-primary-hover contrast-more:bg-tint-base dark:shadow-tint-1">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
disabled={disabled || loading}
|
||||
data-loading={loading}
|
||||
data-testid="ai-chat-input"
|
||||
className={tcls(
|
||||
'resize-none',
|
||||
'focus:outline-hidden',
|
||||
'focus:ring-0',
|
||||
'w-full',
|
||||
'px-3',
|
||||
'py-3',
|
||||
'pb-12',
|
||||
'h-auto',
|
||||
'bg-transparent',
|
||||
'peer',
|
||||
'max-h-64',
|
||||
'placeholder:text-tint/8',
|
||||
'transition-colors',
|
||||
'disabled:bg-tint-subtle',
|
||||
'delay-300',
|
||||
'disabled:delay-0',
|
||||
'disabled:cursor-not-allowed',
|
||||
'data-[loading=true]:cursor-progress',
|
||||
'data-[loading=true]:opacity-50'
|
||||
)}
|
||||
value={value}
|
||||
rows={1}
|
||||
placeholder={tString(language, 'ai_chat_input_placeholder')}
|
||||
onChange={handleInput}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey && value.trim()) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.style.height = 'auto';
|
||||
onSubmit(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!disabled ? (
|
||||
<div className="absolute top-2.5 right-3 animate-[fadeIn_0.2s_0.5s_ease-in-out_both] peer-focus:hidden">
|
||||
<KeyboardShortcut keys={['mod', 'i']} className="bg-tint-base" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="absolute inset-x-0 bottom-0 flex items-center gap-2 px-2 py-2">
|
||||
<HoverCardRoot openDelay={500}>
|
||||
<HoverCard
|
||||
className="max-w-xs bg-tint p-2 text-sm text-tint"
|
||||
@@ -99,8 +135,7 @@ export function AIChatInput(props: {
|
||||
</div>
|
||||
</HoverCard>
|
||||
<HoverCardTrigger>
|
||||
{/* Negative margin to compensate for Input's padding, so the badge appears flush with the cursor */}
|
||||
<div className="-ml-1 flex cursor-help items-center gap-1 circular-corners:rounded-2xl rounded-corners:rounded-md px-2.5 py-1.5 text-tint/7 text-xs transition-all hover:bg-tint">
|
||||
<div className="flex cursor-help items-center gap-1 circular-corners:rounded-2xl rounded-corners:rounded-md px-2.5 py-1.5 text-tint/7 text-xs transition-all hover:bg-tint">
|
||||
<span className="-ml-1 circular-corners:rounded-2xl rounded-corners:rounded-sm bg-tint-11/7 px-1 py-0.5 font-mono font-semibold text-[0.65rem] text-contrast-tint-11 leading-none">
|
||||
{t(language, 'ai_chat_context_badge')}
|
||||
</span>{' '}
|
||||
@@ -111,7 +146,14 @@ export function AIChatInput(props: {
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
</HoverCardRoot>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
label={tString(language, 'send')}
|
||||
size="medium"
|
||||
className="ml-auto"
|
||||
disabled={disabled || !value.trim()}
|
||||
onClick={() => onSubmit(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,16 +7,14 @@ export default function AIChatSuggestedQuestions(props: {
|
||||
suggestions?: string[];
|
||||
}) {
|
||||
const language = useLanguage();
|
||||
const { chatController, suggestions: _suggestions } = props;
|
||||
|
||||
const suggestions =
|
||||
_suggestions && _suggestions.length > 0
|
||||
? _suggestions
|
||||
: [
|
||||
tString(language, 'ai_chat_suggested_questions_about_this_page'),
|
||||
tString(language, 'ai_chat_suggested_questions_read_next'),
|
||||
tString(language, 'ai_chat_suggested_questions_example'),
|
||||
];
|
||||
const {
|
||||
chatController,
|
||||
suggestions = [
|
||||
tString(language, 'ai_chat_suggested_questions_about_this_page'),
|
||||
tString(language, 'ai_chat_suggested_questions_read_next'),
|
||||
tString(language, 'ai_chat_suggested_questions_example'),
|
||||
],
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2 self-start">
|
||||
|
||||
@@ -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="default"
|
||||
onClick={dismissAnnouncement}
|
||||
className={`absolute top-0 right-4 mt-2 mr-2 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>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
aria-describedby={describedById}
|
||||
className={tcls(
|
||||
'fixed',
|
||||
'z-50',
|
||||
'z-10',
|
||||
'bg-tint-base',
|
||||
'rounded-sm',
|
||||
'straight-corners:rounded-none',
|
||||
@@ -52,9 +52,9 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
'depth-flat:shadow-none',
|
||||
'p-4',
|
||||
'pr-8',
|
||||
'bottom-[max(env(safe-area-inset-bottom),1rem)]',
|
||||
'right-[max(env(safe-area-inset-right),1rem)]',
|
||||
'left-[max(env(safe-area-inset-left),4rem)]',
|
||||
'bottom-4',
|
||||
'right-4',
|
||||
'left-16',
|
||||
'max-w-md',
|
||||
'text-balance',
|
||||
'sm:left-auto',
|
||||
|
||||
@@ -156,10 +156,8 @@
|
||||
|
||||
/** Text input */
|
||||
.contentkit-textinput {
|
||||
@apply w-full circular-corners:rounded-3xl ring-primary-hover rounded-corners:rounded-lg border border-tint text-tint-strong transition-all placeholder:text-tint/8 flex resize-none flex-1 px-2 py-1.5 text-sm bg-tint-base whitespace-pre-line;
|
||||
@apply shadow-tint/6 depth-subtle:focus-within:-translate-y-px depth-subtle:shadow-sm depth-subtle:focus-within:shadow-lg dark:shadow-tint-1;
|
||||
@apply focus:border-primary-hover focus:shadow-primary-subtle focus:ring-2 hover:border-tint-hover focus:hover:border-primary-hover;
|
||||
@apply disabled:cursor-not-allowed disabled:border-tint-subtle disabled:bg-tint-subtle;
|
||||
@apply w-full rounded border border-tint text-tint-strong placeholder:text-tint flex resize-none flex-1 px-2 py-1.5 text-sm bg-transparent whitespace-pre-line;
|
||||
@apply focus:outline-primary focus:border-primary;
|
||||
}
|
||||
|
||||
/** Form */
|
||||
|
||||
@@ -98,7 +98,6 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
|
||||
chatController={chatController}
|
||||
chat={chat}
|
||||
suggestions={configuration.suggestions}
|
||||
greeting={configuration.greeting}
|
||||
/>
|
||||
</EmbeddableFrameBody>
|
||||
</EmbeddableFrameMain>
|
||||
|
||||
@@ -30,9 +30,7 @@ type EmbeddableDocsPageProps = {
|
||||
/**
|
||||
* Page component for the embed docs page.
|
||||
*/
|
||||
export async function EmbeddableDocsPage(
|
||||
props: EmbeddableDocsPageProps & { staticRoute: boolean }
|
||||
) {
|
||||
export async function EmbeddableDocsPage(props: EmbeddableDocsPageProps) {
|
||||
const { context, pageParams } = props;
|
||||
const { page, document, ancestors, withPageFeedback } = await getSitePageData({
|
||||
context,
|
||||
@@ -79,9 +77,9 @@ export async function EmbeddableDocsPage(
|
||||
orientation="vertical"
|
||||
className="not-hydrated:animate-blur-in-slow"
|
||||
contentClassName="p-4"
|
||||
leading={{ fade: !context.sections, button: true }}
|
||||
fadeEdges={context.sections ? [] : ['leading']}
|
||||
>
|
||||
<TableOfContents context={context} />
|
||||
<TableOfContents className="pt-0" context={context} />
|
||||
<PageBody
|
||||
context={context}
|
||||
page={page}
|
||||
@@ -89,7 +87,6 @@ export async function EmbeddableDocsPage(
|
||||
document={document}
|
||||
withPageFeedback={withPageFeedback}
|
||||
insightsDisplayContext={SiteInsightsDisplayContext.Embed}
|
||||
staticRoute={props.staticRoute}
|
||||
/>
|
||||
</ScrollContainer>
|
||||
</EmbeddableFrameBody>
|
||||
|
||||
@@ -41,7 +41,6 @@ export async function EmbeddableRootLayout({
|
||||
>
|
||||
<AIContextProvider
|
||||
aiMode={context.customization.ai.mode}
|
||||
suggestions={context.customization.ai.suggestions}
|
||||
trademark={context.customization.trademark.enabled}
|
||||
>
|
||||
<SpaceLayoutServerContext
|
||||
@@ -58,7 +57,7 @@ export async function EmbeddableRootLayout({
|
||||
{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"
|
||||
className="rounded-none border-tint-solid/3 border-t bg-tint-solid/1 px-4 py-2.5 text-tint/8 ring-0"
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Embed}
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,6 @@ export function Header(props: {
|
||||
`h-[${HEADER_HEIGHT_DESKTOP}px]`,
|
||||
'sticky',
|
||||
'top-0',
|
||||
'pt-[env(safe-area-inset-top)]',
|
||||
'z-30',
|
||||
'w-full',
|
||||
'flex-none',
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
'use client';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
|
||||
import { useScrollListener } from '../hooks/useScrollListener';
|
||||
import { Button, type ButtonProps } from '../primitives';
|
||||
|
||||
const globalClassName = 'navigation-open';
|
||||
|
||||
const SCROLL_DISTANCE = 320;
|
||||
|
||||
/**
|
||||
* Button to show/hide the table of content on mobile.
|
||||
*/
|
||||
@@ -15,6 +18,25 @@ export function HeaderMobileMenu(props: ButtonProps) {
|
||||
const language = useLanguage();
|
||||
|
||||
const pathname = usePathname();
|
||||
const hasScrollRef = useRef(false);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleNavigation = () => {
|
||||
if (!hasScrollRef.current && document.body.classList.contains(globalClassName)) {
|
||||
document.body.classList.remove(globalClassName);
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
document.body.classList.add(globalClassName);
|
||||
window.scrollTo(0, 0);
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const windowRef = useRef(typeof window === 'undefined' ? null : window);
|
||||
useScrollListener(() => {
|
||||
hasScrollRef.current = window.scrollY >= SCROLL_DISTANCE;
|
||||
}, windowRef);
|
||||
|
||||
// Close the navigation when navigating to a page
|
||||
useEffect(() => {
|
||||
@@ -28,10 +50,8 @@ export function HeaderMobileMenu(props: ButtonProps) {
|
||||
variant="blank"
|
||||
size="default"
|
||||
label={tString(language, 'table_of_contents_button_label')}
|
||||
onClick={() => {
|
||||
document.body.classList.toggle(globalClassName);
|
||||
}}
|
||||
// Since the button is hidden behind the TOC after toggling, we don't need to keep track of its active state.
|
||||
onClick={toggleNavigation}
|
||||
active={isOpen}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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'
|
||||
)}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Conditionally wrap children in a Suspense component based on SSR status.
|
||||
* We need to do that because otherwise ISR pages will trigger the suspense boundary on every request,
|
||||
* causing flickering and bad UX.
|
||||
*/
|
||||
export default function OptionalSuspense(props: {
|
||||
staticRoute: boolean;
|
||||
fallback: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { staticRoute, fallback, children } = props;
|
||||
|
||||
if (staticRoute) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <React.Suspense fallback={fallback}>{children}</React.Suspense>;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import type { JSONDocument, RevisionPageDocument, SiteInsightsDisplayContext } from '@gitbook/api';
|
||||
import React from 'react';
|
||||
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { t } from '@/intl/translate';
|
||||
@@ -11,7 +12,6 @@ import { TrackPageViewEvent } from '../Insights';
|
||||
import { PageFeedbackForm } from '../PageFeedback';
|
||||
import { CurrentPageProvider } from '../hooks/useCurrentPage';
|
||||
import { DateRelative, SuspenseLoadedHint } from '../primitives';
|
||||
import OptionalSuspense from './OptionalSuspense';
|
||||
import { PageBodyBlankslate } from './PageBodyBlankslate';
|
||||
import { PageCover } from './PageCover';
|
||||
import { PageFooterNavigation } from './PageFooterNavigation';
|
||||
@@ -27,17 +27,8 @@ export function PageBody(props: {
|
||||
document: JSONDocument | null;
|
||||
withPageFeedback: boolean;
|
||||
insightsDisplayContext: SiteInsightsDisplayContext;
|
||||
staticRoute: boolean;
|
||||
}) {
|
||||
const {
|
||||
page,
|
||||
context,
|
||||
ancestors,
|
||||
document,
|
||||
withPageFeedback,
|
||||
insightsDisplayContext,
|
||||
staticRoute,
|
||||
} = props;
|
||||
const { page, context, ancestors, document, withPageFeedback, insightsDisplayContext } = props;
|
||||
const { customization } = context;
|
||||
|
||||
const contentFullWidth = document ? hasFullWidthBlock(document) : false;
|
||||
@@ -74,7 +65,7 @@ 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'
|
||||
@@ -93,8 +84,7 @@ export function PageBody(props: {
|
||||
withRSSFeed={contentHasUpdates}
|
||||
/>
|
||||
{document && !isNodeEmpty(document) ? (
|
||||
<OptionalSuspense
|
||||
staticRoute={staticRoute}
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<DocumentViewSkeleton
|
||||
document={document}
|
||||
@@ -116,7 +106,7 @@ export function PageBody(props: {
|
||||
withLinkPreviews,
|
||||
}}
|
||||
/>
|
||||
</OptionalSuspense>
|
||||
</React.Suspense>
|
||||
) : (
|
||||
<PageBodyBlankslate page={page} context={context} />
|
||||
)}
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function PageBodyBlankslate(props: {
|
||||
<Card
|
||||
key={child.id}
|
||||
leadingIcon={icon}
|
||||
title={child.linkTitle || child.title}
|
||||
title={child.title}
|
||||
href={resolved.href}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
@@ -62,14 +62,7 @@ export async function PageBodyBlankslate(props: {
|
||||
pages: context.revision.pages,
|
||||
page: child,
|
||||
});
|
||||
return (
|
||||
<Card
|
||||
key={child.id}
|
||||
title={child.linkTitle || child.title}
|
||||
leadingIcon={icon}
|
||||
href={href}
|
||||
/>
|
||||
);
|
||||
return <Card key={child.id} title={child.title} leadingIcon={icon} href={href} />;
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function PageFooterNavigation(props: {
|
||||
<NavigationCard
|
||||
icon="chevron-left"
|
||||
label={t(language, 'previous_page')}
|
||||
title={previous.linkTitle || previous.title}
|
||||
title={previous.title}
|
||||
href={previousHref}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
@@ -62,7 +62,7 @@ export async function PageFooterNavigation(props: {
|
||||
<NavigationCard
|
||||
icon="chevron-right"
|
||||
label={t(language, 'next_page')}
|
||||
title={next.linkTitle || next.title}
|
||||
title={next.title}
|
||||
href={nextHref}
|
||||
insights={{
|
||||
type: 'link_click',
|
||||
|
||||
@@ -6,10 +6,10 @@ import React, { type ButtonHTMLAttributes } from 'react';
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t, tString } from '@/intl/translate';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { useTrackEvent } from '../Insights';
|
||||
import { Button, ButtonGroup, Input } from '../primitives';
|
||||
|
||||
const MIN_COMMENT_LENGTH = 3;
|
||||
import { useTrackEvent } from '../Insights';
|
||||
import { Button, ButtonGroup } from '../primitives';
|
||||
|
||||
const MAX_COMMENT_LENGTH = 512;
|
||||
|
||||
/**
|
||||
@@ -24,6 +24,7 @@ export function PageFeedbackForm(props: {
|
||||
const trackEvent = useTrackEvent();
|
||||
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
const [rating, setRating] = React.useState<PageFeedbackRating>();
|
||||
const [comment, setComment] = React.useState('');
|
||||
const [submitted, setSubmitted] = React.useState(false);
|
||||
|
||||
const onSubmitRating = (rating: PageFeedbackRating) => {
|
||||
@@ -85,21 +86,43 @@ export function PageFeedbackForm(props: {
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
{rating ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
label={tString(languages, 'was_this_helpful_comment')}
|
||||
multiline
|
||||
submitButton
|
||||
rows={3}
|
||||
name="page-feedback-comment"
|
||||
onSubmit={(comment) => onSubmitComment(rating, comment as string)}
|
||||
maxLength={MAX_COMMENT_LENGTH}
|
||||
minLength={MIN_COMMENT_LENGTH}
|
||||
disabled={submitted}
|
||||
submitMessage={tString(languages, 'was_this_helpful_thank_you')}
|
||||
className="animate-blur-in"
|
||||
resize
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
{!submitted ? (
|
||||
<>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
name="comment"
|
||||
className="mx-0.5 max-h-40 min-h-16 grow rounded-sm straight-corners:rounded-none bg-tint-base p-2 ring-1 ring-tint ring-inset placeholder:text-sm placeholder:text-tint contrast-more:ring-tint-12 contrast-more:placeholder:text-tint-strong"
|
||||
placeholder={tString(languages, 'was_this_helpful_comment')}
|
||||
aria-label={tString(languages, 'was_this_helpful_comment')}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
value={comment}
|
||||
rows={3}
|
||||
maxLength={MAX_COMMENT_LENGTH}
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => onSubmitComment(rating, comment)}
|
||||
label={tString(languages, 'submit')}
|
||||
/>
|
||||
{comment.length > MAX_COMMENT_LENGTH * 0.8 ? (
|
||||
<span
|
||||
className={
|
||||
comment.length === MAX_COMMENT_LENGTH
|
||||
? 'text-red-500'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{comment.length} / {MAX_COMMENT_LENGTH}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>{t(languages, 'was_this_helpful_thank_you')}</p>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -57,15 +57,13 @@ function preloadFont(fontData: FontData) {
|
||||
* It takes care of setting the theme and the language.
|
||||
*/
|
||||
export async function CustomizationRootLayout(props: {
|
||||
/** The class name to apply to the html element. */
|
||||
htmlClassName?: string;
|
||||
/** The class name to apply to the body element. */
|
||||
bodyClassName?: string;
|
||||
className?: string;
|
||||
forcedTheme?: CustomizationThemeMode | null;
|
||||
context: GitBookAnyContext;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { htmlClassName, bodyClassName, context, forcedTheme, children } = props;
|
||||
const { className, context, forcedTheme, children } = props;
|
||||
const customization =
|
||||
'customization' in context ? context.customization : defaultCustomization();
|
||||
|
||||
@@ -109,8 +107,7 @@ export async function CustomizationRootLayout(props: {
|
||||
// Set the dark/light class statically to avoid flashing and make it work when JS is disabled
|
||||
(forcedTheme ?? customization.themes.default) === CustomizationThemeMode.Dark
|
||||
? 'dark'
|
||||
: '',
|
||||
htmlClassName
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<head>
|
||||
@@ -182,7 +179,7 @@ export async function CustomizationRootLayout(props: {
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body className={tcls(bodyClassName, 'sheet-open:overflow-hidden')}>
|
||||
<body className={className}>
|
||||
<IconsProvider
|
||||
assetsURL={GITBOOK_ICONS_URL}
|
||||
assetsURLToken={GITBOOK_ICONS_TOKEN}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function SearchContainer({
|
||||
viewport,
|
||||
siteSpaces,
|
||||
}: SearchContainerProps) {
|
||||
const { assistants, config } = useAI();
|
||||
const { assistants } = useAI();
|
||||
|
||||
const [state, setSearchState] = useSearch();
|
||||
const searchAsk = useSearchAskState();
|
||||
@@ -200,8 +200,7 @@ export function SearchContainer({
|
||||
siteSpaceId: siteSpace.id,
|
||||
siteSpaceIds,
|
||||
scope: state?.scope ?? 'default',
|
||||
withAI,
|
||||
suggestions: config.suggestions,
|
||||
withAI: withAI,
|
||||
});
|
||||
const searchValue = state?.query ?? (withSearchAI || !withAI ? state?.ask : null) ?? '';
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
'use client';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { Input } from '../primitives';
|
||||
import { Button, variantClasses } from '../primitives';
|
||||
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
|
||||
import { useClassnames } from '../primitives/StyleProvider';
|
||||
|
||||
interface SearchInputProps {
|
||||
onChange: (value: string) => void;
|
||||
@@ -16,11 +20,14 @@ interface SearchInputProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
// Size classes for medium size button
|
||||
const sizeClasses = ['text-sm', 'px-3.5', 'py-1.5', '@2xl:circular-corners:px-4'];
|
||||
|
||||
/**
|
||||
* Input to trigger search.
|
||||
*/
|
||||
export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
function SearchInput(props, containerRef) {
|
||||
function SearchInput(props, ref) {
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown,
|
||||
@@ -35,6 +42,7 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const language = useLanguage();
|
||||
const buttonStyles = useClassnames(['ButtonStyles']);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -50,45 +58,74 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
}, [isOpen, value]);
|
||||
|
||||
return (
|
||||
<div className="relative flex @max-2xl:size-9.5 grow">
|
||||
<Input
|
||||
data-testid="search-input"
|
||||
name="search-input"
|
||||
ref={inputRef}
|
||||
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"
|
||||
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"
|
||||
<div className={tcls('relative flex size-9 grow', className)}>
|
||||
{/* biome-ignore lint/a11y/useKeyWithClickEvents: this div needs an onClick to show the input on mobile, where it's normally hidden.
|
||||
Normally you'd also need to add a keyboard trigger to do the same without a pointer, but in this case the input already be focused on its own. */}
|
||||
<div
|
||||
ref={ref}
|
||||
onClick={onFocus}
|
||||
className={tcls(
|
||||
// Apply button styles
|
||||
buttonStyles,
|
||||
variantClasses.header,
|
||||
sizeClasses,
|
||||
// Additional custom styles
|
||||
'has-[input:focus]:-translate-y-px h-9 grow @2xl:cursor-text cursor-pointer px-2.5 has-[input:focus]:bg-tint-base has-[input:focus]:depth-subtle:shadow-lg has-[input:focus]:depth-subtle:shadow-primary-subtle has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-primary-hover',
|
||||
'theme-bold:border-header-link/3 has-[input:focus-visible]:theme-bold:border-header-link/5 has-[input:focus-visible]:theme-bold:bg-header-link/3 has-[input:focus-visible]:theme-bold:ring-header-link/5',
|
||||
'theme-bold:before:absolute theme-bold:before:inset-0 theme-bold:before:bg-header-background/7 theme-bold:before:backdrop-blur-xl ', // Special overlay to make the transparent colors of theme-bold visible.
|
||||
'@max-2xl:absolute relative @max-2xl:right-0 z-30 max-w-none shrink grow justify-start',
|
||||
isOpen ? '@max-2xl:w-56' : '@max-2xl:w-[38px]'
|
||||
)}
|
||||
>
|
||||
{value && isOpen ? (
|
||||
<Button
|
||||
variant="blank"
|
||||
label={tString(language, 'clear')}
|
||||
size="medium"
|
||||
iconOnly
|
||||
icon="circle-xmark"
|
||||
className="-ml-1.5 -mr-1 animate-scale-in px-1.5 theme-bold:text-header-link theme-bold:hover:bg-header-link/3"
|
||||
onClick={() => {
|
||||
onChange('');
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value);
|
||||
}}
|
||||
value={value}
|
||||
maxLength={512}
|
||||
autoComplete="off"
|
||||
aria-autocomplete="list"
|
||||
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',
|
||||
}}
|
||||
keyboardShortcut={{
|
||||
className:
|
||||
'theme-bold:border-header-link/4 theme-bold:bg-header-background theme-bold:text-header-link',
|
||||
keys: isOpen ? ['esc'] : ['mod', 'k'],
|
||||
}}
|
||||
{...rest}
|
||||
type="text"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon="magnifying-glass"
|
||||
className="size-4 shrink-0 animate-scale-in"
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
<input
|
||||
{...rest}
|
||||
type="text"
|
||||
onFocus={onFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
value={value}
|
||||
// We only show "search or ask" if the search input actually handles both search and ask.
|
||||
placeholder={`${tString(language, withAI ? 'search_or_ask' : 'search')}…`}
|
||||
maxLength={512}
|
||||
size={10}
|
||||
data-testid="search-input"
|
||||
className={tcls(
|
||||
'peer z-10 min-w-0 grow bg-transparent py-0.5 text-tint-strong theme-bold:text-header-link outline-hidden transition-[width] duration-300 contain-paint placeholder:text-tint theme-bold:placeholder:text-current theme-bold:placeholder:opacity-7',
|
||||
isOpen ? '' : '@max-2xl:opacity-0'
|
||||
)}
|
||||
role="combobox"
|
||||
autoComplete="off"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={value && isOpen ? 'true' : 'false'}
|
||||
// Forward
|
||||
ref={inputRef}
|
||||
/>
|
||||
<KeyboardShortcut
|
||||
keys={isOpen ? ['esc'] : ['mod', 'k']}
|
||||
className="last:-mr-1 theme-bold:border-header-link/5 theme-bold:bg-header-background theme-bold:text-header-link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,8 @@ export function useSearchResults(props: {
|
||||
siteSpaceIds: string[];
|
||||
scope: SearchScope;
|
||||
withAI: boolean;
|
||||
suggestions?: string[];
|
||||
}) {
|
||||
const { disabled, query, siteSpaceId, siteSpaceIds, scope, suggestions } = props;
|
||||
const { disabled, query, siteSpaceId, siteSpaceIds, scope } = props;
|
||||
|
||||
const trackEvent = useTrackEvent();
|
||||
|
||||
@@ -79,22 +78,6 @@ export function useSearchResults(props: {
|
||||
const questions = new Set<string>();
|
||||
const recommendedQuestions: ResultType[] = [];
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
suggestions.forEach((question) => {
|
||||
questions.add(question);
|
||||
});
|
||||
setResultsState({
|
||||
results: suggestions.map((question, index) => ({
|
||||
type: 'recommended-question',
|
||||
id: `recommended-question-${index}`,
|
||||
question,
|
||||
})),
|
||||
fetching: false,
|
||||
error: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
@@ -173,11 +156,7 @@ export function useSearchResults(props: {
|
||||
return;
|
||||
}
|
||||
|
||||
const aiEnrichedResults = withAI
|
||||
? withAskTriggers(results, query, assistants)
|
||||
: results;
|
||||
|
||||
setResultsState({ results: aiEnrichedResults, fetching: false, error: false });
|
||||
setResultsState({ results, fetching: false, error: false });
|
||||
|
||||
trackEvent({
|
||||
type: 'search_type_query',
|
||||
@@ -196,9 +175,16 @@ export function useSearchResults(props: {
|
||||
cancelled = true;
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [query, scope, trackEvent, withAI, siteSpaceId, siteSpaceIds, disabled, suggestions]);
|
||||
}, [query, scope, trackEvent, withAI, siteSpaceId, siteSpaceIds, disabled]);
|
||||
|
||||
return resultsState;
|
||||
const aiEnrichedResults: ResultType[] = React.useMemo(() => {
|
||||
if (!withAI) {
|
||||
return resultsState.results;
|
||||
}
|
||||
return withAskTriggers(resultsState.results, query, assistants);
|
||||
}, [resultsState.results, query, withAI]);
|
||||
|
||||
return { ...resultsState, results: aiEnrichedResults };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,7 +56,6 @@ export async function SiteLayout(props: {
|
||||
>
|
||||
<AIContextProvider
|
||||
aiMode={customization.ai?.mode}
|
||||
suggestions={context.customization.ai?.suggestions}
|
||||
trademark={customization.trademark.enabled}
|
||||
>
|
||||
<SpaceLayout
|
||||
@@ -101,7 +100,6 @@ export async function generateSiteLayoutViewport(context: GitBookSiteContext): P
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
viewportFit: 'cover',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export type PageMetaLinks = {
|
||||
/**
|
||||
* Fetch and render a page.
|
||||
*/
|
||||
export async function SitePage(props: SitePageProps & { staticRoute: boolean }) {
|
||||
export async function SitePage(props: SitePageProps) {
|
||||
const {
|
||||
context,
|
||||
page,
|
||||
@@ -99,7 +99,6 @@ export async function SitePage(props: SitePageProps & { staticRoute: boolean })
|
||||
document={document}
|
||||
withPageFeedback={withPageFeedback}
|
||||
insightsDisplayContext={SiteInsightsDisplayContext.Site}
|
||||
staticRoute={props.staticRoute}
|
||||
/>
|
||||
</div>
|
||||
<PageClientLayout pageMetaLinks={pageMetaLinks} />
|
||||
@@ -297,7 +296,7 @@ async function getPageDataWithFallback(args: {
|
||||
}) {
|
||||
const { context: baseContext, pagePathParams } = args;
|
||||
const { context, pageTarget } = await fetchPageData(baseContext, pagePathParams);
|
||||
const pageMetaLinks = await (pageTarget?.page
|
||||
const pageMetaLinks = await (pageTarget?.page && shouldResolveMetaLinks(context.site.id)
|
||||
? resolvePageMetaLinks(context, pageTarget.page.id)
|
||||
: null);
|
||||
|
||||
@@ -359,6 +358,29 @@ async function resolvePageMetaLinks(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to resolve meta links for a site based on a percentage rollout.
|
||||
*/
|
||||
function shouldResolveMetaLinks(siteId: string): boolean {
|
||||
const META_LINKS_PERCENTAGE_ROLLOUT = 25;
|
||||
const ALLOWED_SITES: Record<string, boolean> = {
|
||||
site_CZrtk: true,
|
||||
};
|
||||
|
||||
if (ALLOWED_SITES[siteId] || process.env.NODE_ENV === 'development') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// compute a simple hash of the siteId
|
||||
let hash = 0;
|
||||
for (let i = 0; i < siteId.length; i++) {
|
||||
hash = (hash << 5) - hash + siteId.charCodeAt(i);
|
||||
hash = hash & hash; // Convert to 32-bit integer
|
||||
}
|
||||
|
||||
return Math.abs(hash % 100) < META_LINKS_PERCENTAGE_ROLLOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <title> for a page.
|
||||
*/
|
||||
|
||||
@@ -40,7 +40,7 @@ export function SiteSectionList(props: { sections: ClientSiteSections; className
|
||||
orientation="vertical"
|
||||
style={{ maxHeight: `${MAX_ITEMS * 3 + 2}rem` }}
|
||||
className="pb-4"
|
||||
active={`#${currentSection.id}`}
|
||||
active={currentSection.id}
|
||||
>
|
||||
<div className="flex w-full flex-col px-2">
|
||||
{sectionsAndGroups.map((item) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
} from './encodeClientSiteSections';
|
||||
|
||||
const SCREEN_OFFSET = 16; // 1rem
|
||||
const MAX_ITEMS_PER_COLUMN = 8; // number of items per column
|
||||
const MAX_ITEMS_PER_COLUMN = 5; // number of items per column
|
||||
/**
|
||||
* A set of navigational links representing site sections for multi-section sites
|
||||
*/
|
||||
@@ -76,12 +76,8 @@ export function SiteSectionTabs(props: {
|
||||
? 'md:-mr-8 -mr-4 sm:-mr-6'
|
||||
: 'after:contents[] after:absolute after:inset-y-2 after:right-0 after:border-transparent after:border-r after:transition-colors'
|
||||
)}
|
||||
active={`#${currentSection.id}`}
|
||||
trailing={{
|
||||
fade: true,
|
||||
button: true,
|
||||
className: children ? 'after:border-tint' : '',
|
||||
}}
|
||||
active={currentSection.id}
|
||||
trailingEdgeScrollClassName={children ? 'after:border-tint' : ''}
|
||||
>
|
||||
<NavigationMenu.List
|
||||
className={tcls(
|
||||
@@ -227,7 +223,7 @@ function SectionGroupTileList(props: {
|
||||
{hasSections && (
|
||||
<ul
|
||||
className={tcls(
|
||||
'flex min-w-48 grid-flow-row flex-col gap-x-2 gap-y-1 self-start p-3 md:grid',
|
||||
'flex grid-flow-row flex-col gap-x-2 gap-y-1 p-2 md:grid',
|
||||
hasGroups ? 'bg-tint-base' : ''
|
||||
)}
|
||||
style={{
|
||||
@@ -248,7 +244,7 @@ function SectionGroupTileList(props: {
|
||||
{hasGroups && (
|
||||
<ul
|
||||
className={tcls(
|
||||
'flex min-w-48 flex-col items-start justify-start gap-x-8 gap-y-2 p-3 md:flex-row md:gap-y-8',
|
||||
'flex grid-flow-col flex-col items-start gap-x-2 gap-y-4 p-2 md:grid md:gap-y-1',
|
||||
hasSections
|
||||
? 'border-tint-subtle bg-tint-subtle max-md:border-t md:border-l'
|
||||
: ''
|
||||
@@ -280,7 +276,7 @@ function SectionGroupTile(props: {
|
||||
const { url, icon, title, description } = child;
|
||||
const isActive = child.id === currentSection.id;
|
||||
return (
|
||||
<li className="group/section-tile flex shrink-0 grow md:max-w-68">
|
||||
<li className="group/section-tile flex w-full shrink grow md:w-68">
|
||||
<Link
|
||||
href={url}
|
||||
className={tcls(
|
||||
@@ -321,7 +317,7 @@ function SectionGroupTile(props: {
|
||||
const { title, icon, children } = child;
|
||||
|
||||
return (
|
||||
<li className="flex shrink-0 flex-col gap-1">
|
||||
<li className="flex w-full shrink grow flex-col gap-1">
|
||||
<div className="mt-3 mb-2 flex gap-2.5 px-3 font-semibold text-tint-subtle text-xs uppercase tracking-wider">
|
||||
{icon && (
|
||||
<SectionIcon className="mt-0.5" isActive={false} icon={icon as IconName} />
|
||||
@@ -331,7 +327,7 @@ function SectionGroupTile(props: {
|
||||
<ul
|
||||
className="flex grid-flow-row flex-col gap-x-2 gap-y-1 md:grid"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${Math.ceil(children.length / MAX_ITEMS_PER_COLUMN)}, minmax(0, auto))`,
|
||||
gridTemplateColumns: `repeat(${Math.ceil(children.length / MAX_ITEMS_PER_COLUMN)}, minmax(0, 1fr))`,
|
||||
}}
|
||||
>
|
||||
{children.map((nestedChild) => (
|
||||
|
||||
@@ -128,7 +128,7 @@ export function SpaceLayout(props: SpaceLayoutProps) {
|
||||
'lg:justify-center',
|
||||
CONTAINER_STYLE,
|
||||
'site-width-wide:max-w-screen-4xl',
|
||||
'transition-[max-width] duration-300',
|
||||
'hydrated:transition-[max-width] duration-300',
|
||||
|
||||
// Ensure the footer is display below the viewport even if the content is not enough
|
||||
withFooter && [
|
||||
@@ -141,82 +141,79 @@ export function SpaceLayout(props: SpaceLayoutProps) {
|
||||
<TableOfContents
|
||||
context={context}
|
||||
header={
|
||||
<div
|
||||
className={tcls(
|
||||
'pr-4',
|
||||
'flex',
|
||||
withTopHeader ? 'lg:hidden' : '',
|
||||
'grow-0',
|
||||
'dark:shadow-light/1',
|
||||
'text-base/tight',
|
||||
'items-center'
|
||||
)}
|
||||
>
|
||||
<HeaderLogo context={context} />
|
||||
{variants.translations.length > 1 ? (
|
||||
<TranslationsDropdown
|
||||
context={context}
|
||||
siteSpace={
|
||||
variants.translations.find(
|
||||
(space) => space.id === siteSpace.id
|
||||
) ?? siteSpace
|
||||
}
|
||||
siteSpaces={variants.translations}
|
||||
className="[&_.button-leading-icon]:block! ml-auto py-2 [&_.button-content]:hidden"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
withTopHeader ? null : (
|
||||
<div
|
||||
className={tcls(
|
||||
'hidden',
|
||||
'pr-4',
|
||||
'mt-2',
|
||||
'lg:flex',
|
||||
'grow-0',
|
||||
'dark:shadow-light/1',
|
||||
'text-base/tight',
|
||||
'items-center'
|
||||
)}
|
||||
>
|
||||
<HeaderLogo context={context} />
|
||||
{variants.translations.length > 1 ? (
|
||||
<TranslationsDropdown
|
||||
context={context}
|
||||
siteSpace={
|
||||
variants.translations.find(
|
||||
(space) => space.id === siteSpace.id
|
||||
) ?? siteSpace
|
||||
}
|
||||
siteSpaces={variants.translations}
|
||||
className="[&_.button-leading-icon]:block! ml-auto py-2 [&_.button-content]:hidden"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// Displays the search button and/or the space dropdown in the ToC
|
||||
// according to the header/variant settings.
|
||||
// E.g if there is no header, the search button will be displayed in the ToC.
|
||||
innerHeader={
|
||||
!withTopHeader || variants.generic.length > 1 ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'my-5 sidebar-default:mt-2 flex flex-col gap-2 px-5 empty:hidden',
|
||||
variants.generic.length > 1 ? '' : 'max-lg:hidden'
|
||||
)}
|
||||
>
|
||||
{!withTopHeader && (
|
||||
<div className="flex gap-2 max-lg:hidden">
|
||||
<SearchContainer
|
||||
style={CustomizationSearchStyle.Subtle}
|
||||
withVariants={variants.generic.length > 1}
|
||||
withSiteVariants={
|
||||
visibleSections?.list.some(
|
||||
(s) =>
|
||||
s.object === 'site-section' &&
|
||||
s.siteSpaces.length > 1
|
||||
) ?? false
|
||||
}
|
||||
withSections={withSections}
|
||||
section={visibleSections?.current}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={visibleSiteSpaces}
|
||||
viewport="desktop"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!withTopHeader && withSections && visibleSections && (
|
||||
<SiteSectionList
|
||||
className="hidden lg:block"
|
||||
sections={encodeClientSiteSections(
|
||||
context,
|
||||
visibleSections
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{variants.generic.length > 1 ? (
|
||||
<SpacesDropdown
|
||||
context={context}
|
||||
<>
|
||||
{!withTopHeader && (
|
||||
<div className="flex gap-2">
|
||||
<SearchContainer
|
||||
style={CustomizationSearchStyle.Subtle}
|
||||
withVariants={variants.generic.length > 1}
|
||||
withSiteVariants={
|
||||
visibleSections?.list.some(
|
||||
(s) =>
|
||||
s.object === 'site-section' &&
|
||||
s.siteSpaces.length > 1
|
||||
) ?? false
|
||||
}
|
||||
withSections={withSections}
|
||||
section={visibleSections?.current}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={variants.generic}
|
||||
className="w-full px-3"
|
||||
siteSpaces={visibleSiteSpaces}
|
||||
className="max-lg:hidden"
|
||||
viewport="desktop"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
</div>
|
||||
)}
|
||||
{!withTopHeader && withSections && visibleSections && (
|
||||
<SiteSectionList
|
||||
className={tcls('hidden', 'lg:block')}
|
||||
sections={encodeClientSiteSections(
|
||||
context,
|
||||
visibleSections
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{variants.generic.length > 1 ? (
|
||||
<SpacesDropdown
|
||||
context={context}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={variants.generic}
|
||||
className="w-full px-3 py-2"
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{children}
|
||||
|
||||
@@ -32,8 +32,7 @@ export function PageDocumentItem(props: { page: ClientTOCPageDocument }) {
|
||||
'my-2',
|
||||
'border-tint-subtle',
|
||||
'sidebar-list-default:border-l',
|
||||
'sidebar-list-line:border-l',
|
||||
'break-anywhere'
|
||||
'sidebar-list-line:border-l'
|
||||
)}
|
||||
/>
|
||||
) : null
|
||||
|
||||
@@ -14,9 +14,9 @@ export function PageGroupItem(props: { page: ClientTOCPageGroup; isFirst?: boole
|
||||
<li className="flex flex-col">
|
||||
<div
|
||||
className={tcls(
|
||||
'-top-4 sticky z-1 flex items-center gap-3 px-3',
|
||||
'-top-6 sticky z-1 flex items-center gap-3 px-3 pt-6',
|
||||
'font-semibold text-xs uppercase tracking-wide',
|
||||
'mt-2 pt-4 pb-3', // Add extra padding to make the header fade a bit nicer
|
||||
'pb-3', // Add extra padding to make the header fade a bit nicer
|
||||
'-mb-1.5', // Then pull the page items a bit closer, effective bottom padding is 1.5 units / 6px.
|
||||
'mask-[linear-gradient(rgba(0,0,0,1)_70%,rgba(0,0,0,0))]', // Fade out effect of fixed page items. We want the fade to start past the header, this is a good approximation.
|
||||
'bg-tint-base',
|
||||
@@ -25,9 +25,9 @@ export function PageGroupItem(props: { page: ClientTOCPageGroup; isFirst?: boole
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
|
||||
'[html.sidebar-filled.theme-muted_&]:bg-tint-base',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-base',
|
||||
'lg:[html.sidebar-default.theme-gradient_&]:bg-gradient-primary',
|
||||
'lg:[html.sidebar-default.theme-gradient.tint_&]:bg-gradient-tint',
|
||||
isFirst ? '-mt-4' : ''
|
||||
'[html.sidebar-default.theme-gradient_&]:bg-gradient-primary',
|
||||
'[html.sidebar-default.theme-gradient.tint_&]:bg-gradient-tint',
|
||||
isFirst ? '-mt-6' : ''
|
||||
)}
|
||||
>
|
||||
<TOCPageIcon page={page} />
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
} from 'react';
|
||||
import { assert } from 'ts-essentials';
|
||||
|
||||
interface TOCScrollContainerContextType {
|
||||
getContainer: (listener: (element: HTMLDivElement) => void) => () => void;
|
||||
}
|
||||
|
||||
const TOCScrollContainerContext = React.createContext<TOCScrollContainerContextType | null>(null);
|
||||
|
||||
function useTOCScrollContainerContext() {
|
||||
const ctx = React.useContext(TOCScrollContainerContext);
|
||||
assert(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table of contents scroll container.
|
||||
*/
|
||||
export function TOCScrollContainer(props: ComponentPropsWithoutRef<'div'>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const listeners = useRef<((element: HTMLDivElement) => void)[]>([]);
|
||||
const getContainer: TOCScrollContainerContextType['getContainer'] = useCallback((listener) => {
|
||||
if (ref.current) {
|
||||
listener(ref.current);
|
||||
return () => {};
|
||||
}
|
||||
listeners.current.push(listener);
|
||||
return () => {
|
||||
listeners.current = listeners.current.filter((l) => l !== listener);
|
||||
};
|
||||
}, []);
|
||||
const value: TOCScrollContainerContextType = useMemo(() => ({ getContainer }), [getContainer]);
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
listeners.current.forEach((listener) => listener(element));
|
||||
return () => {
|
||||
listeners.current = [];
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TOCScrollContainerContext.Provider value={value}>
|
||||
<div ref={ref} data-testid="toc-scroll-container" {...props} />
|
||||
</TOCScrollContainerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Offset to scroll the table of contents item by.
|
||||
const TOC_ITEM_OFFSET = 100;
|
||||
|
||||
/**
|
||||
* Scrolls the table of contents container to the page item when it's initially active.
|
||||
*/
|
||||
export function useScrollToActiveTOCItem(props: {
|
||||
anchorRef: React.RefObject<HTMLAnchorElement | null>;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
const { isActive, anchorRef } = props;
|
||||
const { getContainer } = useTOCScrollContainerContext();
|
||||
useEffect(() => {
|
||||
const anchor = anchorRef.current;
|
||||
if (isActive && anchor) {
|
||||
return getContainer((container) => {
|
||||
if (isOutOfView(anchor, container)) {
|
||||
container.scrollTo({ top: anchor.offsetTop - TOC_ITEM_OFFSET });
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [isActive, getContainer, anchorRef]);
|
||||
}
|
||||
|
||||
function isOutOfView(element: HTMLElement, container: HTMLElement) {
|
||||
const tocItemTop = element.offsetTop;
|
||||
const containerTop = container.scrollTop;
|
||||
const containerBottom = containerTop + container.clientHeight;
|
||||
return (
|
||||
tocItemTop < containerTop + TOC_ITEM_OFFSET ||
|
||||
tocItemTop > containerBottom - TOC_ITEM_OFFSET
|
||||
);
|
||||
}
|
||||
@@ -3,16 +3,12 @@ import { SiteInsightsTrademarkPlacement } from '@gitbook/api';
|
||||
import type React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { ScrollContainer } from '../primitives/ScrollContainer';
|
||||
import { SideSheet } from '../primitives/SideSheet';
|
||||
import { PagesList } from './PagesList';
|
||||
import { TOCScrollContainer } from './TOCScroller';
|
||||
import { TableOfContentsScript } from './TableOfContentsScript';
|
||||
import { Trademark } from './Trademark';
|
||||
import { encodeClientTableOfContents } from './encodeClientTableOfContents';
|
||||
|
||||
/**
|
||||
* Sidebar container, responsible for setting the right dimensions and position for the sidebar.
|
||||
*/
|
||||
export async function TableOfContents(props: {
|
||||
context: GitBookSiteContext;
|
||||
header?: React.ReactNode; // Displayed outside the scrollable TOC as a sticky header
|
||||
@@ -26,38 +22,23 @@ export async function TableOfContents(props: {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SideSheet
|
||||
side="left"
|
||||
<aside // Sidebar container, responsible for setting the right dimensions and position for the sidebar.
|
||||
data-testid="table-of-contents"
|
||||
id="table-of-contents"
|
||||
toggleClass="navigation-open"
|
||||
withScrim={true}
|
||||
withCloseButton={true}
|
||||
className={tcls(
|
||||
'group/table-of-contents',
|
||||
'group',
|
||||
'text-sm',
|
||||
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
'basis-full',
|
||||
'lg:basis-72',
|
||||
'lg:page-no-toc:basis-56',
|
||||
|
||||
'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))]',
|
||||
|
||||
'max-lg:not-sidebar-filled:bg-tint-base',
|
||||
'max-lg:not-sidebar-filled:border-r',
|
||||
'border-tint-subtle',
|
||||
|
||||
'lg:flex!',
|
||||
'lg:animate-none!',
|
||||
'relative',
|
||||
'z-1',
|
||||
'lg:sticky',
|
||||
'lg:mr-12',
|
||||
// 'lg:z-0!',
|
||||
'lg:z-20',
|
||||
|
||||
// Server-side static positioning
|
||||
'lg:top-0',
|
||||
@@ -79,23 +60,30 @@ export async function TableOfContents(props: {
|
||||
'lg:page-no-toc:[html[style*="--outline-top-offset"]_&]:top-(--outline-top-offset)!',
|
||||
'lg:page-no-toc:[html[style*="--outline-height"]_&]:top-(--outline-height)!',
|
||||
|
||||
'pt-6 pb-4',
|
||||
'pt-4',
|
||||
'pb-4',
|
||||
'lg:sidebar-filled:pr-6',
|
||||
'lg:page-no-toc:pr-0',
|
||||
|
||||
'hidden',
|
||||
'navigation-open:flex!',
|
||||
'lg:flex',
|
||||
'lg:page-no-toc:hidden',
|
||||
'xl:page-no-toc:flex',
|
||||
'lg:site-header-none:page-no-toc:flex',
|
||||
'flex-col',
|
||||
'gap-4',
|
||||
|
||||
'navigation-open:border-b',
|
||||
'border-tint-subtle',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{header ? header : null}
|
||||
{header && header}
|
||||
<div // The actual sidebar, either shown with a filled bg or transparent.
|
||||
className={tcls(
|
||||
'-ms-5',
|
||||
'relative flex min-h-0 grow flex-col border-tint-subtle',
|
||||
'lg:-ms-5',
|
||||
'relative flex grow flex-col overflow-hidden border-tint-subtle',
|
||||
|
||||
'sidebar-filled:bg-tint-subtle',
|
||||
'theme-muted:bg-tint-subtle',
|
||||
@@ -103,7 +91,6 @@ export async function TableOfContents(props: {
|
||||
'[html.sidebar-filled.theme-muted_&]:bg-tint-base',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-base',
|
||||
'[html.sidebar-filled.theme-gradient_&]:border',
|
||||
'max-lg:sidebar-filled:border',
|
||||
'page-no-toc:bg-transparent!',
|
||||
'page-no-toc:border-none!',
|
||||
|
||||
@@ -112,33 +99,32 @@ export async function TableOfContents(props: {
|
||||
'page-has-toc:[html.sidebar-filled.circular-corners_&]:rounded-3xl'
|
||||
)}
|
||||
>
|
||||
{innerHeader ? innerHeader : null}
|
||||
<ScrollContainer
|
||||
data-testid="toc-scroll-container"
|
||||
orientation="vertical"
|
||||
contentClassName="flex flex-col p-2 gutter-stable"
|
||||
active="[data-active=true]"
|
||||
leading={{
|
||||
fade: true,
|
||||
button: {
|
||||
className: '-mt-4',
|
||||
},
|
||||
}}
|
||||
{innerHeader ? (
|
||||
<div className="my-4 flex flex-col space-y-4 px-5 empty:hidden">
|
||||
{innerHeader}
|
||||
</div>
|
||||
) : null}
|
||||
<TOCScrollContainer // The scrollview inside the sidebar
|
||||
className={tcls(
|
||||
'flex grow flex-col p-2 pt-4',
|
||||
customization.trademark.enabled && 'lg:pb-20',
|
||||
'hide-scrollbar overflow-y-auto'
|
||||
)}
|
||||
>
|
||||
<PagesList
|
||||
pages={pages}
|
||||
isRoot={true}
|
||||
style="page-no-toc:hidden grow border-tint-subtle sidebar-list-line:border-l"
|
||||
style="page-no-toc:hidden border-tint-subtle sidebar-list-line:border-l"
|
||||
/>
|
||||
</ScrollContainer>
|
||||
{customization.trademark.enabled ? (
|
||||
<Trademark
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Sidebar}
|
||||
/>
|
||||
) : null}
|
||||
{customization.trademark.enabled ? (
|
||||
<Trademark
|
||||
context={context}
|
||||
placement={SiteInsightsTrademarkPlacement.Sidebar}
|
||||
/>
|
||||
) : null}
|
||||
</TOCScrollContainer>
|
||||
</div>
|
||||
</SideSheet>
|
||||
</aside>
|
||||
<TableOfContentsScript />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { useCurrentPagePath } from '../hooks';
|
||||
import { Link, type LinkInsightsProps, type LinkProps } from '../primitives';
|
||||
import { useScrollToActiveTOCItem } from './TOCScroller';
|
||||
|
||||
/**
|
||||
* Client component for a page document to toggle its children and be marked as active.
|
||||
@@ -80,6 +81,8 @@ function LinkItem(
|
||||
}
|
||||
) {
|
||||
const { isActive, href, insights, children, onActiveClick } = props;
|
||||
const anchorRef = useRef<HTMLAnchorElement>(null);
|
||||
useScrollToActiveTOCItem({ anchorRef, isActive });
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
if (isActive && onActiveClick) {
|
||||
@@ -90,7 +93,7 @@ function LinkItem(
|
||||
|
||||
return (
|
||||
<Link
|
||||
data-active={isActive}
|
||||
ref={anchorRef}
|
||||
href={href}
|
||||
insights={insights}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
|
||||
@@ -19,7 +19,46 @@ export function Trademark(props: {
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'mt-auto circular-corners:rounded-2xl rounded-lg straight-corners:rounded-none px-2 sidebar-default:pr-4 pb-2',
|
||||
'relative',
|
||||
'z-2',
|
||||
'lg:absolute',
|
||||
|
||||
'left-0',
|
||||
'right-2',
|
||||
'bottom-0',
|
||||
|
||||
'pointer-events-none',
|
||||
'sidebar-filled:pl-2',
|
||||
'sidebar-filled:pb-2',
|
||||
'sidebar-filled:page-no-toc:p-0',
|
||||
|
||||
'bg-tint-base',
|
||||
'sidebar-filled:bg-tint-subtle',
|
||||
'theme-muted:bg-tint-subtle',
|
||||
'[html.sidebar-filled.theme-muted_&]:bg-tint-base',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:bg-tint-base',
|
||||
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
'circular-corners:rounded-2xl',
|
||||
|
||||
'before:hidden',
|
||||
'lg:before:block',
|
||||
'before:content-[""]',
|
||||
'before:absolute',
|
||||
'before:inset-x-0',
|
||||
'before:bottom-full',
|
||||
'before:h-8',
|
||||
'before:bg-linear-to-b',
|
||||
'before:from-transparent',
|
||||
'before:to-tint-base',
|
||||
'sidebar-filled:before:to-tint-subtle',
|
||||
'theme-muted:before:to-tint-subtle',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:before:to-tint-subtle',
|
||||
'[html.sidebar-filled.theme-muted_&]:before:to-tint-base',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:before:to-tint-base',
|
||||
'page-no-toc:before:to-transparent!',
|
||||
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -62,12 +101,16 @@ export function TrademarkLink(props: {
|
||||
|
||||
'sidebar-filled:px-3',
|
||||
'lg:sidebar-filled:page-no-toc:px-5',
|
||||
'lg:page-no-toc:bg-tint-base',
|
||||
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
'circular-corners:rounded-2xl',
|
||||
|
||||
'hover:bg-tint',
|
||||
'hover:text-tint-strong',
|
||||
|
||||
'ring-1',
|
||||
'ring-2',
|
||||
'lg:ring-1',
|
||||
'ring-inset',
|
||||
'ring-tint-subtle',
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { TableOfContents } from './TableOfContents';
|
||||
export { PagesList } from './PagesList';
|
||||
export { TOCScrollContainer } from './TOCScroller';
|
||||
export { Trademark } from './Trademark';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ToggleableLinkItemStyles = [
|
||||
'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong',
|
||||
'contrast-more:hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-tint-12',
|
||||
'before:contents[] before:-left-px before:absolute before:inset-y-0',
|
||||
'sidebar-list-line:rounded-l-none! sidebar-list-line:before:w-px [&+div_a]:sidebar-list-default:rounded-l-none [&+div_a]:pl-5 [&+div_a]:sidebar-list-default:before:w-px',
|
||||
'sidebar-list-line:rounded-l-none sidebar-list-line:before:w-px [&+div_a]:sidebar-list-default:rounded-l-none [&+div_a]:pl-5 [&+div_a]:sidebar-list-default:before:w-px',
|
||||
];
|
||||
|
||||
export const ToggleableLinkItemActiveStyles = [
|
||||
|
||||
@@ -27,11 +27,7 @@ export function ThemeToggler() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
role="radiogroup"
|
||||
className="xl:page-no-toc:-m-2 gap-2 circular-corners:rounded-full bg-tint-base xl:page-no-toc:p-2"
|
||||
combinedShape={false}
|
||||
>
|
||||
<ButtonGroup role="radiogroup" className="gap-2" combinedShape={false}>
|
||||
<ThemeButton
|
||||
active={mounted && theme === 'light'}
|
||||
icon="sun-bright"
|
||||
@@ -70,7 +66,11 @@ function ThemeButton(props: {
|
||||
aria-checked={active}
|
||||
variant="blank"
|
||||
size="default"
|
||||
active={active}
|
||||
className={
|
||||
active
|
||||
? 'bg-primary theme-muted:bg-primary-hover text-primary-strong ring-primary hover:bg-primary contrast-more:text-primary-strong contrast-more:ring-1 [html.sidebar-filled.theme-bold.tint_&]:bg-primary-hover'
|
||||
: ''
|
||||
}
|
||||
icon={icon}
|
||||
iconOnly
|
||||
/>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Hook to manage a controlled state.
|
||||
*
|
||||
* From https://github.com/adobe/react-spectrum/blob/main/packages/%40react-stately/utils/src/useControlledState.ts
|
||||
*/
|
||||
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
// Use the earliest effect possible to reset the ref below.
|
||||
const useEarlyEffect: typeof React.useLayoutEffect =
|
||||
typeof document !== 'undefined' ? React.useInsertionEffect || React.useLayoutEffect : () => {};
|
||||
|
||||
export function useControlledState<T, C = T>(
|
||||
value: Exclude<T, undefined>,
|
||||
defaultValue: Exclude<T, undefined> | undefined,
|
||||
onChange?: (v: C, ...args: any[]) => void
|
||||
): [T, (value: SetStateAction<T>, ...args: any[]) => void];
|
||||
export function useControlledState<T, C = T>(
|
||||
value: Exclude<T, undefined> | undefined,
|
||||
defaultValue: Exclude<T, undefined>,
|
||||
onChange?: (v: C, ...args: any[]) => void
|
||||
): [T, (value: SetStateAction<T>, ...args: any[]) => void];
|
||||
export function useControlledState<T, C = T>(
|
||||
value: T,
|
||||
defaultValue: T,
|
||||
onChange?: (v: C, ...args: any[]) => void
|
||||
): [T, (value: SetStateAction<T>, ...args: any[]) => void] {
|
||||
// Store the value in both state and a ref. The state value will only be used when uncontrolled.
|
||||
// The ref is used to track the most current value, which is passed to the function setState callback.
|
||||
const [stateValue, setStateValue] = useState(value || defaultValue);
|
||||
const valueRef = useRef(stateValue);
|
||||
|
||||
const isControlledRef = useRef(value !== undefined);
|
||||
const isControlled = value !== undefined;
|
||||
useEffect(() => {
|
||||
const wasControlled = isControlledRef.current;
|
||||
if (wasControlled !== isControlled && process.env.NODE_ENV !== 'production') {
|
||||
console.warn(
|
||||
`WARN: A component changed from ${wasControlled ? 'controlled' : 'uncontrolled'} to ${isControlled ? 'controlled' : 'uncontrolled'}.`
|
||||
);
|
||||
}
|
||||
isControlledRef.current = isControlled;
|
||||
}, [isControlled]);
|
||||
|
||||
// After each render, update the ref to the current value.
|
||||
// This ensures that the setState callback argument is reset.
|
||||
// Note: the effect should not have any dependencies so that controlled values always reset.
|
||||
const currentValue = isControlled ? value : stateValue;
|
||||
useEarlyEffect(() => {
|
||||
valueRef.current = currentValue;
|
||||
});
|
||||
|
||||
const [, forceUpdate] = useReducer(() => ({}), {});
|
||||
const setValue = useCallback(
|
||||
(value: SetStateAction<T>, ...args: any[]) => {
|
||||
// @ts-ignore - TS doesn't know that T cannot be a function.
|
||||
const newValue = typeof value === 'function' ? value(valueRef.current) : value;
|
||||
if (!Object.is(valueRef.current, newValue)) {
|
||||
// Update the ref so that the next setState callback has the most recent value.
|
||||
valueRef.current = newValue;
|
||||
|
||||
setStateValue(newValue);
|
||||
|
||||
// Always trigger a re-render, even when controlled, so that the layout effect above runs to reset the value.
|
||||
forceUpdate();
|
||||
|
||||
// Trigger onChange. Note that if setState is called multiple times in a single event,
|
||||
// onChange will be called for each one instead of only once.
|
||||
onChange?.(newValue, ...args);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return [currentValue, setValue];
|
||||
}
|
||||
@@ -9,9 +9,9 @@ export const HEADER_HEIGHT_DESKTOP = 64 as const;
|
||||
* Style for the container to adapt between normal and full width.
|
||||
*/
|
||||
export const CONTAINER_STYLE: ClassValue = [
|
||||
'px-4 pl-[max(env(safe-area-inset-left),1rem)] pr-[max(env(safe-area-inset-right),1rem)]',
|
||||
'sm:px-6 sm:pl-[max(env(safe-area-inset-left),1.5rem)] sm:pr-[max(env(safe-area-inset-right),1.5rem)]',
|
||||
'md:px-8 md:pl-[max(env(safe-area-inset-left),2rem)] md:pr-[max(env(safe-area-inset-right),2rem)]',
|
||||
'px-4',
|
||||
'sm:px-6',
|
||||
'md:px-8',
|
||||
'max-w-screen-2xl',
|
||||
'mx-auto',
|
||||
];
|
||||
|
||||
@@ -28,7 +28,7 @@ export function HoverCard(
|
||||
<RadixHoverCard.Portal>
|
||||
<RadixHoverCard.Content
|
||||
side={props.side ?? 'top'}
|
||||
className="pointer-events-none z-50 w-screen max-w-md animate-scale-in px-4 data-[state='closed']:animate-scale-out sm:w-auto"
|
||||
className="pointer-events-none z-40 w-screen max-w-md animate-scale-in px-4 data-[state='closed']:animate-scale-out sm:w-auto"
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import React, { type ReactNode } from 'react';
|
||||
import { useControlledState } from '../hooks/useControlledState';
|
||||
import { Button, type ButtonProps } from './Button';
|
||||
import { KeyboardShortcut, type KeyboardShortcutProps } from './KeyboardShortcut';
|
||||
|
||||
type CustomInputProps = {
|
||||
label: string;
|
||||
inline?: boolean;
|
||||
leading?: IconName | React.ReactNode;
|
||||
trailing?: React.ReactNode;
|
||||
sizing?: 'medium' | 'large'; // The `size` prop is already taken by the HTML input element.
|
||||
containerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
/**
|
||||
* A submit button, shown to the right of the input.
|
||||
*/
|
||||
submitButton?: boolean | ButtonProps;
|
||||
/**
|
||||
* A message to be shown to the right of the input when the value has been submitted.
|
||||
*/
|
||||
submitMessage?: string | ReactNode;
|
||||
/**
|
||||
* A clear button, shown to the left of the input.
|
||||
*/
|
||||
clearButton?: boolean | ButtonProps;
|
||||
/**
|
||||
* A keyboard shortcut, shown to the right of the input.
|
||||
*/
|
||||
keyboardShortcut?: boolean | KeyboardShortcutProps;
|
||||
|
||||
onSubmit?: (value: string | number | readonly string[] | undefined) => void;
|
||||
resize?: boolean;
|
||||
};
|
||||
|
||||
export type InputProps = CustomInputProps &
|
||||
(
|
||||
| ({ multiline?: false } & React.InputHTMLAttributes<HTMLInputElement>)
|
||||
| ({ multiline: true } & React.TextareaHTMLAttributes<HTMLTextAreaElement>)
|
||||
);
|
||||
|
||||
type InputElement = HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
/**
|
||||
* Input component with core functionality (submitting, clearing, validating) and shared styles.
|
||||
*/
|
||||
export const Input = React.forwardRef<InputElement, InputProps>((props, passedRef) => {
|
||||
const {
|
||||
// Custom props
|
||||
multiline,
|
||||
sizing = 'medium',
|
||||
inline = false,
|
||||
leading,
|
||||
trailing,
|
||||
className,
|
||||
clearButton,
|
||||
submitButton,
|
||||
submitMessage,
|
||||
label,
|
||||
keyboardShortcut,
|
||||
onSubmit,
|
||||
containerRef,
|
||||
resize = false,
|
||||
// HTML attributes we need to read
|
||||
value: passedValue,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-busy': ariaBusy,
|
||||
placeholder,
|
||||
disabled,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
maxLength,
|
||||
minLength,
|
||||
// Rest are HTML attributes to pass through
|
||||
...htmlProps
|
||||
} = props;
|
||||
|
||||
const [value, setValue] = useControlledState(passedValue, passedValue ?? '');
|
||||
const [submitted, setSubmitted] = React.useState(false);
|
||||
const [height, setHeight] = React.useState<number>();
|
||||
const inputRef = React.useRef<InputElement>(null);
|
||||
const ref = (passedRef as React.RefObject<HTMLInputElement | HTMLTextAreaElement>) ?? inputRef;
|
||||
|
||||
const language = useLanguage();
|
||||
const hasValue = value.toString().trim().length > 0;
|
||||
const hasValidValue =
|
||||
hasValue &&
|
||||
(maxLength ? value.toString().length <= maxLength : true) &&
|
||||
(minLength ? value.toString().length >= minLength : true);
|
||||
|
||||
const sizes = {
|
||||
medium: {
|
||||
container: `${multiline ? 'p-2' : 'px-4 py-2'} gap-2 circular-corners:rounded-3xl rounded-corners:rounded-xl`,
|
||||
input: '-m-2 p-2',
|
||||
gap: 'gap-2',
|
||||
},
|
||||
large: {
|
||||
container: `${multiline ? 'p-3' : 'px-6 py-3 '} gap-3 circular-corners:rounded-3xl rounded-corners:rounded-xl`,
|
||||
input: '-m-3 p-3',
|
||||
gap: 'gap-3',
|
||||
},
|
||||
};
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<InputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
setValue(newValue);
|
||||
onChange?.(event as React.ChangeEvent<HTMLInputElement & HTMLTextAreaElement>);
|
||||
|
||||
// Reset submitted state when user edits the value to allow re-submission
|
||||
if (submitted) {
|
||||
setSubmitted(false);
|
||||
}
|
||||
|
||||
if (multiline && resize && ref.current) {
|
||||
// TODO: replace with `field-sizing: content` when more broadly supported. https://caniuse.com/?search=field-sizing
|
||||
// Reset the height to auto, then set it to the scroll height. If we don't reset, the height will only ever grow.
|
||||
setHeight(ref.current.scrollHeight);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
if (!ref.current) return;
|
||||
setValue('');
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
ref.current?.focus();
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (hasValue && onSubmit) {
|
||||
onSubmit(value);
|
||||
setSubmitted(true);
|
||||
setValue('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<InputElement>) => {
|
||||
onKeyDown?.(event as React.KeyboardEvent<HTMLInputElement & HTMLTextAreaElement>);
|
||||
// If the user wants to handle the keydown by itself, we let him do it.
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey && hasValue) {
|
||||
event.preventDefault();
|
||||
handleSubmit();
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const inputClassName = tcls(
|
||||
'peer -m-2 max-h-64 grow resize-none text-left outline-none placeholder:text-tint/8 aria-busy:cursor-progress',
|
||||
sizes[sizing].input
|
||||
);
|
||||
|
||||
const inputProps = {
|
||||
className: inputClassName,
|
||||
value: value,
|
||||
onKeyDown: handleKeyDown,
|
||||
'aria-busy': ariaBusy,
|
||||
onChange: handleChange,
|
||||
'aria-label': ariaLabel ?? label,
|
||||
placeholder: placeholder ?? label,
|
||||
disabled: disabled,
|
||||
maxLength: maxLength,
|
||||
minLength: minLength,
|
||||
style: {
|
||||
height: multiline && resize && hasValue && height ? `${height}px` : undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const Tag: React.ElementType = inline ? 'span' : 'div';
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={tcls(
|
||||
'group/input relative flex min-h-min overflow-hidden border border-tint bg-tint-base align-middle shadow-tint/6 ring-primary-hover transition-all dark:shadow-tint-1',
|
||||
disabled
|
||||
? 'cursor-not-allowed border-tint-subtle bg-tint-subtle opacity-7'
|
||||
: [
|
||||
'depth-subtle:focus-within:-translate-y-px depth-subtle:hover:-translate-y-px depth-subtle:shadow-xs',
|
||||
'focus-within:border-primary-hover focus-within:depth-subtle:shadow-lg focus-within:shadow-primary-subtle focus-within:ring-2 hover:cursor-text hover:border-tint-hover depth-subtle:hover:not-focus-within:shadow-md focus-within:hover:border-primary-hover',
|
||||
],
|
||||
multiline ? 'flex-col' : 'flex-row',
|
||||
ariaBusy ? 'cursor-progress' : '',
|
||||
sizes[sizing].container,
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
handleClick();
|
||||
}
|
||||
}}
|
||||
ref={containerRef}
|
||||
>
|
||||
<Tag
|
||||
className={tcls(
|
||||
'flex grow',
|
||||
sizes[sizing].gap,
|
||||
multiline ? 'items-start' : 'items-center'
|
||||
)}
|
||||
>
|
||||
{leading ? (
|
||||
<Tag
|
||||
className={tcls(
|
||||
clearButton && hasValue ? 'group-focus-within/input:hidden' : '',
|
||||
multiline ? 'my-1.25' : ''
|
||||
)}
|
||||
>
|
||||
{typeof leading === 'string' ? (
|
||||
<Icon
|
||||
icon={leading as IconName}
|
||||
className="size-4 shrink-0 text-tint"
|
||||
/>
|
||||
) : (
|
||||
leading
|
||||
)}
|
||||
</Tag>
|
||||
) : null}
|
||||
{clearButton ? (
|
||||
<Button
|
||||
variant="blank"
|
||||
size="medium"
|
||||
label={tString(language, 'clear')}
|
||||
iconOnly
|
||||
icon="circle-xmark"
|
||||
onClick={handleClear}
|
||||
{...(typeof clearButton === 'object' ? clearButton : {})}
|
||||
className={tcls(
|
||||
'-mx-1.5 hidden shrink-0 animate-fade-in p-1.5 text-tint',
|
||||
multiline ? '-my-0.25' : '-my-1.5',
|
||||
hasValue ? 'group-focus-within/input:flex' : '',
|
||||
typeof clearButton === 'object' ? clearButton.className : ''
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{multiline ? (
|
||||
<textarea
|
||||
{...inputProps}
|
||||
ref={ref as React.RefObject<HTMLTextAreaElement>}
|
||||
{...(htmlProps as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
{...inputProps}
|
||||
ref={ref as React.RefObject<HTMLInputElement>}
|
||||
type="text"
|
||||
size={1} // Size controls the intrinsic width of the input, but we want to control the width with CSS
|
||||
{...(htmlProps as React.InputHTMLAttributes<HTMLInputElement>)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{keyboardShortcut !== false ? (
|
||||
<Tag
|
||||
className={
|
||||
multiline ? `absolute top-0 right-0 ${sizes[sizing].container}` : ''
|
||||
}
|
||||
>
|
||||
{typeof keyboardShortcut === 'object' ? (
|
||||
<KeyboardShortcut {...keyboardShortcut} />
|
||||
) : onSubmit && !submitted && hasValue ? (
|
||||
<KeyboardShortcut
|
||||
keys={['enter']}
|
||||
className="hidden bg-tint-base group-focus-within/input:flex"
|
||||
/>
|
||||
) : null}
|
||||
</Tag>
|
||||
) : null}
|
||||
</Tag>
|
||||
{trailing || submitButton || maxLength ? (
|
||||
<Tag className="flex items-center gap-2 empty:hidden">
|
||||
{trailing}
|
||||
{maxLength && !submitted && value.toString().length > maxLength * 0.8 ? (
|
||||
<span
|
||||
className={tcls(
|
||||
'shrink-0 animate-fade-in text-xs tabular-nums',
|
||||
value.toString().length >= maxLength
|
||||
? 'text-danger-subtle'
|
||||
: 'text-tint-subtle'
|
||||
)}
|
||||
>
|
||||
{value.toString().length} / {maxLength}
|
||||
</span>
|
||||
) : null}
|
||||
{submitted && submitMessage ? (
|
||||
typeof submitMessage === 'string' ? (
|
||||
<Tag className="ml-auto flex animate-fade-in items-center gap-1 p-1.5 text-success-subtle">
|
||||
<Icon icon="check-circle" className="size-4" />
|
||||
{submitMessage}
|
||||
</Tag>
|
||||
) : (
|
||||
submitMessage
|
||||
)
|
||||
) : submitButton ? (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="medium"
|
||||
label={tString(language, 'submit')}
|
||||
onClick={handleSubmit}
|
||||
icon={multiline ? undefined : 'arrow-right'}
|
||||
disabled={disabled || !hasValidValue}
|
||||
iconOnly={!multiline}
|
||||
className="ml-auto"
|
||||
{...(typeof submitButton === 'object' ? submitButton : {})}
|
||||
/>
|
||||
) : null}
|
||||
</Tag>
|
||||
) : null}
|
||||
</Tag>
|
||||
);
|
||||
});
|
||||
@@ -1,13 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import * as React from 'react';
|
||||
|
||||
export type KeyboardShortcutProps = {
|
||||
keys: string[];
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
function getOperatingSystem() {
|
||||
const platform = navigator.platform.toLowerCase();
|
||||
|
||||
@@ -17,7 +13,7 @@ function getOperatingSystem() {
|
||||
return 'win';
|
||||
}
|
||||
|
||||
export function KeyboardShortcut(props: KeyboardShortcutProps) {
|
||||
export function KeyboardShortcut(props: { keys: string[]; className?: ClassValue }) {
|
||||
const { keys, className } = props;
|
||||
|
||||
const [operatingSystem, setOperatingSystem] = React.useState<string | null>(null);
|
||||
@@ -45,7 +41,7 @@ export function KeyboardShortcut(props: KeyboardShortcutProps) {
|
||||
break;
|
||||
|
||||
case 'enter':
|
||||
element = <Icon icon="arrow-turn-down-left" className="size-[.9em]" />;
|
||||
element = <Icon icon="arrow-turn-down-left" className="size-[.75em]" />;
|
||||
break;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import * as React from 'react';
|
||||
import { useScrollListener } from '../hooks/useScrollListener';
|
||||
import { Button, type ButtonProps } from './Button';
|
||||
import { Button } from './Button';
|
||||
|
||||
/**
|
||||
* A container that encapsulates a scrollable area with usability features.
|
||||
@@ -17,26 +17,17 @@ export type ScrollContainerProps = {
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
|
||||
/** Optional class(es) to apply when there the container can be scrolled on the leading (left or top) edge */
|
||||
leadingEdgeScrollClassName?: string;
|
||||
|
||||
/** Optional class(es) to apply when there the container can be scrolled on the trailing (right or bottom) edge */
|
||||
trailingEdgeScrollClassName?: string;
|
||||
|
||||
/** The direction of the scroll container. */
|
||||
orientation: 'horizontal' | 'vertical';
|
||||
|
||||
leading?: {
|
||||
/** Whether to fade out the leading edge of the container. */
|
||||
fade: boolean;
|
||||
/** Whether to show a button to scroll back. */
|
||||
button: boolean | ButtonProps;
|
||||
/** Optional class(es) to apply when there the container can be scrolled on the leading (left or top) edge */
|
||||
className?: string;
|
||||
};
|
||||
|
||||
trailing?: {
|
||||
/** Whether to fade out the trailing edge of the container. */
|
||||
fade: boolean;
|
||||
/** Whether to show a button to scroll forward. */
|
||||
button: boolean | ButtonProps;
|
||||
/** Optional class(es) to apply when there the container can be scrolled on the trailing (right or bottom) edge */
|
||||
className?: string;
|
||||
};
|
||||
/** Whether to fade out the edges of the container. */
|
||||
fadeEdges?: ('leading' | 'trailing')[];
|
||||
|
||||
/** The ID or ref of the active item to scroll to. */
|
||||
active?: string | React.RefObject<HTMLElement | null>;
|
||||
@@ -48,9 +39,10 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
className,
|
||||
contentClassName,
|
||||
orientation,
|
||||
fadeEdges = ['leading', 'trailing'],
|
||||
active,
|
||||
leading = { fade: true, button: true },
|
||||
trailing = { fade: true, button: true },
|
||||
leadingEdgeScrollClassName,
|
||||
trailingEdgeScrollClassName,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -110,9 +102,7 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
return;
|
||||
}
|
||||
const activeItem =
|
||||
typeof active === 'string'
|
||||
? containerRef.current?.querySelector(active)
|
||||
: active.current;
|
||||
typeof active === 'string' ? document.getElementById(active) : active.current;
|
||||
if (!activeItem || !container.contains(activeItem)) {
|
||||
return;
|
||||
}
|
||||
@@ -148,30 +138,28 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'group/scroll-container relative flex shrink grow',
|
||||
orientation === 'horizontal' ? 'min-w-0' : 'min-h-0',
|
||||
'group/scroll-container relative flex overflow-hidden',
|
||||
className,
|
||||
scrollPosition > 0 ? leading?.className : '',
|
||||
scrollPosition < scrollSize ? trailing?.className : ''
|
||||
scrollPosition > 0 ? leadingEdgeScrollClassName : '',
|
||||
scrollPosition < scrollSize ? trailingEdgeScrollClassName : ''
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{/* Scrollable content */}
|
||||
<div
|
||||
className={tcls(
|
||||
'flex flex-1 overflow-hidden',
|
||||
orientation === 'horizontal' ? 'min-w-0' : 'min-h-0',
|
||||
'flex shrink grow',
|
||||
orientation === 'horizontal' ? 'no-scrollbar' : 'hide-scrollbar',
|
||||
orientation === 'horizontal' ? 'overflow-x-scroll' : 'flex-col overflow-y-auto',
|
||||
leading.fade && scrollPosition > 0
|
||||
fadeEdges.includes('leading') && scrollPosition > 0
|
||||
? orientation === 'horizontal'
|
||||
? 'mask-l-from-[calc(100%-1rem)]'
|
||||
: 'mask-t-from-[calc(100%-1rem)]'
|
||||
? 'mask-l-from-[calc(100%-2rem)]'
|
||||
: 'mask-t-from-[calc(100%-2rem)]'
|
||||
: '',
|
||||
trailing.fade && scrollPosition < scrollSize
|
||||
fadeEdges.includes('trailing') && scrollPosition < scrollSize
|
||||
? orientation === 'horizontal'
|
||||
? 'mask-r-from-[calc(100%-1rem)]'
|
||||
: 'mask-b-from-[calc(100%-1rem)]'
|
||||
? 'mask-r-from-[calc(100%-2rem)]'
|
||||
: 'mask-b-from-[calc(100%-2rem)]'
|
||||
: '',
|
||||
contentClassName
|
||||
)}
|
||||
@@ -181,52 +169,44 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
</div>
|
||||
|
||||
{/* Scroll buttons back & forward */}
|
||||
{leading.button !== false ? (
|
||||
<Button
|
||||
icon={orientation === 'horizontal' ? 'chevron-left' : 'chevron-up'}
|
||||
iconOnly
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
tabIndex={-1}
|
||||
onClick={scrollBack}
|
||||
label={tString(language, 'scroll_back')}
|
||||
{...(typeof leading.button === 'object' ? leading.button : {})}
|
||||
className={tcls(
|
||||
'z-10 bg-tint-base!',
|
||||
orientation === 'horizontal'
|
||||
? '-translate-y-1/2! top-1/2 left-0 ml-2'
|
||||
: '-translate-x-1/2! top-0 left-1/2 mt-2',
|
||||
'absolute z-10 not-pointer-none:block hidden scale-0 opacity-0 transition-[scale,opacity]',
|
||||
scrollPosition > 0
|
||||
? 'not-pointer-none:group-hover/scroll-container:scale-100 not-pointer-none:group-hover/scroll-container:opacity-11'
|
||||
: 'pointer-events-none',
|
||||
typeof leading.button === 'object' ? leading.button.className : ''
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
{trailing.button !== false ? (
|
||||
<Button
|
||||
icon={orientation === 'horizontal' ? 'chevron-right' : 'chevron-down'}
|
||||
iconOnly
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
tabIndex={-1}
|
||||
onClick={scrollFurther}
|
||||
label={tString(language, 'scroll_further')}
|
||||
{...(typeof trailing.button === 'object' ? trailing.button : {})}
|
||||
className={tcls(
|
||||
'z-10 bg-tint-base!',
|
||||
orientation === 'horizontal'
|
||||
? '-translate-y-1/2! top-1/2 right-0 mr-2'
|
||||
: '-translate-x-1/2! bottom-0 left-1/2 mb-2',
|
||||
'absolute z-10 not-pointer-none:block hidden scale-0 transition-[scale,opacity]',
|
||||
scrollPosition < scrollSize
|
||||
? 'not-pointer-none:group-hover/scroll-container:scale-100 not-pointer-none:group-hover/scroll-container:opacity-11'
|
||||
: 'pointer-events-none',
|
||||
typeof trailing.button === 'object' ? trailing.button.className : ''
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
icon={orientation === 'horizontal' ? 'chevron-left' : 'chevron-up'}
|
||||
iconOnly
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
tabIndex={-1}
|
||||
className={tcls(
|
||||
'bg-tint-base!',
|
||||
orientation === 'horizontal'
|
||||
? '-translate-y-1/2! top-1/2 left-0 ml-2'
|
||||
: '-translate-x-1/2! top-0 left-1/2 mt-2',
|
||||
'absolute not-pointer-none:block hidden scale-0 opacity-0 transition-[scale,opacity]',
|
||||
scrollPosition > 0
|
||||
? 'not-pointer-none:group-hover/scroll-container:scale-100 not-pointer-none:group-hover/scroll-container:opacity-11'
|
||||
: 'pointer-events-none'
|
||||
)}
|
||||
onClick={scrollBack}
|
||||
label={tString(language, 'scroll_back')}
|
||||
/>
|
||||
<Button
|
||||
icon={orientation === 'horizontal' ? 'chevron-right' : 'chevron-down'}
|
||||
iconOnly
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
tabIndex={-1}
|
||||
className={tcls(
|
||||
'bg-tint-base!',
|
||||
orientation === 'horizontal'
|
||||
? '-translate-y-1/2! top-1/2 right-0 mr-2'
|
||||
: '-translate-x-1/2! bottom-0 left-1/2 mb-2',
|
||||
'absolute not-pointer-none:block hidden scale-0 transition-[scale,opacity]',
|
||||
scrollPosition < scrollSize
|
||||
? 'not-pointer-none:group-hover/scroll-container:scale-100 not-pointer-none:group-hover/scroll-container:opacity-11'
|
||||
: 'pointer-events-none'
|
||||
)}
|
||||
onClick={scrollFurther}
|
||||
label={tString(language, 'scroll_further')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -234,7 +214,7 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
/**
|
||||
* Scroll to an element in a container.
|
||||
*/
|
||||
function scrollToElementInContainer(element: Element, container: HTMLElement) {
|
||||
function scrollToElementInContainer(element: HTMLElement, container: HTMLElement) {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
@@ -249,6 +229,6 @@ function scrollToElementInContainer(element: Element, container: HTMLElement) {
|
||||
(rect.left - containerRect.left) -
|
||||
container.clientWidth / 2 +
|
||||
rect.width / 2,
|
||||
behavior: 'auto',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { tString } from '@/intl/translate';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
import React from 'react';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { Button } from './Button';
|
||||
|
||||
/**
|
||||
* SideSheet - A slide-in panel component that can appear from the left or right side.
|
||||
*
|
||||
* Supports both controlled and uncontrolled modes:
|
||||
* - Controlled: Provide both `open` and `onOpenChange` props. Parent manages state.
|
||||
* - Uncontrolled: Omit `open` prop. Component manages its own state internally.
|
||||
*/
|
||||
export function SideSheet(
|
||||
props: {
|
||||
/** Which side the sheet slides in from */
|
||||
side: 'left' | 'right';
|
||||
/**
|
||||
* Optional CSS class to monitor and sync with `document.body.classList`.
|
||||
* When set, a MutationObserver watches for the class and syncs the sheet state accordingly.
|
||||
* Adding this class opens the sheet, removing it closes it.
|
||||
* Works in both controlled and uncontrolled modes.
|
||||
*/
|
||||
toggleClass?: string;
|
||||
/**
|
||||
* Modal behavior: true (always modal), false (never modal), or 'mobile' (modal only on mobile).
|
||||
* Defaults to 'mobile'.
|
||||
*/
|
||||
modal?: true | false | 'mobile';
|
||||
/**
|
||||
* Controls visibility. If provided, component is controlled (parent manages state).
|
||||
* If undefined, component is uncontrolled (manages its own state).
|
||||
*/
|
||||
open?: boolean;
|
||||
/** Called when the open state changes. Receives the new state (true/false). Only used in controlled mode. */
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
/** Show a backdrop overlay when modal */
|
||||
withScrim?: boolean;
|
||||
/** Show a close button when modal */
|
||||
withCloseButton?: boolean;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
) {
|
||||
const {
|
||||
side,
|
||||
children,
|
||||
className,
|
||||
toggleClass,
|
||||
open: openState,
|
||||
modal = 'mobile',
|
||||
withScrim,
|
||||
withCloseButton,
|
||||
onOpenChange,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isModal = modal === 'mobile' ? isMobile : modal;
|
||||
|
||||
// Internal state for uncontrolled mode (only used when open prop is undefined)
|
||||
const [open, setOpen] = React.useState(openState ?? false);
|
||||
|
||||
// Determine actual open state: controlled (from prop) or uncontrolled (from internal state)
|
||||
const isOpen = openState !== undefined ? openState : open;
|
||||
|
||||
const handleClose = React.useCallback(() => {
|
||||
if (openState !== undefined) {
|
||||
// Controlled mode: parent manages state, notify via callback with new state
|
||||
onOpenChange?.(false);
|
||||
} else {
|
||||
// Uncontrolled mode: update internal state and sync body class if needed
|
||||
setOpen(false);
|
||||
if (toggleClass) {
|
||||
document.body.classList.remove(toggleClass);
|
||||
}
|
||||
}
|
||||
}, [openState, onOpenChange, toggleClass]);
|
||||
|
||||
// Sync the sheet state with the body class if the toggleClass is set
|
||||
React.useEffect(() => {
|
||||
if (!toggleClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const callback = (mutationList: MutationRecord[]) => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.attributeName === 'class') {
|
||||
const shouldBeOpen = document.body.classList.contains(toggleClass);
|
||||
if (openState !== undefined) {
|
||||
// Controlled mode: sync with parent's state
|
||||
// Notify parent of state change via onOpenChange
|
||||
if (shouldBeOpen !== openState) {
|
||||
onOpenChange?.(shouldBeOpen);
|
||||
}
|
||||
} else {
|
||||
// Uncontrolled mode: sync internal state with body class
|
||||
setOpen(shouldBeOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(callback);
|
||||
observer.observe(document.body, { attributes: true });
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [toggleClass, openState, onOpenChange]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{withScrim ? (
|
||||
<SideSheetScrim
|
||||
className={tcls(isModal && isOpen ? '' : 'hidden opacity-0 backdrop-blur-none')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<aside
|
||||
className={tcls(
|
||||
'side-sheet',
|
||||
isOpen
|
||||
? side === 'left'
|
||||
? 'hydrated:animate-enter-from-left'
|
||||
: 'hydrated:animate-enter-from-right'
|
||||
: side === 'left'
|
||||
? 'hydrated:animate-exit-to-left'
|
||||
: 'hydrated:animate-exit-to-right',
|
||||
'fixed inset-y-0 z-41', // Above the side sheet scrim on z-40
|
||||
side === 'left' ? 'left-0' : 'right-0',
|
||||
withCloseButton ? 'max-w-[calc(100vw-5rem)]' : 'max-w-[calc(100vw-3rem)]',
|
||||
isOpen ? '' : 'hidden',
|
||||
className
|
||||
)}
|
||||
aria-expanded={isOpen}
|
||||
aria-modal={isModal}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{withCloseButton ? (
|
||||
<SideSheetCloseButton
|
||||
className={tcls(
|
||||
side === 'left' ? 'left-full ml-4' : 'right-full mr-4',
|
||||
isModal && isOpen ? 'animate-blur-in' : 'hidden animate-blur-out'
|
||||
)}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
) : null}
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Backdrop overlay shown behind the modal sheet */
|
||||
export function SideSheetScrim(props: { className?: ClassValue; onClick?: () => void }) {
|
||||
const { className, onClick } = props;
|
||||
return (
|
||||
<div
|
||||
id="side-sheet-scrim"
|
||||
onClick={() => {
|
||||
onClick?.();
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
onClick?.();
|
||||
}
|
||||
}}
|
||||
className={tcls(
|
||||
'fixed inset-0 z-40 items-start bg-tint-base/3 not-hydrated:opacity-0 starting:opacity-0 backdrop-blur-md starting:backdrop-blur-none transition-[opacity,display,backdrop-filter] transition-discrete duration-250 dark:bg-tint-base/6',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** Close button displayed outside the sheet when modal */
|
||||
export function SideSheetCloseButton(props: { className?: ClassValue; onClick?: () => void }) {
|
||||
const { className, onClick } = props;
|
||||
const language = useLanguage();
|
||||
return (
|
||||
<Button
|
||||
icon="xmark"
|
||||
variant="secondary"
|
||||
size="default"
|
||||
iconOnly
|
||||
label={tString(language, 'close')}
|
||||
className={tcls('absolute top-4 bg-tint-base! transition-discrete', className)}
|
||||
onClick={() => {
|
||||
onClick?.();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -13,4 +13,3 @@ export * from './Popover';
|
||||
export * from './LoadingStateProvider';
|
||||
export * from './HoverCard';
|
||||
export * from './DropdownMenu';
|
||||
export * from './Input';
|
||||
|
||||
@@ -61,13 +61,13 @@ export function servePagesSitemap(context: GitBookSiteContext) {
|
||||
const lastModified = page.updatedAt || page.createdAt;
|
||||
|
||||
const url: { loc: string; priority: number; lastmod?: string } = {
|
||||
priority: normalizedPriority,
|
||||
loc: linker.toAbsoluteURL(
|
||||
linker.toPathForPage({
|
||||
pages: context.revision.pages,
|
||||
page,
|
||||
})
|
||||
),
|
||||
priority: normalizedPriority,
|
||||
};
|
||||
|
||||
if (lastModified) {
|
||||
|
||||
@@ -328,10 +328,10 @@ const config: Config = {
|
||||
blurOut: 'blurOut 200ms ease-in both',
|
||||
'blurOut-slow': 'blurOut 500ms ease-in both',
|
||||
|
||||
enterFromLeft: 'enterFromLeft 300ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
enterFromRight: 'enterFromRight 300ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
exitToLeft: 'exitToLeft 300ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
exitToRight: 'exitToRight 300ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
enterFromLeft: 'enterFromLeft 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
enterFromRight: 'enterFromRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
exitToLeft: 'exitToLeft 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
exitToRight: 'exitToRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
|
||||
|
||||
heightIn: 'heightIn 200ms ease both',
|
||||
crawl: 'crawl 2s ease-in-out infinite',
|
||||
@@ -474,18 +474,18 @@ const config: Config = {
|
||||
},
|
||||
enterFromRight: {
|
||||
from: { opacity: '0', transform: 'translateX(50%)', display: 'none' },
|
||||
to: { opacity: '1', transform: 'translateX(0)', display: 'inherit' },
|
||||
to: { opacity: '1', transform: 'translateX(0)', display: 'block' },
|
||||
},
|
||||
enterFromLeft: {
|
||||
from: { opacity: '0', transform: 'translateX(-50%)', display: 'none' },
|
||||
to: { opacity: '1', transform: 'translateX(0)', display: 'inherit' },
|
||||
to: { opacity: '1', transform: 'translateX(0)', display: 'block' },
|
||||
},
|
||||
exitToRight: {
|
||||
from: { opacity: '1', transform: 'translateX(0)', display: 'inherit' },
|
||||
from: { opacity: '1', transform: 'translateX(0)', display: 'block' },
|
||||
to: { opacity: '0', transform: 'translateX(50%)', display: 'none' },
|
||||
},
|
||||
exitToLeft: {
|
||||
from: { opacity: '1', transform: 'translateX(0)', display: 'inherit' },
|
||||
from: { opacity: '1', transform: 'translateX(0)', display: 'block' },
|
||||
to: { opacity: '0', transform: 'translateX(-50%)', display: 'none' },
|
||||
},
|
||||
scaleIn: {
|
||||
@@ -576,11 +576,7 @@ const config: Config = {
|
||||
* Variant when the Table of Content navigation is open.
|
||||
*/
|
||||
addVariant('navigation-open', 'body.navigation-open &');
|
||||
addVariant('chat-open', 'body:has(.ai-chat[aria-expanded="true"]) &');
|
||||
addVariant(
|
||||
'sheet-open',
|
||||
'html:has(.side-sheet[aria-modal="true"][aria-expanded="true"]) &, &:has(.side-sheet[aria-modal="true"][aria-expanded="true"])'
|
||||
);
|
||||
addVariant('chat-open', 'body:has(.ai-chat:not(.hidden)) &');
|
||||
|
||||
/**
|
||||
* Variant when a header is displayed.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getContentTestURL } from './utils';
|
||||
|
||||
const EMBED_SCRIPT_URL = getContentTestURL(
|
||||
'https://gitbook.gitbook.io/test-gitbook-open/~gitbook/embed/script.js'
|
||||
);
|
||||
|
||||
describe('embed script', () => {
|
||||
it('serves the embeddable script with permissive headers', async () => {
|
||||
const response = await fetch(EMBED_SCRIPT_URL, {
|
||||
headers: {
|
||||
Origin: 'https://example.com',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('content-type')).toContain('application/javascript');
|
||||
expect(response.headers.get('access-control-allow-origin')).toBe('*');
|
||||
expect(response.headers.get('cross-origin-resource-policy')).toBe('cross-origin');
|
||||
|
||||
const body = await response.text();
|
||||
expect(body).toContain('w.GitBook');
|
||||
});
|
||||
});
|
||||
@@ -17,11 +17,11 @@
|
||||
"sideEffects": false,
|
||||
"version": "0.3.4",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/fontawesome-free": "^7.1.0"
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@gitbook/fontawesome-pro": "1.0.16"
|
||||
"@gitbook/fontawesome-pro": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "catalog:",
|
||||
|
||||
@@ -36,19 +36,9 @@ function OpenAPISchemaProperty(
|
||||
context: OpenAPIClientContext;
|
||||
circularRefs: CircularRefsIds;
|
||||
className?: string;
|
||||
discriminator?: OpenAPIV3.DiscriminatorObject;
|
||||
discriminatorValue?: string;
|
||||
} & Omit<ComponentPropsWithoutRef<'div'>, 'property' | 'context' | 'circularRefs' | 'className'>
|
||||
) {
|
||||
const {
|
||||
circularRefs: parentCircularRefs,
|
||||
context,
|
||||
className,
|
||||
property,
|
||||
discriminator,
|
||||
discriminatorValue,
|
||||
...rest
|
||||
} = props;
|
||||
const { circularRefs: parentCircularRefs, context, className, property, ...rest } = props;
|
||||
|
||||
const { schema } = property;
|
||||
|
||||
@@ -69,7 +59,7 @@ function OpenAPISchemaProperty(
|
||||
const circularRefs = new Map(parentCircularRefs);
|
||||
circularRefs.set(schema, id);
|
||||
|
||||
const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
|
||||
const properties = getSchemaProperties(schema);
|
||||
|
||||
const ancestors = new Set(circularRefs.keys());
|
||||
const alternatives = getSchemaAlternatives(schema, ancestors);
|
||||
@@ -77,15 +67,26 @@ function OpenAPISchemaProperty(
|
||||
const header = <OpenAPISchemaPresentation id={id} context={context} property={property} />;
|
||||
const content = (() => {
|
||||
if (alternatives?.schemas) {
|
||||
const { schemas, discriminator } = alternatives;
|
||||
return (
|
||||
<OpenAPISchemaAlternatives
|
||||
alternatives={alternatives}
|
||||
schema={schema}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
parentDiscriminator={discriminator}
|
||||
parentDiscriminatorValue={discriminatorValue}
|
||||
/>
|
||||
<div className="openapi-schema-alternatives">
|
||||
{schemas.map((alternativeSchema, index) => (
|
||||
<div key={index} className="openapi-schema-alternative">
|
||||
<OpenAPISchemaAlternative
|
||||
schema={alternativeSchema}
|
||||
discriminator={discriminator}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
{index < schemas.length - 1 ? (
|
||||
<OpenAPISchemaAlternativeSeparator
|
||||
schema={schema}
|
||||
context={context}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,30 +187,11 @@ function OpenAPIRootSchema(props: {
|
||||
const id = useId();
|
||||
const properties = getSchemaProperties(schema);
|
||||
const description = resolveDescription(schema);
|
||||
const ancestors = new Set(parentCircularRefs.keys());
|
||||
const alternatives = getSchemaAlternatives(schema, ancestors);
|
||||
|
||||
const circularRefs = new Map(parentCircularRefs);
|
||||
circularRefs.set(schema, id);
|
||||
|
||||
// Handle root-level oneOf/allOf/anyOf
|
||||
if (alternatives?.schemas) {
|
||||
return (
|
||||
<>
|
||||
{description ? (
|
||||
<Markdown source={description} className="openapi-schema-root-description" />
|
||||
) : null}
|
||||
<OpenAPISchemaAlternatives
|
||||
alternatives={alternatives}
|
||||
schema={schema}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (properties?.length) {
|
||||
const circularRefs = new Map(parentCircularRefs);
|
||||
circularRefs.set(schema, id);
|
||||
|
||||
return (
|
||||
<>
|
||||
{description ? (
|
||||
@@ -246,116 +228,6 @@ export function OpenAPIRootSchemaFromServer(props: {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the discriminator value for a schema.
|
||||
*/
|
||||
function getDiscriminatorValue(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
discriminator: OpenAPIV3.DiscriminatorObject | undefined
|
||||
): string | undefined {
|
||||
if (!discriminator) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (discriminator.mapping) {
|
||||
const mappingEntry = Object.entries(discriminator.mapping).find(([key, ref]) => {
|
||||
if (schema.title === ref || (!!schema.title && ref.endsWith(`/${schema.title}`))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: check if the title contains the key (normalized)
|
||||
if (schema.title?.toLowerCase().replace(/\s/g, '').includes(key.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (mappingEntry) {
|
||||
return mappingEntry[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!discriminator.propertyName || !schema.properties) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const property = schema.properties[discriminator.propertyName];
|
||||
if (!property || checkIsReference(property)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (property.const) {
|
||||
return String(property.const);
|
||||
}
|
||||
|
||||
if (property.enum?.length === 1) {
|
||||
return String(property.enum[0]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render alternatives (oneOf/allOf/anyOf) for a schema.
|
||||
*/
|
||||
function OpenAPISchemaAlternatives(props: {
|
||||
alternatives: SchemaAlternatives;
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
circularRefs: CircularRefsIds;
|
||||
context: OpenAPIClientContext;
|
||||
parentDiscriminator?: OpenAPIV3.DiscriminatorObject;
|
||||
parentDiscriminatorValue?: string;
|
||||
}) {
|
||||
const {
|
||||
alternatives,
|
||||
schema,
|
||||
circularRefs,
|
||||
context,
|
||||
parentDiscriminator,
|
||||
parentDiscriminatorValue,
|
||||
} = props;
|
||||
|
||||
if (!alternatives?.schemas) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { schemas, discriminator: alternativeDiscriminator, type } = alternatives;
|
||||
|
||||
return (
|
||||
<div className="openapi-schema-alternatives">
|
||||
{schemas.map((alternativeSchema, index) => {
|
||||
// If the alternative has its own discriminator, use it.
|
||||
// Otherwise, for allOf, inherit from parent discriminator.
|
||||
const effectiveDiscriminator =
|
||||
alternativeDiscriminator ||
|
||||
(type === 'allOf' ? parentDiscriminator : undefined);
|
||||
|
||||
// If we are inheriting and using parent discriminator, pass down the value.
|
||||
const effectiveDiscriminatorValue =
|
||||
!alternativeDiscriminator && type === 'allOf'
|
||||
? parentDiscriminatorValue
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div key={index} className="openapi-schema-alternative">
|
||||
<OpenAPISchemaAlternative
|
||||
schema={alternativeSchema}
|
||||
discriminator={effectiveDiscriminator}
|
||||
discriminatorValue={effectiveDiscriminatorValue}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
{index < schemas.length - 1 ? (
|
||||
<OpenAPISchemaAlternativeSeparator schema={schema} context={context} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a tab for an alternative schema.
|
||||
* It renders directly the properties if relevant;
|
||||
@@ -364,14 +236,11 @@ function OpenAPISchemaAlternatives(props: {
|
||||
function OpenAPISchemaAlternative(props: {
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
discriminator: OpenAPIV3.DiscriminatorObject | undefined;
|
||||
discriminatorValue?: string;
|
||||
circularRefs: CircularRefsIds;
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { schema, discriminator, circularRefs, context } = props;
|
||||
const discriminatorValue =
|
||||
props.discriminatorValue || getDiscriminatorValue(schema, discriminator);
|
||||
const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
|
||||
const properties = getSchemaProperties(schema, discriminator);
|
||||
|
||||
return properties?.length ? (
|
||||
<OpenAPIDisclosure
|
||||
@@ -388,8 +257,6 @@ function OpenAPISchemaAlternative(props: {
|
||||
) : (
|
||||
<OpenAPISchemaProperty
|
||||
property={{ schema }}
|
||||
discriminator={discriminator}
|
||||
discriminatorValue={discriminatorValue}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
@@ -515,7 +382,7 @@ export function OpenAPISchemaPresentation(props: {
|
||||
<div id={id} className="openapi-schema-presentation">
|
||||
<OpenAPISchemaName
|
||||
schema={schema}
|
||||
type={getSchemaTitle(schema, { ignoreAlternatives: !propertyName })}
|
||||
type={getSchemaTitle(schema)}
|
||||
propertyName={propertyName}
|
||||
isDiscriminatorProperty={isDiscriminatorProperty}
|
||||
required={required}
|
||||
@@ -563,87 +430,17 @@ export function OpenAPISchemaPresentation(props: {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process properties from a schema object into property entries.
|
||||
*/
|
||||
function processSchemaProperties(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
discriminator?: OpenAPIV3.DiscriminatorObject | undefined,
|
||||
discriminatorValue?: string | undefined,
|
||||
allRequired?: Set<string>
|
||||
): OpenAPISchemaPropertyEntry[] {
|
||||
const result: OpenAPISchemaPropertyEntry[] = [];
|
||||
|
||||
if (schema.properties) {
|
||||
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
|
||||
const isDiscriminator = discriminator?.propertyName === propertyName;
|
||||
if (checkIsReference(propertySchema)) {
|
||||
if (!isDiscriminator || !discriminatorValue) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let finalSchema = propertySchema;
|
||||
if (isDiscriminator && discriminatorValue) {
|
||||
finalSchema = {
|
||||
...propertySchema,
|
||||
const: discriminatorValue,
|
||||
enum: [discriminatorValue],
|
||||
};
|
||||
}
|
||||
|
||||
result.push({
|
||||
propertyName,
|
||||
required: Array.isArray(schema.required)
|
||||
? schema.required.includes(propertyName)
|
||||
: allRequired?.has(propertyName)
|
||||
? true
|
||||
: undefined,
|
||||
isDiscriminatorProperty: isDiscriminator,
|
||||
schema: finalSchema,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) {
|
||||
result.push({
|
||||
propertyName: 'Other properties',
|
||||
schema: schema.additionalProperties === true ? {} : schema.additionalProperties,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge properties into a result array, with later properties overriding earlier ones.
|
||||
*/
|
||||
function mergeProperties(
|
||||
result: OpenAPISchemaPropertyEntry[],
|
||||
newProperties: OpenAPISchemaPropertyEntry[]
|
||||
): void {
|
||||
for (const prop of newProperties) {
|
||||
const existingIndex = result.findIndex((p) => p.propertyName === prop.propertyName);
|
||||
if (existingIndex >= 0) {
|
||||
result[existingIndex] = prop;
|
||||
} else {
|
||||
result.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sub-properties of a schema.
|
||||
*/
|
||||
function getSchemaProperties(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
discriminator?: OpenAPIV3.DiscriminatorObject | undefined,
|
||||
discriminatorValue?: string | undefined
|
||||
discriminator?: OpenAPIV3.DiscriminatorObject | undefined
|
||||
): null | OpenAPISchemaPropertyEntry[] {
|
||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||
if (schema.type === 'array' && schema.items && !checkIsReference(schema.items)) {
|
||||
const items = schema.items;
|
||||
const itemProperties = getSchemaProperties(items, discriminator, discriminatorValue);
|
||||
const itemProperties = getSchemaProperties(items);
|
||||
if (itemProperties) {
|
||||
return itemProperties.map((prop) => ({
|
||||
...prop,
|
||||
@@ -665,55 +462,34 @@ function getSchemaProperties(
|
||||
return [{ propertyName: 'items', schema: items }];
|
||||
}
|
||||
|
||||
// Handle allOf by collecting properties from all schemas in the allOf
|
||||
if (schema.allOf && Array.isArray(schema.allOf)) {
|
||||
const allOfSchemas = schema.allOf.filter(
|
||||
(s): s is OpenAPIV3.SchemaObject => !checkIsReference(s)
|
||||
);
|
||||
|
||||
if (allOfSchemas.length > 0) {
|
||||
const result: OpenAPISchemaPropertyEntry[] = [];
|
||||
const allRequired = new Set<string>();
|
||||
|
||||
// Collect required fields and properties from all allOf schemas
|
||||
for (const allOfSchema of allOfSchemas) {
|
||||
if (Array.isArray(allOfSchema.required)) {
|
||||
allOfSchema.required.forEach((req) => allRequired.add(req));
|
||||
}
|
||||
const allOfProperties = getSchemaProperties(
|
||||
allOfSchema,
|
||||
discriminator,
|
||||
discriminatorValue
|
||||
);
|
||||
if (allOfProperties) {
|
||||
mergeProperties(result, allOfProperties);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect required fields from the schema itself
|
||||
if (Array.isArray(schema.required)) {
|
||||
schema.required.forEach((req) => allRequired.add(req));
|
||||
}
|
||||
|
||||
// Include direct properties from the schema itself
|
||||
mergeProperties(
|
||||
result,
|
||||
processSchemaProperties(schema, discriminator, discriminatorValue, allRequired)
|
||||
);
|
||||
|
||||
// Update required status for all properties
|
||||
result.forEach((prop) => {
|
||||
if (prop.propertyName && allRequired.has(prop.propertyName)) {
|
||||
prop.required = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result.length > 0 ? result : null;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.type === 'object' || schema.properties) {
|
||||
return processSchemaProperties(schema, discriminator, discriminatorValue);
|
||||
const result: OpenAPISchemaPropertyEntry[] = [];
|
||||
|
||||
if (schema.properties) {
|
||||
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
|
||||
if (checkIsReference(propertySchema)) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push({
|
||||
propertyName,
|
||||
required: Array.isArray(schema.required)
|
||||
? schema.required.includes(propertyName)
|
||||
: undefined,
|
||||
isDiscriminatorProperty: discriminator?.propertyName === propertyName,
|
||||
schema: propertySchema,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) {
|
||||
result.push({
|
||||
propertyName: 'Other properties',
|
||||
schema: schema.additionalProperties === true ? {} : schema.additionalProperties,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -912,90 +688,34 @@ function flattenAlternatives(
|
||||
): OpenAPIV3.SchemaObject[] {
|
||||
// Get the parent schema's required fields from the most recent ancestor
|
||||
const latestAncestor = Array.from(ancestors).pop();
|
||||
const result: OpenAPIV3.SchemaObject[] = [];
|
||||
|
||||
for (const schemaOrRef of schemasOrRefs) {
|
||||
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
|
||||
if (checkIsReference(schemaOrRef)) {
|
||||
continue;
|
||||
return acc;
|
||||
}
|
||||
|
||||
const flattened = flattenSchema(schemaOrRef, alternativeType, ancestors, latestAncestor);
|
||||
|
||||
if (flattened) {
|
||||
result.push(...flattened);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten a schema that is an alternative of another schema.
|
||||
*/
|
||||
function flattenSchema(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
alternativeType: AlternativeType,
|
||||
ancestors: Set<OpenAPIV3.SchemaObject>,
|
||||
latestAncestor: OpenAPIV3.SchemaObject | undefined
|
||||
): OpenAPIV3.SchemaObject[] {
|
||||
if (schema[alternativeType] && !ancestors.has(schema)) {
|
||||
const alternatives = getSchemaAlternatives(schema, ancestors);
|
||||
if (alternatives?.schemas) {
|
||||
return alternatives.schemas.map((s) => {
|
||||
const required = mergeRequiredFields(s, latestAncestor);
|
||||
return {
|
||||
...s,
|
||||
...(required ? { required } : {}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const required = mergeRequiredFields(schema, latestAncestor);
|
||||
return [{ ...schema, ...(required ? { required } : {}) }];
|
||||
}
|
||||
|
||||
// if a schema has allOf that can be safely merged, merge it
|
||||
if (
|
||||
(alternativeType === 'oneOf' || alternativeType === 'anyOf') &&
|
||||
schema.allOf &&
|
||||
Array.isArray(schema.allOf) &&
|
||||
!ancestors.has(schema)
|
||||
) {
|
||||
const allOfSchemas = schema.allOf.filter(
|
||||
(s): s is OpenAPIV3.SchemaObject => !checkIsReference(s)
|
||||
);
|
||||
|
||||
if (allOfSchemas.length > 0) {
|
||||
const merged = mergeAlternatives('allOf', allOfSchemas);
|
||||
if (merged && merged.length > 0) {
|
||||
// Only merge if all schemas were successfully merged into one (safe to merge)
|
||||
if (merged.length === 1) {
|
||||
return merged.map((s) => {
|
||||
const required = mergeRequiredFields(s, latestAncestor);
|
||||
const result: OpenAPIV3.SchemaObject = {
|
||||
...s,
|
||||
...(required ? { required } : {}),
|
||||
};
|
||||
|
||||
if (schema.title && !s.title) {
|
||||
result.title = schema.title;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) {
|
||||
const alternatives = getSchemaAlternatives(schemaOrRef, ancestors);
|
||||
if (alternatives?.schemas) {
|
||||
acc.push(
|
||||
...alternatives.schemas.map((schema) => ({
|
||||
...schema,
|
||||
required: mergeRequiredFields(schema, latestAncestor),
|
||||
}))
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
const required = mergeRequiredFields(schema, latestAncestor);
|
||||
// For direct schemas, handle required fields
|
||||
const schema = {
|
||||
...schemaOrRef,
|
||||
required: mergeRequiredFields(schemaOrRef, latestAncestor),
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
...schema,
|
||||
...(required ? { required } : {}),
|
||||
},
|
||||
];
|
||||
acc.push(schema);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,6 @@ export function isFormData(contentType?: string): boolean {
|
||||
return !!contentType && contentType.toLowerCase().includes('multipart/form-data');
|
||||
}
|
||||
|
||||
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
export function isPlainObject(value: unknown): boolean {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
@@ -1037,90 +1037,4 @@ describe('generateSchemaExample', () => {
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('merges object properties from oneOf -> allOf', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
discriminator: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
oneOf: [
|
||||
{
|
||||
allOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
bar: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
baz: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string', // This will return a string, but should be ignored
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} satisfies OpenAPIV3.SchemaObject;
|
||||
|
||||
const result = generateSchemaExample(schema);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveProperty('discriminator');
|
||||
expect(result).toHaveProperty('bar');
|
||||
expect(result).toHaveProperty('baz');
|
||||
});
|
||||
|
||||
it('merges object properties from anyOf -> allOf', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
discriminator: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
allOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
bar: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
baz: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string', // This will return a string, but should be ignored
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} satisfies OpenAPIV3.SchemaObject;
|
||||
|
||||
const result = generateSchemaExample(schema);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveProperty('discriminator');
|
||||
expect(result).toHaveProperty('bar');
|
||||
expect(result).toHaveProperty('baz');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { isPlainObject } from './contentTypeChecks';
|
||||
import { checkIsReference } from './utils';
|
||||
|
||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||
@@ -148,23 +147,6 @@ const getExampleFromSchema = (
|
||||
return result;
|
||||
}
|
||||
|
||||
// Process allOf items and merge object results into the response
|
||||
function mergeAllOfIntoResponse(
|
||||
allOfItems: Record<string, unknown>[],
|
||||
response: Record<string, unknown>,
|
||||
parent: Record<string, unknown> | undefined
|
||||
): void {
|
||||
const allOfResults = allOfItems
|
||||
.map((item: Record<string, unknown>) =>
|
||||
getExampleFromSchema(item, options, level + 1, parent, undefined, resultCache)
|
||||
)
|
||||
.filter(isPlainObject);
|
||||
|
||||
if (allOfResults.length > 0) {
|
||||
Object.assign(response, ...allOfResults);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the result is already cached
|
||||
if (resultCache.has(schema)) {
|
||||
return resultCache.get(schema);
|
||||
@@ -325,48 +307,45 @@ const getExampleFromSchema = (
|
||||
}
|
||||
|
||||
if (schema.anyOf !== undefined) {
|
||||
const anyOfItem = schema.anyOf[0];
|
||||
|
||||
if (anyOfItem) {
|
||||
// If anyOf[0] has allOf, process allOf items individually to merge object results
|
||||
if (anyOfItem?.allOf !== undefined && Array.isArray(anyOfItem.allOf)) {
|
||||
mergeAllOfIntoResponse(anyOfItem.allOf, response, anyOfItem);
|
||||
} else {
|
||||
const anyOfResult = getExampleFromSchema(
|
||||
anyOfItem,
|
||||
options,
|
||||
level + 1,
|
||||
undefined,
|
||||
undefined,
|
||||
resultCache
|
||||
);
|
||||
if (isPlainObject(anyOfResult)) {
|
||||
Object.assign(response, anyOfResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(
|
||||
response,
|
||||
getExampleFromSchema(
|
||||
schema.anyOf[0],
|
||||
options,
|
||||
level + 1,
|
||||
undefined,
|
||||
undefined,
|
||||
resultCache
|
||||
)
|
||||
);
|
||||
} else if (schema.oneOf !== undefined) {
|
||||
const oneOfItem = schema.oneOf[0];
|
||||
if (oneOfItem) {
|
||||
// If oneOf[0] has allOf, process allOf items individually to merge object results
|
||||
if (oneOfItem?.allOf !== undefined && Array.isArray(oneOfItem.allOf)) {
|
||||
mergeAllOfIntoResponse(oneOfItem.allOf, response, oneOfItem);
|
||||
} else {
|
||||
const oneOfResult = getExampleFromSchema(
|
||||
oneOfItem,
|
||||
options,
|
||||
level + 1,
|
||||
undefined,
|
||||
undefined,
|
||||
resultCache
|
||||
);
|
||||
if (isPlainObject(oneOfResult)) {
|
||||
Object.assign(response, oneOfResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(
|
||||
response,
|
||||
getExampleFromSchema(
|
||||
schema.oneOf[0],
|
||||
options,
|
||||
level + 1,
|
||||
undefined,
|
||||
undefined,
|
||||
resultCache
|
||||
)
|
||||
);
|
||||
} else if (schema.allOf !== undefined) {
|
||||
mergeAllOfIntoResponse(schema.allOf, response, schema);
|
||||
Object.assign(
|
||||
response,
|
||||
...schema.allOf
|
||||
.map((item: Record<string, any>) =>
|
||||
getExampleFromSchema(
|
||||
item,
|
||||
options,
|
||||
level + 1,
|
||||
schema,
|
||||
undefined,
|
||||
resultCache
|
||||
)
|
||||
)
|
||||
.filter((item: any) => item !== undefined)
|
||||
);
|
||||
}
|
||||
|
||||
return cache(schema, response);
|
||||
|
||||
@@ -218,10 +218,7 @@ function getStatusCodeCategory(statusCode: number | string): number | string {
|
||||
return category;
|
||||
}
|
||||
|
||||
export function getSchemaTitle(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
options?: { ignoreAlternatives?: boolean }
|
||||
): string {
|
||||
export function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
||||
// Otherwise try to infer a nice title
|
||||
let type = 'any';
|
||||
|
||||
@@ -229,7 +226,7 @@ export function getSchemaTitle(
|
||||
type = `${schema.type} · enum`;
|
||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||
} else if (schema.type === 'array' && !!schema.items) {
|
||||
type = `${getSchemaTitle(schema.items, options)}[]`;
|
||||
type = `${getSchemaTitle(schema.items)}[]`;
|
||||
} else if (Array.isArray(schema.type)) {
|
||||
type = schema.type.join(' | ');
|
||||
} else if (schema.type || schema.properties) {
|
||||
@@ -245,17 +242,14 @@ export function getSchemaTitle(
|
||||
}
|
||||
}
|
||||
|
||||
// Skip alternative type labels if ignoreAlternatives is true (useful when rendering alternatives)
|
||||
if (!options?.ignoreAlternatives) {
|
||||
if ('anyOf' in schema) {
|
||||
type = 'any of';
|
||||
} else if ('oneOf' in schema) {
|
||||
type = 'one of';
|
||||
} else if ('allOf' in schema) {
|
||||
type = 'all of';
|
||||
} else if ('not' in schema) {
|
||||
type = 'not';
|
||||
}
|
||||
if ('anyOf' in schema) {
|
||||
type = 'any of';
|
||||
} else if ('oneOf' in schema) {
|
||||
type = 'one of';
|
||||
} else if ('allOf' in schema) {
|
||||
type = 'all of';
|
||||
} else if ('not' in schema) {
|
||||
type = 'not';
|
||||
}
|
||||
|
||||
return type;
|
||||
|
||||
Reference in New Issue
Block a user