Compare commits

..

23 Commits

Author SHA1 Message Date
Samy Pessé 885264f132 Version Packages (#3506)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-05 11:03:50 +02:00
Greg Bergé f1a6dec951 Less strict OpenAPI parser (#3529)
Co-authored-by: Nolann Biron <biron.nolann@gmail.com>
2025-08-05 08:45:45 +00:00
Claire Chabas 44f41510fa Fix close buttons tooltips (#3525) 2025-08-04 15:22:59 +02:00
spastorelli f220229e5e Fix stepper title misalignment (#3524) 2025-08-04 09:34:22 +02:00
Greg Bergé 61b4cb8d7c Upgrade Argos (#3522) 2025-08-01 16:07:35 +02:00
Greg Bergé 185cdb4883 Fix scrolling to anchor positionning (#3521) 2025-08-01 15:44:27 +02:00
Zeno Kapitein 2cdba53451 Upgrade to Tailwind v4 (#3206) 2025-08-01 13:03:44 +02:00
Greg Bergé 950f6c36cf greg/rnd 6997 fix long openapi responses label (#3518) 2025-07-31 12:08:57 +00:00
Zeno Kapitein ea37977b1e Fix inconsistent state between q= and ask= and keyboard navigation (#3519) 2025-07-31 13:20:27 +02:00
Greg Bergé 81da82aed7 Increase tests max duration (#3517) 2025-07-30 18:33:56 +00:00
Greg Bergé 39a3518651 Show a HoverCard when an internal link is broken (#3516) 2025-07-30 18:09:57 +02:00
spastorelli d903273759 Strip visitor.* param when normalizing URL (#3512) 2025-07-30 17:22:09 +02:00
Taran Vohra d130532f69 Suggest questions in the current space context (#3514) 2025-07-30 20:36:40 +05:30
Zeno Kapitein 94fcd21f34 Improve ?ask= init handling (#3513) 2025-07-30 13:17:15 +02:00
conico974 1d2db4ae2b Fix cookie handling for AI assistant server actions (#3509)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-30 12:29:51 +02:00
Steven H d270b4af9b Don't track events if a special middleware header is set. (#3511) 2025-07-30 09:26:01 +01:00
Zeno Kapitein ccfbd3c1a8 Fix AI Search Query (#3510) 2025-07-30 07:44:37 +00:00
Claire Chabas 5c4923557f Temporarily remove highlighting for PowerShell and C++ in Safari to avoid page crash (#3508) 2025-07-29 20:39:26 +02:00
Zeno Kapitein b1f608b865 Implement basic URL scheme for Assistant & AI Search (#3503) 2025-07-29 16:13:44 +02:00
conico974 c7993756dd Fix regional caching of tag cache (#3504)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-29 13:27:56 +02:00
Nolann B. d02465823f Prevent page breaks inside blocks when printing (#3507) 2025-07-28 18:08:08 +02:00
Greg Bergé 6b9f6b5b79 Improve french translations (#3501) 2025-07-28 14:05:04 +02:00
Taran Vohra df7de8fb0c Fix an issue where links were not rendering properly in Ask AI answers (#3505) 2025-07-28 16:43:18 +05:30
147 changed files with 4142 additions and 1239 deletions
+4 -4
View File
@@ -102,7 +102,7 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing v2
needs: deploy-v2-vercel
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -123,7 +123,7 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing v2 (Cloudflare)
needs: deploy-v2-cloudflare
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -144,7 +144,7 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing Customers v2
needs: deploy-v2-vercel
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -165,7 +165,7 @@ jobs:
runs-on: ubuntu-latest
name: Visual Testing Customers v2 (Cloudflare)
needs: deploy-v2-cloudflare
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
+2 -1
View File
@@ -21,7 +21,8 @@
"packages/openapi-parser/src/fixtures/**/*",
"packages/emoji-codepoints/index.ts",
"packages/icons/src/data/*.json",
"packages/gitbook/worker-configuration.d.ts"
"packages/gitbook/worker-configuration.d.ts",
"**/*.css"
]
},
"formatter": {
+201 -67
View File
@@ -48,7 +48,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.14.0",
"version": "0.14.1",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/cache-tags": "workspace:*",
@@ -60,7 +60,7 @@
"@gitbook/react-contentkit": "workspace:*",
"@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*",
"@opennextjs/cloudflare": "^1.4.0",
"@opennextjs/cloudflare": "^1.6.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-navigation-menu": "^1.2.3",
@@ -118,8 +118,9 @@
"zustand": "^5.0.3",
},
"devDependencies": {
"@argos-ci/playwright": "^5.0.5",
"@playwright/test": "^1.51.1",
"@argos-ci/playwright": "^5.0.9",
"@playwright/test": "^1.54.2",
"@tailwindcss/postcss": "^4.1.11",
"@types/js-cookie": "^3.0.6",
"@types/jsontoxml": "^1.0.5",
"@types/jsonwebtoken": "^9.0.6",
@@ -131,14 +132,13 @@
"@types/react": "18.3.13",
"@types/react-dom": "18.3.1",
"@types/rison": "^0.0.9",
"autoprefixer": "^10",
"deepmerge": "^4.3.1",
"env-cmd": "^10.1.0",
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"psi": "^4.1.0",
"stylelint": "^16.16.0",
"tailwindcss": "^3.4.0",
"tailwindcss": "^4.1.11",
"ts-essentials": "^10.0.1",
"typescript": "^5.5.3",
"vercel": "^39.3.0",
@@ -166,7 +166,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "2.2.1",
"version": "2.2.2",
"dependencies": {
"@scalar/openapi-parser": "^0.18.0",
"@scalar/openapi-types": "^0.1.9",
@@ -213,7 +213,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.3.3",
"version": "1.3.4",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.3.16",
@@ -246,7 +246,7 @@
"react-dom": "^19.0.0",
},
"catalog": {
"@gitbook/api": "^0.129.0",
"@gitbook/api": "^0.130.0",
},
"packages": {
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
@@ -259,15 +259,17 @@
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@argos-ci/api-client": ["@argos-ci/api-client@0.8.1", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.5" } }, "sha512-3IHv7ANSPNO6OwWgwULlHbP9/tFV9kQDu6+nL9jysfPkGj0GgtrOsyBb+iU931c7wSMo1OD+XNujCnRzDD968w=="],
"@argos-ci/browser": ["@argos-ci/browser@4.1.2", "", {}, "sha512-OljPFzxSNndWSwMBxKGtN3p8lSbt73z+/0CIXbBVTQOPjPMErXWjgtrFL23xqtTq5wdds3uxGv7tjVDWJbgBYg=="],
"@argos-ci/browser": ["@argos-ci/browser@4.2.0", "", {}, "sha512-xztv7hMNJyAYw2FXZDm4OCtw+jotJv0BPp5/9dljKrJ0us/2dDEEYKAvVcxzdzRpSe7VOtXSdsoFf0XCCPmxtQ=="],
"@argos-ci/core": ["@argos-ci/core@3.2.0", "", { "dependencies": { "@argos-ci/api-client": "0.8.1", "@argos-ci/util": "2.3.1", "axios": "^1.8.4", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-wraH5RGFpoU7zcKV2BVi4LvY+5bWR/Ds6OVhUtq3jtd7I3qJRFVFq2IX1TfPPvBBA7wX0OqcrqgIWAQwy+vsoA=="],
"@argos-ci/core": ["@argos-ci/core@3.2.1", "", { "dependencies": { "@argos-ci/api-client": "0.8.1", "@argos-ci/util": "2.3.2", "axios": "^1.8.4", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-P+tGofNLAtH0+e87M8sZc+juAtbOcnV6z2nA2MwB2OzUVVXGINJHAF2cK0ZUyXC9d8a7RL0+rQWkP4vXDA/gBw=="],
"@argos-ci/playwright": ["@argos-ci/playwright@5.0.5", "", { "dependencies": { "@argos-ci/browser": "4.1.2", "@argos-ci/core": "3.2.0", "@argos-ci/util": "2.3.1", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-ZqaqptjI8rVUjD2KQzpvI1olFDiiMjgDaIyFOMWo8531cB7CMCwPXDNsFw2psUfsz3a3LP91eH2OrJopmevEkQ=="],
"@argos-ci/playwright": ["@argos-ci/playwright@5.0.9", "", { "dependencies": { "@argos-ci/browser": "4.2.0", "@argos-ci/core": "3.2.1", "@argos-ci/util": "2.3.2", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-FbUPjNVm+Qer9aUU56oGNiPeynqDCh33GOYKekzjikBlxWFZfhiW4NA+sOXF0VWzGb5ni9l1U7fy6ycOTSalAA=="],
"@argos-ci/util": ["@argos-ci/util@2.3.1", "", {}, "sha512-kE61HU2480fbAnimmA4x9HK45ZJvkoqLdW5GxT5uvwhkclQykVd2S6WfGFUr3JokTXfZ5LZEEfoWgtGA316KSQ=="],
"@argos-ci/util": ["@argos-ci/util@2.3.2", "", {}, "sha512-xtNHJxpWYNst/sMNn4Jv/vkODuFsJ+APr4FBeoFUdIa+Izjl4ZFHsYA2PUyu+ygIpQCkof8yZLL9U1/VpiyyIw=="],
"@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=="],
@@ -609,7 +611,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.129.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-Uh+k/BiDgdXj5a8BIlvwfEXdZNNlQMFDlGKmivUYH1gvFySqrTO6PjR/HtUOZEdNauAeWZmGoB8CLuwrVZS+YA=="],
"@gitbook/api": ["@gitbook/api@0.130.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-nfAPfTISUou+hJPZLrBsuUSSaHEeWB6eZ4adapWAagmaeeA3CpfopDROXcqGJPBHYPfvfLZIGdgyYhyYC3Objw=="],
"@gitbook/cache-tags": ["@gitbook/cache-tags@workspace:packages/cache-tags"],
@@ -787,9 +789,9 @@
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@opennextjs/aws": ["@opennextjs/aws@3.7.0", "", { "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-JXUZice+CedEQW20hnBVqzBEj+jfr4Oe2LVYSE4RNKdfHVIeYG+WJAop14TxRJ+NugKWGcJx6opf944l+ZG7XQ=="],
"@opennextjs/aws": ["@opennextjs/aws@3.7.1", "", { "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-ryV5cQucSJDU0TF3+Jzqn9g0/+zhUZgjNopSnchPvH0SxSAbKkNaNH7SlnWBhykjusqHaFzQ8dfEjcjAq7TJSQ=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.4.0", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.7.0", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^4.19.1" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-nEtxmSo6/y83Qw2TSU8rBpljaGl3NUl5GenD/c71h6YqgbP4QURSxMh7ySe7KK+vtQbilD7WePXovqL4Y6D6dw=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.6.2", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.7.1", "cloudflare": "^4.4.1", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "wrangler": "^4.24.4" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-N96OTbp1970NwLP6dNkAzBDrxjthLQ3VXHcjKl8edqNbJ/iT75H60ZYHU/ZZ7gSZQeJluPvPb1n0ZPQ08EooCQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -797,7 +799,7 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@playwright/test": ["@playwright/test@1.51.1", "", { "dependencies": { "playwright": "1.51.1" }, "bin": { "playwright": "cli.js" } }, "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q=="],
"@playwright/test": ["@playwright/test@1.54.2", "", { "dependencies": { "playwright": "1.54.2" }, "bin": { "playwright": "cli.js" } }, "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA=="],
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
@@ -1247,6 +1249,36 @@
"@tailwindcss/container-queries": ["@tailwindcss/container-queries@0.1.1", "", { "peerDependencies": { "tailwindcss": ">=3.2.0" } }, "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.16", "", { "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.10.8", "", {}, "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA=="],
@@ -1301,6 +1333,8 @@
"@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="],
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
"@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="],
"@types/object-hash": ["@types/object-hash@3.0.6", "", {}, "sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w=="],
@@ -1407,6 +1441,8 @@
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ai": ["ai@4.3.0", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "@ai-sdk/provider-utils": "2.2.4", "@ai-sdk/react": "1.2.6", "@ai-sdk/ui-utils": "1.2.5", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-PxyQYKhWaU3LiZEpeKRaekVonZIbWdKAwgnqm0CSAxy1MFufmYEC5SM5Mc9uiK2DoHcbAL3d1jyaQ2fSDAJL8w=="],
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
@@ -1429,7 +1465,7 @@
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -1453,8 +1489,6 @@
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="],
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="],
@@ -1489,8 +1523,6 @@
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["browserslist@4.24.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
@@ -1549,8 +1581,12 @@
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
"clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
"cloudflare": ["cloudflare@4.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"code-block-writer": ["code-block-writer@10.1.1", "", {}, "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw=="],
@@ -1653,7 +1689,7 @@
"detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
@@ -1697,8 +1733,6 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.36", "", {}, "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw=="],
"emoji-assets": ["emoji-assets@8.0.0", "", {}, "sha512-Y2M8Y5N6AUxKtE7AYj61+V3tU1CAcLQLX4kCO7HJEH1K+o2lgFd3ieiGSbZ8/qfz6qYxr0NUY+5Ds0WQer85og=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -1709,6 +1743,8 @@
"end-of-stream": ["end-of-stream@1.1.0", "", { "dependencies": { "once": "~1.3.0" } }, "sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ=="],
"enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
@@ -1823,11 +1859,13 @@
"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=="],
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
"format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"framer-motion": ["framer-motion@10.18.0", "", { "dependencies": { "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w=="],
@@ -1851,6 +1889,10 @@
"generic-pool": ["generic-pool@3.4.2", "", {}, "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
"get-intrinsic": ["get-intrinsic@1.2.4", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@@ -1865,7 +1907,7 @@
"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@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
@@ -1979,6 +2021,8 @@
"human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="],
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
"humanize-url": ["humanize-url@2.1.1", "", { "dependencies": { "normalize-url": "^4.5.1" } }, "sha512-V4nxsPGNE7mPjr1qDp471YfW8nhBiTRWrG/4usZlpvFU8I7gsV7Jvrrzv/snbLm5dWO3dr1ennu2YqnhTWFmYA=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -2059,7 +2103,7 @@
"jackspeak": ["jackspeak@4.0.2", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw=="],
"jiti": ["jiti@1.21.6", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="],
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
@@ -2117,6 +2161,28 @@
"leven": ["leven@4.0.0", "", {}, "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
@@ -2163,7 +2229,7 @@
"lru-queue": ["lru-queue@0.1.0", "", { "dependencies": { "es5-ext": "~0.10.2" } }, "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ=="],
"magic-string": ["magic-string@0.30.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
@@ -2331,22 +2397,20 @@
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
"node-forge": ["node-forge@0.10.0", "", {}, "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="],
"node-gyp-build": ["node-gyp-build@4.8.2", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw=="],
"node-releases": ["node-releases@2.0.18", "", {}, "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="],
"nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
"normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
"normalize-url": ["normalize-url@4.5.1", "", {}, "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
@@ -2723,7 +2787,9 @@
"tailwind-shades": ["tailwind-shades@1.1.2", "", {}, "sha512-gsyyr9NtfPS1QNWV/YQMcoO5tLStTwp/So2I6jnPWmCFmqY8JEM1csBoq0nbFtuRsjYypC/Bm09pprUyLL/Zjg=="],
"tailwindcss": ["tailwindcss@3.4.13", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw=="],
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
"tar": ["tar@4.4.18", "", { "dependencies": { "chownr": "^1.1.4", "fs-minipass": "^1.2.7", "minipass": "^2.9.0", "minizlib": "^1.3.3", "mkdirp": "^0.5.5", "safe-buffer": "^5.2.1", "yallist": "^3.1.1" } }, "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg=="],
@@ -2841,8 +2907,6 @@
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"update-browserslist-db": ["update-browserslist-db@1.1.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A=="],
"update-notifier": ["update-notifier@4.1.3", "", { "dependencies": { "boxen": "^4.2.0", "chalk": "^3.0.0", "configstore": "^5.0.1", "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", "is-ci": "^2.0.0", "is-installed-globally": "^0.3.1", "is-npm": "^4.0.0", "is-yarn-global": "^0.3.0", "latest-version": "^5.0.0", "pupa": "^2.0.1", "semver-diff": "^3.1.1", "xdg-basedir": "^4.0.0" } }, "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
@@ -2897,6 +2961,8 @@
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
"web-vitals": ["web-vitals@0.2.4", "", {}, "sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -2913,7 +2979,7 @@
"wrangler": ["wrangler@4.10.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.1", "blake3-wasm": "2.1.5", "esbuild": "0.24.2", "miniflare": "4.20250409.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.15", "workerd": "1.20250409.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250409.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-fTE4hZ79msEUt8+HEjl/8Q72haCyzPLu4PgrU3L81ysmjrMEdiYfUPqnvCkBUVtJvrDNdctTEimkufT1Y0ipNg=="],
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@@ -2929,10 +2995,14 @@
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
"yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
@@ -3607,12 +3677,16 @@
"@formatjs/intl-localematcher/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@headlessui/tailwindcss/tailwindcss": ["tailwindcss@3.4.13", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw=="],
"@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=="],
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"@lezer/css/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
"@lezer/highlight/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
@@ -3637,6 +3711,8 @@
"@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
"@mapbox/node-pre-gyp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"@mapbox/node-pre-gyp/tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
@@ -3647,6 +3723,8 @@
"@opennextjs/aws/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"@playwright/test/playwright": ["playwright@1.54.2", "", { "dependencies": { "playwright-core": "1.54.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw=="],
"@radix-ui/react-collection/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "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-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
@@ -3965,6 +4043,20 @@
"@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide/tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
"@ts-morph/common/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
@@ -4013,6 +4105,8 @@
"@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"@vue/compiler-sfc/magic-string": ["magic-string@0.30.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw=="],
"@vue/compiler-sfc/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"@vueuse/integrations/@vueuse/core": ["@vueuse/core@11.2.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "11.2.0", "@vueuse/shared": "11.2.0", "vue-demi": ">=0.14.10" } }, "sha512-JIUwRcOqOWzcdu1dGlfW04kaJhW3EXnnjJJfLTtddJanymTL7lF1C0+dVVZ/siLfc73mWn+cGP1PE1PKPruRSA=="],
@@ -4049,6 +4143,14 @@
"camelcase-keys/quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="],
"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=="],
"cloudflare/@types/node": ["@types/node@18.19.121", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ=="],
"cloudflare/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"codemirror/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="],
"codemirror/@codemirror/commands": ["@codemirror/commands@6.7.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw=="],
@@ -4085,8 +4187,6 @@
"express/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
@@ -4155,8 +4255,6 @@
"meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
"micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
"micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
@@ -4241,6 +4339,8 @@
"send/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
@@ -4255,14 +4355,6 @@
"sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"tailwindcss/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"tailwindcss/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"tar/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
"terminal-link/supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
@@ -4271,8 +4363,6 @@
"ts-node/acorn": ["acorn@8.12.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg=="],
"ts-node/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
"unenv/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"update-notifier/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
@@ -4287,10 +4377,14 @@
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"wrap-ansi/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=="],
"wrap-ansi/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=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"yargs/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=="],
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
"youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"@argos-ci/core/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
@@ -4331,6 +4425,8 @@
"@argos-ci/core/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@argos-ci/core/sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
@@ -4765,10 +4861,26 @@
"@dotenvx/dotenvx/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"@headlessui/tailwindcss/tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"@headlessui/tailwindcss/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"@headlessui/tailwindcss/tailwindcss/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"@headlessui/tailwindcss/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"@headlessui/tailwindcss/tailwindcss/jiti": ["jiti@1.21.6", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="],
"@headlessui/tailwindcss/tailwindcss/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"@headlessui/tailwindcss/tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"@mapbox/node-pre-gyp/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"@mapbox/node-pre-gyp/tar/minizlib": ["minizlib@3.0.1", "", { "dependencies": { "minipass": "^7.0.4", "rimraf": "^5.0.5" } }, "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg=="],
@@ -4783,6 +4895,8 @@
"@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=="],
"@playwright/test/playwright/playwright-core": ["playwright-core@1.54.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "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-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
@@ -4871,7 +4985,15 @@
"@smithy/util-endpoints/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw=="],
"@ts-morph/common/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
"@tailwindcss/oxide/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"@tailwindcss/oxide/tar/minizlib": ["minizlib@3.0.1", "", { "dependencies": { "minipass": "^7.0.4", "rimraf": "^5.0.5" } }, "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg=="],
"@tailwindcss/oxide/tar/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"@tailwindcss/oxide/tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
@@ -4909,6 +5031,12 @@
"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"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=="],
"cloudflare/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"codemirror/@codemirror/autocomplete/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
"codemirror/@codemirror/commands/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
@@ -4929,8 +5057,6 @@
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"google-auth-library/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
"gtoken/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
@@ -4969,16 +5095,6 @@
"sucrase/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=="],
"tailwindcss/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"tailwindcss/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tailwindcss/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"wrangler/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"wrangler/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
@@ -5017,10 +5133,16 @@
"wrangler/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"wrangler/sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"yargs/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"yargs/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"@argos-ci/core/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
"@aws-sdk/client-dynamodb/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
@@ -5157,6 +5279,16 @@
"@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"@headlessui/tailwindcss/tailwindcss/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"@headlessui/tailwindcss/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@headlessui/tailwindcss/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"@headlessui/tailwindcss/tailwindcss/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@headlessui/tailwindcss/tailwindcss/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -5181,6 +5313,8 @@
"wrangler/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@argos-ci/core/sharp/@img/sharp-wasm32/@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw=="],
+1 -1
View File
@@ -34,7 +34,7 @@
"workspaces": {
"packages": ["packages/*"],
"catalog": {
"@gitbook/api": "^0.129.0"
"@gitbook/api": "^0.130.0"
}
},
"patchedDependencies": {
+26
View File
@@ -1,5 +1,31 @@
# gitbook
## 0.15.0
### Minor Changes
- d130532: Suggest questions in the current space context
### Patch Changes
- 44f4151: Fix close buttons tooltips
- d903273: Strip visitor params from URL
- df7de8f: Fix an issue where links were not rendering properly in Ask AI answers
- d270b4a: All tracking is now disabled for dynamic routes such as the site preview.
- b1f608b: Implement basic URL scheme for assistant with ask
- d024658: Prevent page breaks inside blocks when printing
- 185cdb4: Fix scrolling to anchor positionning
- 5c49235: Remove highlighting in Safari for PowerShell and C++ to avoid page crash until next version with bug fix is released
- f1a6dec: Update OpenAPI parser
- 2cdba53: Upgrade to Tailwind v4
- Updated dependencies [2cdba53]
- Updated dependencies [f1a6dec]
- @gitbook/react-math@0.6.1
- @gitbook/icons@0.2.2
- @gitbook/openapi-parser@3.0.0
- @gitbook/react-contentkit@0.7.3
- @gitbook/react-openapi@1.3.5
## 0.14.1
### Patch Changes
+248 -56
View File
@@ -1,4 +1,5 @@
import {
CustomizationAIMode,
CustomizationBackground,
CustomizationCorners,
CustomizationDefaultMonospaceFont,
@@ -19,6 +20,7 @@ import {
import { getSiteAPIToken } from '../tests/utils';
import {
type Test,
type TestsCase,
allDeprecatedThemePresets,
allLocales,
@@ -34,6 +36,250 @@ import {
waitForNotFound,
} from './util';
const searchTestCases: Test[] = [
{
name: 'Search - AI Mode: None - Complete flow',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.None,
},
}),
screenshot: false,
run: async (page) => {
const searchInput = page.getByTestId('search-input');
await searchInput.focus();
await expect(page.getByTestId('search-results')).toHaveCount(0); // No pop-up yet because there's no recommended questions.
// Fill search input, expecting search results
await searchInput.fill('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
const pageResults = await page.getByTestId('search-page-result').all();
await expect(pageResults.length).toBeGreaterThan(2);
const pageSectionResults = await page.getByTestId('search-page-section-result').all();
await expect(pageSectionResults.length).toBeGreaterThan(2);
await expect(page.getByTestId('search-ask-question')).toHaveCount(0); // No AI search results with aiMode=None.
},
},
{
name: 'Search - AI Mode: None - Keyboard shortcut',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.None,
},
}),
screenshot: false,
run: async (page) => {
await page.keyboard.press('ControlOrMeta+K');
await expect(page.getByTestId('search-input')).toBeFocused();
},
},
{
name: 'Search - AI Mode: None - URL query (Initial)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.None,
},
})}&q=`,
run: async (page) => {
await expect(page.getByTestId('search-results')).toHaveCount(0); // No pop-up yet because there's no recommended questions.
},
},
{
name: 'Search - AI Mode: None - URL query (Results)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.None,
},
})}&q=gitbook`,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-input')).toHaveValue('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
{
name: 'Search - AI Mode: Search - Complete flow',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.Search,
},
}),
screenshot: false,
run: async (page) => {
const searchInput = page.getByTestId('search-input');
// Focus search input, expecting recommended questions
await searchInput.focus();
await expect(page.getByTestId('search-results')).toBeVisible();
const recommendedQuestions = await page
.getByTestId('search-recommended-question')
.all();
await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
// Fill search input, expecting AI search option
await searchInput.fill('What is gitbook?');
await expect(page.getByTestId('search-results')).toBeVisible();
const aiSearchResult = page.getByTestId('search-ask-question');
await expect(aiSearchResult).toBeVisible();
await aiSearchResult.click();
await expect(page.getByTestId('search-ask-answer')).toBeVisible({
timeout: 15_000,
});
},
},
{
name: 'Search - AI Mode: Search - URL query (Initial)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Search,
},
})}&q=`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-results')).toBeVisible();
const recommendedQuestions = await page
.getByTestId('search-recommended-question')
.all();
await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
},
},
{
name: 'Search - AI Mode: Search - URL query (Results)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Search,
},
})}&q=gitbook`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-input')).toHaveValue('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
{
name: 'Ask - AI Mode: Search - URL query (Ask initial)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Search,
},
})}&ask=`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-results')).toBeVisible();
const recommendedQuestions = await page
.getByTestId('search-recommended-question')
.all();
await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
},
},
{
name: 'Ask - AI Mode: Search - URL query (Ask results)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Search,
},
})}&ask=What+is+GitBook%3F`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).toBeFocused();
await expect(page.getByTestId('search-input')).toHaveValue('What is GitBook?');
await expect(page.getByTestId('search-ask-answer')).toBeVisible();
},
},
{
name: 'Ask - AI Mode: Assistant - Complete flow',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.Assistant,
},
}),
screenshot: false,
run: async (page) => {
const searchInput = page.locator('css=[data-testid="search-input"]');
// Focus search input, expecting recommended questions
await searchInput.focus();
await expect(page.getByTestId('search-results')).toBeVisible();
const recommendedQuestions = await page
.getByTestId('search-recommended-question')
.all();
await expect(recommendedQuestions.length).toBeGreaterThan(2); // Expect at least 3 questions
// Fill search input, expecting AI search option
await searchInput.fill('What is gitbook?');
const aiSearchResult = page.getByTestId('search-ask-question');
await expect(aiSearchResult).toBeVisible();
await aiSearchResult.click();
await expect(page.getByTestId('ai-chat')).toBeVisible();
},
},
{
name: 'Ask - AI Mode: Assistant - Keyboard shortcut',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.Assistant,
},
}),
screenshot: false,
run: async (page) => {
await page.keyboard.press('ControlOrMeta+J');
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
},
},
{
name: 'Ask - AI Mode: Assistant - Button',
url: getCustomizationURL({
ai: {
mode: CustomizationAIMode.Assistant,
},
}),
screenshot: false,
run: async (page) => {
await page.getByTestId('ai-chat-button').click();
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
},
},
{
name: 'Ask - AI Mode: Assistant - URL query (Initial)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Assistant,
},
})}&ask=`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).not.toBeFocused();
await expect(page.getByTestId('search-input')).not.toHaveValue('What is GitBook?');
await expect(page.getByTestId('ai-chat')).toBeVisible();
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
},
},
{
name: 'Ask - AI Mode: Assistant - URL query (Results)',
url: `${getCustomizationURL({
ai: {
mode: CustomizationAIMode.Assistant,
},
})}&ask=What+is+GitBook%3F`,
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-input')).not.toBeFocused();
await expect(page.getByTestId('search-input')).not.toHaveValue('What is GitBook?');
await expect(page.getByTestId('ai-chat')).toBeVisible({
timeout: 15_000,
});
await expect(page.getByTestId('ai-chat-message').first()).toHaveText(
'What is GitBook?'
);
},
},
];
const testCases: TestsCase[] = [
{
name: 'GitBook Site (Single Variant)',
@@ -53,34 +299,7 @@ const testCases: TestsCase[] = [
);
},
},
{
name: 'Search',
url: '?q=',
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-results')).toBeVisible();
const allItems = await page.getByTestId('search-result-item').all();
// Expect at least 3 questions
await expect(allItems.length).toBeGreaterThan(2);
},
},
{
name: 'Search Results',
url: '?q=gitbook',
run: async (page) => {
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
{
name: 'AI Search',
url: '?q=What+is+GitBook%3F&ask=true',
run: async (page) => {
await expect(page.getByTestId('search-ask-answer')).toBeVisible({
timeout: 15_000,
});
},
screenshot: false,
},
...searchTestCases,
{
name: 'Not found',
url: 'content-not-found',
@@ -279,34 +498,7 @@ const testCases: TestsCase[] = [
url: '',
run: waitForCookiesDialog,
},
{
name: 'Search',
url: '?q=',
screenshot: false,
run: async (page) => {
await expect(page.getByTestId('search-results')).toBeVisible();
const allItems = await page.getByTestId('search-result-item').all();
// Expect at least 3 questions
await expect(allItems.length).toBeGreaterThan(2);
},
},
{
name: 'Search Results',
url: '?q=gitbook',
run: async (page) => {
await expect(page.getByTestId('search-results')).toBeVisible();
},
},
{
name: 'AI Search',
url: '?q=What+is+GitBook%3F&ask=true',
run: async (page) => {
await expect(page.getByTestId('search-ask-answer')).toBeVisible({
timeout: 15_000,
});
},
screenshot: false,
},
...searchTestCases,
{
name: 'Not found',
url: 'content-not-found',
@@ -2,6 +2,9 @@ import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/ini
import { DurableObject } from 'cloudflare:workers';
//Only needed to run locally, in prod we'll use the one from do.js
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
// Only needed to run locally, in prod we'll use the one from do.js
export class R2WriteBuffer extends DurableObject {
writePromise;
@@ -36,13 +36,17 @@
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["R2WriteBuffer"]
"new_sqlite_classes": ["R2WriteBuffer", "DOShardedTagCache"]
}
]
},
@@ -22,6 +22,9 @@
"vars": {
"STAGE": "dev",
"NEXT_PRIVATE_DEBUG_CACHE": "true",
// When deployed locally, we don't have access to the tag cache here,
// we should just bypass the cache to go to the server directly
"SHOULD_BYPASS_CACHE": "true",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"r2_buckets": [
+24 -11
View File
@@ -36,6 +36,11 @@ class GitbookIncrementalCache implements IncrementalCache {
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
if (process.env.SHOULD_BYPASS_CACHE === 'true') {
// We are in a local middleware environment, we should bypass the cache
// and go directly to the server.
return null;
}
try {
// Check local cache first if available
const localCacheEntry = await localCache.match(this.getCacheUrlKey(cacheKey));
@@ -134,19 +139,27 @@ class GitbookIncrementalCache implements IncrementalCache {
}
async writeToR2(key: string, value: string): Promise<void> {
const env = getCloudflareContext().env as {
WRITE_BUFFER: DurableObjectNamespace<
Rpc.DurableObjectBranded & {
write: (key: string, value: string) => Promise<void>;
}
>;
};
const id = env.WRITE_BUFFER.idFromName(key);
try {
const env = getCloudflareContext().env as {
WRITE_BUFFER: DurableObjectNamespace<
Rpc.DurableObjectBranded & {
write: (key: string, value: string) => Promise<void>;
}
>;
};
const id = env.WRITE_BUFFER.idFromName(key);
// A stub is a client used to invoke methods on the Durable Object
const stub = env.WRITE_BUFFER.get(id);
// A stub is a client used to invoke methods on the Durable Object
const stub = env.WRITE_BUFFER.get(id);
await stub.write(key, value);
await stub.write(key, value);
} catch {
// We fallback to writing directly to R2
// it can fail locally because the limit is 1Mb per args
// It is 32Mb in production, so we should be fine
const r2 = getCloudflareContext().env[BINDING_NAME];
r2?.put(key, value);
}
}
async getCacheInstance(): Promise<Cache> {
@@ -1,4 +1,4 @@
import { getLogger } from '@/lib/logger';
import { createLogger, getLogger } from '@/lib/logger';
import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js';
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
@@ -6,8 +6,9 @@ import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-ca
const originalTagCache = doShardedTagCache({
baseShardSize: 12,
regionalCache: true,
// We can probably increase this value even further
regionalCacheTtlSec: 60,
regionalCacheTtlSec: 60 * 5 /* 5 minutes */,
// Because we invalidate the Cache API on update, we can safely set this to true
regionalCacheDangerouslyPersistMissingTags: true,
shardReplication: {
numberOfSoftReplicas: 2,
numberOfHardReplicas: 1,
@@ -20,6 +21,7 @@ const originalTagCache = doShardedTagCache({
export default {
name: 'GitbookTagCache',
mode: 'nextMode',
// We don't really use this one, as of now it is only used for soft tags
getLastRevalidated: async (tags: string[]) => {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
@@ -29,12 +31,20 @@ export default {
return await originalTagCache.getLastRevalidated(tagsToCheck);
},
hasBeenRevalidated: async (tags: string[], lastModified?: number) => {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
return false; // If no tags to check, return false
}
try {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
return false; // If no tags to check, return false
}
return await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
return await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
} catch (e) {
createLogger('gitbookTagCache', {}).error(
`hasBeenRevalidated - Error checking tags ${tags.join(', ')}`,
e
);
return false; // In case of error, return false
}
},
writeTags: async (tags: string[]) => {
const tagsToWrite = tags.filter(softTagFilter);
@@ -43,7 +53,7 @@ export default {
logger.warn('writeTags - No valid tags to write');
return; // If no tags to write, exit early
}
// Write only the filtered tags
await originalTagCache.writeTags(tagsToWrite);
},
} satisfies NextModeTagCache;
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.14.1",
"version": "0.15.0",
"private": true,
"dependencies": {
"@gitbook/api": "catalog:",
@@ -13,7 +13,7 @@
"@gitbook/react-contentkit": "workspace:*",
"@gitbook/react-math": "workspace:*",
"@gitbook/react-openapi": "workspace:*",
"@opennextjs/cloudflare": "^1.4.0",
"@opennextjs/cloudflare": "^1.6.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-navigation-menu": "^1.2.3",
@@ -22,6 +22,7 @@
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
"@tusbar/cache-control": "^1.0.2",
"ai": "^4.2.2",
"assert-never": "^1.2.1",
"bun-types": "^1.1.20",
@@ -49,7 +50,7 @@
"object-identity": "^0.1.2",
"openapi-types": "^12.1.3",
"p-map": "^7.0.3",
"@tusbar/cache-control": "^1.0.2",
"quick-lru": "^7.0.1",
"react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
@@ -67,12 +68,12 @@
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
"warn-once": "^0.1.1",
"zustand": "^5.0.3",
"quick-lru": "^7.0.1"
"zustand": "^5.0.3"
},
"devDependencies": {
"@argos-ci/playwright": "^5.0.5",
"@playwright/test": "^1.51.1",
"@argos-ci/playwright": "^5.0.9",
"@playwright/test": "^1.54.2",
"@tailwindcss/postcss": "^4.1.11",
"@types/js-cookie": "^3.0.6",
"@types/jsontoxml": "^1.0.5",
"@types/jsonwebtoken": "^9.0.6",
@@ -84,14 +85,13 @@
"@types/react": "18.3.13",
"@types/react-dom": "18.3.1",
"@types/rison": "^0.0.9",
"autoprefixer": "^10",
"deepmerge": "^4.3.1",
"env-cmd": "^10.1.0",
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"psi": "^4.1.0",
"stylelint": "^16.16.0",
"tailwindcss": "^3.4.0",
"tailwindcss": "^4.1.11",
"ts-essentials": "^10.0.1",
"typescript": "^5.5.3",
"vercel": "^39.3.0"
@@ -100,7 +100,7 @@
"generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math && wrangler types",
"copy:icons": "gitbook-icons ./public/~gitbook/static/icons",
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math",
"dev": "env-cmd --silent -f ../../.env.local next --turbopack",
"dev": "env-cmd --silent -f ../../.env.local next",
"build": "next build",
"start": "next start",
"build:cloudflare": "opennextjs-cloudflare build",
+1 -2
View File
@@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};
@@ -5,8 +5,9 @@ import {
generateSiteLayoutMetadata,
generateSiteLayoutViewport,
} from '@/components/SiteLayout';
import { GITBOOK_DISABLE_TRACKING } from '@/lib/env';
import { getThemeFromMiddleware } from '@/lib/middleware';
import { shouldTrackEvents } from '@/lib/tracking';
import { headers } from 'next/headers';
interface SiteDynamicLayoutProps {
params: Promise<RouteLayoutParams>;
@@ -18,13 +19,14 @@ export default async function SiteDynamicLayout({
}: React.PropsWithChildren<SiteDynamicLayoutProps>) {
const { context, visitorAuthClaims } = await getDynamicSiteContext(await params);
const forcedTheme = await getThemeFromMiddleware();
const withTracking = shouldTrackEvents(await headers());
return (
<CustomizationRootLayout forcedTheme={forcedTheme} customization={context.customization}>
<SiteLayout
context={context}
forcedTheme={forcedTheme}
withTracking={!GITBOOK_DISABLE_TRACKING}
withTracking={withTracking}
visitorAuthClaims={visitorAuthClaims}
>
{children}
@@ -5,7 +5,7 @@ import {
generateSiteLayoutMetadata,
generateSiteLayoutViewport,
} from '@/components/SiteLayout';
import { GITBOOK_DISABLE_TRACKING } from '@/lib/env';
import { shouldTrackEvents } from '@/lib/tracking';
interface SiteStaticLayoutProps {
params: Promise<RouteLayoutParams>;
@@ -16,12 +16,13 @@ export default async function SiteStaticLayout({
children,
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
const { context, visitorAuthClaims } = await getStaticSiteContext(await params);
const withTracking = shouldTrackEvents();
return (
<CustomizationRootLayout customization={context.customization}>
<SiteLayout
context={context}
withTracking={!GITBOOK_DISABLE_TRACKING}
withTracking={withTracking}
visitorAuthClaims={visitorAuthClaims}
>
{children}
@@ -23,7 +23,7 @@ export function AIMessageView(
<div
key={index}
className={tcls(
'flex animate-fadeIn-slow flex-col gap-2',
'flex animate-fade-in-slow flex-col gap-2',
step.content.nodes.length > 0 ? 'has-content' : ''
)}
>
@@ -38,7 +38,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont
const { toolCall, context } = props;
return (
<div className="flex origin-left animate-scaleIn-slow items-start gap-2 text-sm text-tint-subtle">
<div className="flex origin-left animate-scale-in-slow items-start gap-2 text-sm text-tint-subtle">
<Icon
icon={getIconForToolCall(toolCall)}
className="mt-1 size-3 shrink-0 text-tint-subtle/8"
@@ -175,12 +175,12 @@ async function DescriptionForSearchToolCall(props: {
) : null}
</summary>
{hasResults ? (
<div className="mt-1 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all duration-500 [transition-behavior:allow-discrete] group-open:max-h-96 group-open:opacity-11">
<div className="mt-1 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all transition-discrete duration-500 group-open:max-h-96 group-open:opacity-11">
<ol className="space-y-1">
{searchResultsWithHrefs.map((result, index) => (
<li
key={`${result.pageId}-${index}`}
className="animate-fadeIn-slow"
className="animate-fade-in-slow"
style={{
animationDelay: `${index * 25}ms`,
}}
+212 -106
View File
@@ -5,6 +5,7 @@ import * as zustand from 'zustand';
import { AIMessageRole } from '@gitbook/api';
import * as React from 'react';
import { useTrackEvent } from '../Insights';
import { useSearch } from '../Search';
import { streamAIChatResponse } from './server-actions';
import { useAIMessageContextRef } from './useAIMessageContext';
@@ -30,6 +31,11 @@ export type AIChatState = {
*/
query: string | null;
/**
* The first query sent to the AI. This is appended to the URL when the AI chat is opened.
*/
initialQuery: string | null;
/**
* Messages in the session.
*/
@@ -56,20 +62,15 @@ export type AIChatState = {
export type AIChatController = {
/** Open the dialog */
open: () => void;
/** Close the dialog */
close: () => void;
/** Post a message to the session */
postMessage: (input: {
/** The message to post to the session. it can be markdown formatted. */
message: string;
}) => void;
postMessage: (input: { message: string }) => void;
/** Clear the conversation */
clear: () => void;
};
// Global state store for AI chat
const globalState = zustand.create<{
state: AIChatState;
setState: (fn: (state: AIChatState) => Partial<AIChatState>) => void;
@@ -83,6 +84,7 @@ const globalState = zustand.create<{
followUpSuggestions: [],
loading: false,
error: false,
initialQuery: null,
},
setState: (fn) => set((state) => ({ state: { ...state.state, ...fn(state.state) } })),
};
@@ -98,112 +100,216 @@ export function useAIChatState(): AIChatState {
/**
* Get the controller to interact with the AI chat.
* Integrates with search state to synchronize ?ask= parameter.
*/
export function useAIChatController(): AIChatController {
const messageContextRef = useAIMessageContextRef();
const setState = zustand.useStore(globalState, (state) => state.setState);
const trackEvent = useTrackEvent();
const [searchState, setSearchState] = useSearch(true);
// Open AI chat and sync with search state
const onOpen = React.useCallback(() => {
const { initialQuery } = globalState.getState().state;
setState((state) => ({ ...state, opened: true }));
// Update search state to show ask mode with first message or current ask value
setSearchState((prev) => ({
ask: prev?.ask ?? initialQuery ?? '',
query: prev?.query ?? null,
global: prev?.global ?? false,
open: false, // Close search popover when opening chat
}));
}, [setState, setSearchState]);
// Close AI chat and clear ask parameter
const onClose = React.useCallback(() => {
setState((state) => ({ ...state, opened: false }));
// Clear ask parameter but keep other search state
setSearchState((prev) => ({
ask: null,
query: prev?.query ?? null,
global: prev?.global ?? false,
open: false,
}));
}, [setState, setSearchState]);
// Post a message to the AI chat
const onPostMessage = React.useCallback(
async (input: { message: string }) => {
const { messages } = globalState.getState().state;
// For first message, update the ask parameter in URL
if (messages.length === 0) {
setSearchState((prev) => ({
ask: input.message,
query: prev?.query ?? null,
global: prev?.global ?? false,
open: false,
}));
}
trackEvent({ type: 'ask_question', query: input.message });
// Add user message and placeholder for AI response
setState((state) => {
return {
...state,
messages: [
...state.messages,
{
role: AIMessageRole.User,
content: input.message,
query: input.message,
},
{
role: AIMessageRole.Assistant,
content: null, // Placeholder for streaming response
},
],
query: input.message,
responseId: null,
followUpSuggestions: [],
loading: true,
error: false,
};
});
try {
const stream = await streamAIChatResponse({
message: input.message,
messageContext: messageContextRef.current,
previousResponseId: globalState.getState().state.responseId ?? undefined,
});
// Process streaming response
for await (const data of stream) {
if (!data) continue;
const event = data.event;
switch (event.type) {
case 'response_finish': {
setState((state) => ({
...state,
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,
error: false,
}));
break;
}
case 'response_followup_suggestion': {
setState((state) => ({
...state,
followUpSuggestions: [
...state.followUpSuggestions,
...event.suggestions,
],
}));
break;
}
}
// Update the assistant message with streamed content
setState((state) => ({
...state,
messages: [
...state.messages.slice(0, -1),
{
role: AIMessageRole.Assistant,
content: data.content,
},
],
}));
}
setState((state) => ({
...state,
loading: false,
error: false,
}));
} catch {
setState((state) => ({
...state,
loading: false,
error: true,
}));
}
},
[messageContextRef.current, setState, setSearchState, trackEvent]
);
// Clear the conversation and reset ask parameter
const onClear = React.useCallback(() => {
setState((state) => ({
opened: state.opened,
loading: false,
messages: [],
query: null,
followUpSuggestions: [],
responseId: null,
error: false,
initialQuery: null,
}));
// Reset ask parameter to empty string (keeps chat open but clears content)
setSearchState((prev) => ({
ask: '',
query: prev?.query ?? null,
global: prev?.global ?? false,
open: false,
}));
}, [setState, setSearchState]);
// Auto-trigger AI chat when ?ask= parameter appears in URL (only once)
React.useEffect(() => {
const hasNoAsk = searchState?.ask === undefined || searchState?.ask === null;
const hasQuery = searchState?.query !== null;
// Don't trigger if we have a regular search query active
if (hasNoAsk) return;
if (hasQuery && searchState.open === false) return;
// Open the chat when ask parameter appears
onOpen();
// Auto-post the message if ask has content
if (searchState?.ask?.trim()) {
const trimmedAsk = searchState.ask.trim();
const { loading, initialQuery } = globalState.getState().state;
// Don't trigger if we're already posting a message
if (loading) return;
// Only initialize once per URL ask value
if (initialQuery === trimmedAsk) return;
// Wait for messageContextRef to be defined before proceeding
if (!messageContextRef.current?.location) return;
// Mark this ask value as processed
setState((state) => ({ ...state, initialQuery: trimmedAsk }));
onPostMessage({ message: trimmedAsk });
}
}, [
searchState?.ask,
searchState?.query,
searchState?.open,
messageContextRef,
onOpen,
setState,
onPostMessage,
]);
return React.useMemo(() => {
return {
open: () => setState((state) => ({ ...state, opened: true })),
close: () => setState((state) => ({ ...state, opened: false })),
clear: () =>
setState((state) => ({
opened: state.opened,
loading: false,
messages: [],
query: null,
followUpSuggestions: [],
responseId: null,
error: false,
})),
postMessage: async (input: { message: string }) => {
trackEvent({ type: 'ask_question', query: input.message });
setState((state) => {
return {
...state,
messages: [
...state.messages,
{
// TODO: how to handle markdown here?
// to avoid rendering as plain text
role: AIMessageRole.User,
content: input.message,
},
{
role: AIMessageRole.Assistant,
content: null,
},
],
query: input.message,
followUpSuggestions: [],
loading: true,
error: false,
};
});
try {
const stream = await streamAIChatResponse({
message: input.message,
messageContext: messageContextRef.current,
previousResponseId: globalState.getState().state.responseId ?? undefined,
});
for await (const data of stream) {
if (!data) continue;
const event = data.event;
switch (event.type) {
case 'response_finish': {
setState((state) => ({
...state,
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,
error: false,
}));
break;
}
case 'response_followup_suggestion': {
setState((state) => ({
...state,
followUpSuggestions: [
...state.followUpSuggestions,
...event.suggestions,
],
}));
break;
}
}
setState((state) => ({
...state,
messages: [
...state.messages.slice(0, -1),
{
role: AIMessageRole.Assistant,
content: data.content,
},
],
}));
}
setState((state) => ({
...state,
loading: false,
error: false,
}));
} catch {
setState((state) => ({
...state,
loading: false,
error: true,
}));
}
},
open: onOpen,
close: onClose,
clear: onClear,
postMessage: onPostMessage,
};
}, [messageContextRef, setState, trackEvent]);
}, [onOpen, onClose, onClear, onPostMessage]);
}
@@ -93,6 +93,15 @@ export function AIChatWindow(props: {
block: 'start',
});
const timeout = setTimeout(() => {
if (lastUserMessageRef.current) {
lastUserMessageRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, 100);
// We want the chat messages to scroll underneath the input, but they should scroll past the input when scrolling all the way down.
// The best way to do this is to observe the input height and adjust the padding bottom of the scroll container accordingly.
const observer = new ResizeObserver((entries) => {
@@ -103,12 +112,16 @@ export function AIChatWindow(props: {
if (inputRef.current) {
observer.observe(inputRef.current);
}
return () => observer.disconnect();
}, [chat.opened]);
return () => {
observer.disconnect();
clearTimeout(timeout);
};
}, []);
return (
<div
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enterFromRight lg:pr-4 lg:pl-0 xl:w-96"
data-testid="ai-chat"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enter-from-right lg:pr-4 lg:pl-0 xl:w-96"
ref={containerRef}
>
<div className="relative flex h-full grow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint-base text-sm text-tint depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle">
@@ -150,7 +163,6 @@ export function AIChatWindow(props: {
iconOnly
icon="ellipsis"
label={tString(language, 'actions')}
className="!px-2"
variant="blank"
size="default"
/>
@@ -174,7 +186,6 @@ export function AIChatWindow(props: {
iconOnly
icon="close"
label={tString(language, 'close')}
className="!px-2"
variant="blank"
size="default"
/>
@@ -189,7 +200,7 @@ export function AIChatWindow(props: {
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex size-32 animate-fadeIn-slow items-center justify-center rounded-full bg-tint-subtle">
<div className="flex size-32 animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle">
<AIChatIcon
state="intro"
trademark={trademark}
@@ -218,7 +229,7 @@ export function AIChatWindow(props: {
</div>
<div
ref={inputRef}
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-gradient-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-linear-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
>
{/* Display an error banner when something went wrong. */}
{chat.error ? <AIChatError chatController={chatController} /> : null}
@@ -244,7 +255,7 @@ function AIChatError(props: { chatController: AIChatController }) {
const { chatController } = props;
return (
<div className="flex animate-scaleIn flex-wrap justify-between gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md bg-danger px-3 py-2 text-danger text-sm ring-1 ring-danger">
<div className="flex animate-scale-in flex-wrap justify-between gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md bg-danger px-3 py-2 text-danger text-sm ring-1 ring-danger">
<div className="flex items-center gap-2">
<Icon icon="exclamation-triangle" className="size-3.5" />
<span className="flex items-center gap-1">{t(language, 'ai_chat_error')}</span>
@@ -258,7 +269,7 @@ function AIChatError(props: { chatController: AIChatController }) {
onClick={() => {
chatController.clear();
}}
className="!text-danger hover:bg-danger-5"
className="text-danger! hover:bg-danger-5"
/>
</div>
</div>
@@ -20,6 +20,7 @@ export function AIChatButton(props: { trademark: boolean }) {
return (
<Button
icon={<AIChatIcon trademark={trademark} />}
data-testid="ai-chat-button"
iconOnly
size="default"
variant="header"
@@ -38,7 +38,7 @@ export function AIChatInput(props: {
// This fixes inconsistent focus behaviour across browsers
const timeout = setTimeout(() => {
inputRef.current?.focus();
}, 50);
}, 150);
return () => clearTimeout(timeout);
}
@@ -59,9 +59,10 @@ export function AIChatInput(props: {
ref={inputRef}
disabled={disabled || loading}
data-loading={loading}
data-testid="ai-chat-input"
className={tcls(
'resize-none',
'focus:outline-none',
'focus:outline-hidden',
'focus:ring-0',
'w-full',
'px-3',
@@ -126,7 +127,7 @@ export function AIChatInput(props: {
arrow
>
<div className="flex cursor-help items-center gap-1 circular-corners:rounded-2xl rounded-corners:rounded-md px-2 py-1 text-tint/7 text-xs transition-all hover:bg-tint">
<span className="-ml-1 rounded-corners:rounded circular-corners:rounded-2xl bg-tint-11/7 px-1 py-0.5 font-mono font-semibold text-[0.65rem] text-contrast-tint-11 leading-none">
<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>{' '}
<span>{t(language, 'ai_chat_context_title')}</span>
@@ -23,8 +23,9 @@ export function AIChatMessages(props: {
return (
<div
ref={isLastUserMessage ? lastUserMessageRef : undefined}
data-testid="ai-chat-message"
className={tcls(
message.content ? 'animate-fadeIn-slow' : '',
message.content ? 'animate-fade-in-slow' : '',
'shrink-0',
'last:min-h-[calc(100%-5rem)]',
'scroll-mt-36',
@@ -43,7 +44,7 @@ export function AIChatMessages(props: {
{message.content ? message.content : null}
{isLastMessage && chat.loading ? (
<div className="flex w-full animate-fadeIn-slow flex-wrap gap-2 group-has-[.has-content]/message:hidden">
<div className="flex w-full animate-fade-in-slow flex-wrap gap-2 group-has-[.has-content]/message:hidden">
{Array.from({ length: 7 }).map((_, index) => (
<div
key={index}
@@ -31,8 +31,8 @@ export function AIResponseFeedback(props: {
label={tString(language, 'was_this_helpful_positive_label')}
variant="blank"
className={tcls(
'animate-fadeIn overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== 1 && 'px-0 text-[0] opacity-0'
'animate-fade-in overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== 1 && 'px-0 text-[0rem] opacity-0'
)}
size="medium"
style={{ animationDuration: '.5s' }}
@@ -47,8 +47,8 @@ export function AIResponseFeedback(props: {
label={tString(language, 'was_this_helpful_negative_label')}
variant="blank"
className={tcls(
'animate-fadeIn overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== -1 && 'px-0 text-[0] opacity-0'
'animate-fade-in overflow-hidden text-tint-subtle transition-all',
rating !== null && rating !== -1 && 'px-0 text-[0rem] opacity-0'
)}
size="medium"
style={{ animationDelay: '.2s', animationDuration: '.5s' }}
@@ -59,7 +59,7 @@ export function AIResponseFeedback(props: {
/>
{rating !== null ? (
<span
className="ml-2 animate-fadeIn-slow text-tint-subtle"
className="ml-2 animate-fade-in-slow text-tint-subtle"
style={{ animationDelay: '.3s' }}
>
{t(language, 'was_this_helpful_thank_you')}
@@ -23,7 +23,7 @@ export function AIChatFollowupSuggestions(props: {
chatController.postMessage({ message: suggestion });
}}
label={suggestion}
className="!whitespace-normal max-w-full animate-[present_500ms_both] text-left ring-1 ring-tint-subtle"
className="whitespace-normal! max-w-full animate-[present_500ms_both] text-left ring-1 ring-tint-subtle"
size="medium"
variant="blank"
style={{
@@ -49,7 +49,7 @@ export async function AdCoverRendering({
'rounded-lg',
'p-4',
'overflow-hidden',
'shadow-sm'
'shadow-xs'
)}
style={{ backgroundColor: ad.backgroundColor, color: ad.textColor ?? '#ffffff' }}
href={ad.statlink}
@@ -68,7 +68,7 @@ export async function AdCoverRendering({
}}
/>
<div className={tcls('z-[2]')}>
<div className={tcls('z-2')}>
<img
alt="Large cover"
src={largeImage}
@@ -81,10 +81,10 @@ export async function AdCoverRendering({
)}
/>
</div>
<div className={tcls('z-[2]')}>
<div className={tcls('z-2')}>
<img alt={ad.company} src={ad.logo} className={tcls('max-w-36', 'max-h-12')} />
</div>
<div className={tcls('flex', 'flex-col', 'z-[2]')}>
<div className={tcls('flex', 'flex-col', 'z-2')}>
<div className={tcls('text-sm', 'font-semibold', 'mb-2')}>{ad.companyTagline}</div>
<div
className={tcls(
@@ -99,7 +99,7 @@ export async function AdCoverRendering({
{ad.description}
</div>
</div>
<div className={tcls('z-[2]')}>
<div className={tcls('z-2')}>
<span
className={tcls(
'text-sm',
@@ -119,7 +119,7 @@ export async function AdCoverRendering({
</span>
</div>
<div
className={tcls('absolute', 'inset-0', 'backdrop-blur', 'z-[1]')}
className={tcls('absolute', 'inset-0', 'backdrop-blur-sm', 'z-1')}
style={{
backgroundColor: hexToRgba(ad.backgroundColor, 0.8),
}}
@@ -7,7 +7,7 @@ import { tcls } from '@/lib/tailwind';
import { type CustomizationAnnouncement, SiteInsightsLinkPosition } from '@gitbook/api';
import { Icon, type IconName } from '@gitbook/icons';
import { CONTAINER_STYLE } from '../layout';
import { Link } from '../primitives';
import { Button, Link } from '../primitives';
import { LinkStyles } from '../primitives/styles';
import { ANNOUNCEMENT_CSS_CLASS, ANNOUNCEMENT_STORAGE_KEY } from './constants';
@@ -86,14 +86,15 @@ export function AnnouncementBanner(props: {
</div>
</Tag>
{closeable ? (
<button
className={`absolute top-0 right-4 mt-2 mr-2 rounded circular-corners:rounded-lg straight-corners:rounded-none p-1.5 transition-all hover:ring-1 sm:right-6 md:right-8 ${style.close}`}
type="button"
<Button
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
size="default"
onClick={dismissAnnouncement}
title={tString(language, 'close')}
>
<Icon icon="close" className="size-4" />
</button>
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>
@@ -130,7 +131,7 @@ const BANNER_STYLES = {
icon: 'circle-exclamation',
iconColor: 'text-warning-subtle',
close: 'hover:bg-tint-base hover:ring-warning-subtle',
link: 'links-default:text-warning links-default:hover:text-warning-strong links-default:decoration-warning/6 links-accent:decoration-warning',
link: 'links-default:text-warning hover:links-default:text-warning-strong links-default:decoration-warning/6 links-accent:decoration-warning',
},
danger: {
container: 'bg-danger decoration-danger/6 ring-danger-subtle',
@@ -138,7 +139,7 @@ const BANNER_STYLES = {
icon: 'triangle-exclamation',
iconColor: 'text-danger-subtle',
close: 'hover:bg-tint-base hover:ring-danger-subtle',
link: 'links-default:text-danger links-default:hover:text-danger-strong links-default:decoration-danger/6 links-accent:decoration-danger',
link: 'links-default:text-danger hover:links-default:text-danger-strong links-default:decoration-danger/6 links-accent:decoration-danger',
},
success: {
container: 'bg-success decoration-success/6 ring-success-subtle',
@@ -146,6 +147,6 @@ const BANNER_STYLES = {
icon: 'circle-check',
iconColor: 'text-success-subtle',
close: 'hover:bg-tint-base hover:ring-success-subtle',
link: 'links-default:text-success links-default:hover:text-success-strong links-default:decoration-success/6 links-accent:decoration-success',
link: 'links-default:text-success hover:links-default:text-success-strong links-default:decoration-success/6 links-accent:decoration-success',
},
};
@@ -1,6 +1,4 @@
'use client';
import { Icon } from '@gitbook/icons';
import * as React from 'react';
import { Button, StyledLink } from '@/components/primitives';
@@ -45,7 +43,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'fixed',
'z-10',
'bg-tint-base',
'rounded',
'rounded-sm',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'ring-1',
@@ -61,7 +59,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'text-balance',
'sm:left-auto',
'lg:chat-open:mr-80',
'xl:chat-open:mr-[25rem]',
'xl:chat-open:mr-100',
'transition-all',
'duration-300'
)}
@@ -75,27 +73,14 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
</StyledLink>
)}
</p>
<button
type="button"
<Button
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
onClick={() => setShow(false)}
aria-label={tString(language, 'close')}
className={tcls(
'absolute',
'top-3',
'right-3',
'w-6',
'h-6',
'flex',
'justify-center',
'items-center',
'rounded-sm',
'circular-corners:rounded-full',
'hover:bg-tint-hover'
)}
title={tString(language, 'close')}
>
<Icon icon="xmark" className={tcls('size-4')} />
</button>
className={tcls('absolute', 'top-2', 'right-2', 'hover:bg-tint-hover')}
/>
<div className={tcls('mt-3', 'flex', 'flex-row', 'gap-2')}>
<Button
variant="primary"
@@ -34,7 +34,7 @@ export function AnnotationPopover(props: { children: React.ReactNode; body: Reac
'bg-tint',
'ring-1',
'ring-tint',
'rounded',
'rounded-sm',
'shadow-1xs',
'shadow-tint-12/1',
'dark:shadow-tint-1/2',
@@ -53,7 +53,7 @@ export function AnnotationPopover(props: { children: React.ReactNode; body: Reac
viewBox="0 0 8 5"
className={tcls(
'relative',
'z-[2]',
'z-2',
'fill-tint-3', // Same as bg-tint
'stroke-tint-7', // Same as ring-tint
'[paint-order:stroke_fill]'
@@ -85,9 +85,9 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
key={node.key || `${node.type}-${index}`}
block={node}
style={[
'mx-auto page-width-wide:mx-0 w-full decoration-primary/6',
'mx-auto page-width-wide:mx-0 w-full decoration-primary/6 print:break-inside-avoid',
node.data && 'fullWidth' in node.data && node.data.fullWidth
? 'max-w-screen-2xl'
? 'max-w-screen-xl'
: 'max-w-3xl',
FULL_WIDTH_BLOCKS.includes(node.type) && 'page-width-wide:max-w-screen-2xl',
blockStyle,
@@ -38,11 +38,11 @@ export function Caption(
'overflow-hidden',
'after:block',
'after:absolute',
'after:-inset-[0]',
'after:-inset-0',
'after:pointer-events-none',
fit ? 'w-fit' : null,
withBorder
? 'rounded circular-corners:rounded-2xl straight-corners:rounded-none after:border-tint-subtle after:border after:rounded circular-corners:after:rounded-2xl straight-corners:after:rounded-none dark:after:mix-blend-plus-lighter after:pointer-events-none'
? 'rounded-corners:rounded-sm circular-corners:rounded-2xl after:border-tint-subtle after:border after:rounded circular-corners:after:rounded-2xl rounded-corners:after:rounded-sm dark:after:mix-blend-plus-lighter after:pointer-events-none'
: null,
],
style,
@@ -43,12 +43,12 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
</div>
<CopyCodeButton
codeId={id}
style="z-[2] mt-2 mr-2 self-start justify-self-end rounded-md bg-transparent p-1 text-tint text-xs leading-none opacity-0 ring-1 ring-tint backdrop-blur-md transition-opacity duration-75 [grid-area:2/1] hover:ring-tint-hover group-hover/codeblock:opacity-[1]"
style="z-2 mt-2 mr-2 self-start justify-self-end rounded-md bg-transparent p-1 text-tint text-xs leading-none opacity-0 ring-1 ring-tint backdrop-blur-md transition-opacity duration-75 [grid-area:2/1] hover:ring-tint-hover group-hover/codeblock:opacity-11"
/>
<pre
className={tcls(
'relative overflow-auto border border-tint-subtle bg-tint-subtle theme-muted:bg-tint-base p-2 [grid-area:2/1] contrast-more:border-tint contrast-more:bg-tint-base [html.theme-bold.sidebar-filled_&]:bg-tint-base',
'rounded-md straight-corners:rounded-sm shadow-sm',
'rounded-md straight-corners:rounded-xs shadow-xs',
title && 'rounded-ss-none'
)}
>
@@ -34,6 +34,8 @@ export type RenderedInline = {
body: React.ReactNode;
};
const isSafari =
typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const theme = createCssVariablesTheme();
const { getSingletonHighlighter } = createSingletonShorthands(
@@ -65,8 +67,13 @@ export async function highlight(
inlines: RenderedInline[]
): Promise<HighlightLine[]> {
const langName = getBlockLang(block);
if (!langName) {
// Language not found, fallback to plain highlighting
if (!langName || (isSafari && ['powershell', 'cpp'].includes(langName))) {
// Fallback to plain highlighting if
// - language is not found
// - TEMP : language is PowerShell or C++ and browser is Safari:
// RegExp#[Symbol.search] throws TypeError when `lastIndex` isnt writable
// Fixed in upcoming Safari 18.6, remove when it'll be released - RND-7772
return plainHighlight(block, inlines);
}
@@ -51,7 +51,7 @@ function transformLengthToCSS(length: Length | undefined) {
if (length.unit === '%') {
return {
className: [
'md:flex-shrink-0',
'md:shrink-0',
COLUMN_WIDTHS[Math.round(length.value * 0.01 * (COLUMN_WIDTHS.length - 1))],
],
};
@@ -50,12 +50,12 @@ export function Details(props: {
'border-b-0',
'border-tint-subtle',
//all
'[&]:mt-[0px]',
'mt-0!',
//select first child
'[&:first-child]:mt-5',
'[&:first-child]:rounded-t-lg',
'first:mt-5!',
'first:rounded-t-lg',
//select first in group
'[:not(&)_+&]:mt-5',
'[:not(&)_+&]:mt-5!',
'[:not(&)_+&]:rounded-t-lg',
//select last in group
'[&:not(:has(+_&))]:mb-5',
@@ -30,8 +30,8 @@ export function HashLinkButton(props: {
'opacity-0',
'group-hover/hash:opacity-[0]',
'group-focus/hash:opacity-[0]',
'md:group-hover/hash:md:opacity-[1]',
'md:group-focus/hash:md:opacity-[1]',
'md:group-hover/hash:opacity-[1]',
'md:group-focus/hash:opacity-[1]',
className
)}
>
@@ -26,7 +26,7 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
'heading',
'flex',
'items-baseline',
'scroll-m-12',
'scroll-mt-(--content-scroll-margin)',
getTextAlignment(block.data.align),
hashLinkButtonWrapperStyles,
style,
@@ -44,7 +44,7 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
<div
className={tcls(
'flex-1',
'z-[1]',
'z-1',
'justify-self-start',
'max-w-full',
'break-words',
@@ -21,7 +21,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
'hint',
'transition-colors',
'rounded-md',
hasHeading ? 'rounded-l' : null,
hasHeading ? 'rounded-l-sm' : null,
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'overflow-hidden',
@@ -52,7 +52,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
{hasHeading ? (
<Block
style={tcls(
'!py-4 w-full items-start pl-3 text-[1em] *:flex-none',
'w-full items-start py-4! pl-3 text-[1em] *:flex-none',
// Heading hash styles
'flip-heading-hash pr-8',
hintStyle.header
@@ -19,7 +19,7 @@ export async function InlineButton(props: InlineProps<api.DocumentInlineButton>)
return (
// Set the leading to have some vertical space between adjacent buttons
<span className="inline-button leading-[3rem] [&:has(+.inline-button)]:mr-2">
<span className="inline-button leading-12 [&:has(+.inline-button)]:mr-2">
<Button
href={resolved.href}
label={inline.data.label}
@@ -51,7 +51,7 @@ export async function InlineImage(props: InlineProps<DocumentInlineImage>) {
}}
priority="lazy"
preload
style={[size === 'line' ? ['max-h-[1lh]', 'h-[1lh]', 'w-auto'] : null]}
style={[size === 'line' ? ['max-h-lh', 'h-lh', 'w-auto'] : null]}
inline
zoom={!isInLink}
/>
@@ -1,11 +1,10 @@
import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
import { getSpaceLanguage, tString } from '@/intl/server';
import { languages } from '@/intl/translations';
import type { GitBookAnyContext } from '@/lib/context';
import { type TranslationLanguage, languages } from '@/intl/translations';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
import { Icon } from '@gitbook/icons';
import { StyledLink } from '../../primitives';
import { HoverCard, HoverCardRoot, HoverCardTrigger, StyledLink } from '../../primitives';
import type { InlineProps } from '../Inline';
import { Inlines } from '../Inlines';
import { InlineLinkTooltip } from './InlineLinkTooltip';
@@ -19,17 +18,34 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
resolveAnchorText: false,
})
: null;
const { contentContext } = context;
if (!context.contentContext || !resolved) {
const language =
contentContext && 'customization' in contentContext
? getSpaceLanguage(contentContext.customization)
: languages.en;
if (!contentContext || !resolved) {
return (
<span title="Broken link" className="underline">
<Inlines
context={context}
document={document}
nodes={inline.nodes}
ancestorInlines={[...ancestorInlines, inline]}
/>
</span>
<HoverCardRoot>
<HoverCardTrigger>
<span className="cursor-not-allowed underline">
<Inlines
context={context}
document={document}
nodes={inline.nodes}
ancestorInlines={[...ancestorInlines, inline]}
/>
</span>
</HoverCardTrigger>
<HoverCard className="flex flex-col gap-1 p-4">
<div className="flex items-center gap-2">
<Icon icon="ban" className="size-4 text-tint-subtle" />
<h5 className="font-semibold">{tString(language, 'notfound_title')}</h5>
</div>
<p className="text-sm text-tint">{tString(language, 'notfound_link')}</p>
</HoverCard>
</HoverCardRoot>
);
}
const isExternal = inline.data.ref.kind === 'url';
@@ -61,11 +77,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
if (context.shouldRenderLinkPreviews) {
return (
<InlineLinkTooltipWrapper
inline={inline}
context={context.contentContext}
resolved={resolved}
>
<InlineLinkTooltipWrapper inline={inline} language={language} resolved={resolved}>
{content}
</InlineLinkTooltipWrapper>
);
@@ -80,15 +92,13 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
*/
function InlineLinkTooltipWrapper(props: {
inline: DocumentInlineLink;
context: GitBookAnyContext;
children: React.ReactNode;
resolved: ResolvedContentRef;
language: TranslationLanguage;
}) {
const { inline, context, resolved, children } = props;
const { inline, language, resolved, children } = props;
let breadcrumbs = resolved.ancestors ?? [];
const language =
'customization' in context ? getSpaceLanguage(context.customization) : languages.en;
const isExternal = inline.data.ref.kind === 'url';
const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined;
if (isExternal) {
@@ -1,9 +1,8 @@
'use client';
import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import * as Tooltip from '@radix-ui/react-tooltip';
import { Fragment } from 'react';
import { Button, StyledLink } from '../../primitives';
import { Button, HoverCard, HoverCardRoot, HoverCardTrigger, StyledLink } from '../../primitives';
export function InlineLinkTooltipImpl(props: {
isSamePage: boolean;
@@ -21,89 +20,76 @@ export function InlineLinkTooltipImpl(props: {
const { isSamePage, isExternal, openInNewTabLabel, target, breadcrumbs, children } = props;
return (
<Tooltip.Provider delayDuration={200}>
<Tooltip.Root>
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto">
<div className="overflow-hidden rounded-md straight-corners:rounded-none shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1 ">
<div className="bg-tint-base p-4">
<div className="flex items-start gap-4">
<div className="flex flex-col">
{breadcrumbs && breadcrumbs.length > 0 ? (
<div className="mb-1 flex grow flex-wrap items-center gap-x-2 gap-y-0.5 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
{breadcrumbs.map((crumb, index) => {
const Tag = crumb.href ? StyledLink : 'div';
<HoverCardRoot>
<HoverCardTrigger>{children}</HoverCardTrigger>
<HoverCard className="p-4">
<div className="flex items-start gap-4">
<div className="flex flex-col">
{breadcrumbs && breadcrumbs.length > 0 ? (
<div className="mb-1 flex grow flex-wrap items-center gap-x-2 gap-y-0.5 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
{breadcrumbs.map((crumb, index) => {
const Tag = crumb.href ? StyledLink : 'div';
return (
<Fragment key={crumb.label}>
{index !== 0 ? (
<Icon
icon="chevron-right"
className="size-3 text-tint-subtle"
/>
) : null}
<Tag
className={tcls(
'flex gap-1',
crumb.href &&
'links-default:text-tint no-underline hover:underline contrast-more:underline contrast-more:decoration-current'
)}
href={crumb.href ?? '#'}
>
{crumb.icon ? (
<span className="mt-0.5 text-tint-subtle empty:hidden">
{crumb.icon}
</span>
) : null}
{crumb.label}
</Tag>
</Fragment>
);
})}
</div>
) : null}
<div
className={tcls(
'flex gap-2 leading-snug',
isExternal && 'text-sm [overflow-wrap:anywhere]'
)}
>
{target.icon ? (
<div className="mt-1 text-tint-subtle empty:hidden">
{target.icon}
</div>
return (
<Fragment key={crumb.label}>
{index !== 0 ? (
<Icon
icon="chevron-right"
className="size-3 text-tint-subtle"
/>
) : null}
<h5 className="font-semibold">{target.text}</h5>
</div>
</div>
{!isSamePage && target.href ? (
<Button
className={tcls(
'-mx-2 -my-2 ml-auto',
breadcrumbs?.length === 0
? 'place-self-center'
: null
)}
variant="blank"
href={target.href}
target="_blank"
label={openInNewTabLabel}
size="small"
icon="arrow-up-right-from-square"
iconOnly={true}
/>
) : null}
</div>
{target.subText ? (
<p className="mt-1 text-sm text-tint">{target.subText}</p>
) : null}
<Tag
className={tcls(
'flex gap-1',
crumb.href &&
'links-default:text-tint no-underline hover:underline contrast-more:underline contrast-more:decoration-current'
)}
href={crumb.href ?? '#'}
>
{crumb.icon ? (
<span className="mt-0.5 text-tint-subtle empty:hidden">
{crumb.icon}
</span>
) : null}
{crumb.label}
</Tag>
</Fragment>
);
})}
</div>
) : null}
<div
className={tcls(
'flex gap-2 leading-snug',
isExternal && 'wrap-anywhere text-sm'
)}
>
{target.icon ? (
<div className="mt-1 text-tint-subtle empty:hidden">
{target.icon}
</div>
) : null}
<h5 className="font-semibold">{target.text}</h5>
</div>
<Tooltip.Arrow className="fill-tint-1" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
</div>
{!isSamePage && target.href ? (
<Button
className={tcls(
'-mx-2 -my-2 ml-auto',
breadcrumbs?.length === 0 ? 'place-self-center' : null
)}
variant="blank"
href={target.href}
target="_blank"
label={openInNewTabLabel}
size="small"
icon="arrow-up-right-from-square"
iconOnly={true}
/>
) : null}
</div>
{target.subText ? <p className="mt-1 text-sm text-tint">{target.subText}</p> : null}
</HoverCard>
</HoverCardRoot>
);
}
@@ -1,3 +1,5 @@
@reference '../../RootLayout/globals.css';
/** Stacks */
.contentkit-stack {
@apply flex gap-4;
@@ -114,7 +116,7 @@
/** Modals */
.contentkit-modal-backdrop {
@apply fixed inset-0 z-40 px-7 pb-11 pt-4 md:pt-[min(8vw,_6rem)] flex items-start justify-center backdrop-blur-2xl bg-tint-12/4 dark:bg-tint-1/6;
@apply fixed inset-0 z-40 px-7 pb-11 pt-4 md:pt-[min(8vw,6rem)] flex items-start justify-center backdrop-blur-2xl bg-tint-12/4 dark:bg-tint-1/6;
}
.contentkit-modal {
@apply flex flex-col gap-6 relative z-50 bg-tint
@@ -31,13 +31,13 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
nodes={block.nodes}
ancestorBlocks={[...ancestorBlocks, block]}
blockStyle={tcls(
'min-h-[1lh]',
'min-h-lh',
// flip heading hash icon if list item is a heading
'flip-heading-hash',
// remove margin-top for the first heading in a list
'[&:is(h2)]:pt-0',
'[&:is(h3)]:pt-0',
'[&:is(h4)]:pt-0',
'[h2]:pt-0',
'[h3]:pt-0',
'[h4]:pt-0',
// Override the "mx-auto" class from UnwrappedBlocks
'mx-0'
)}
@@ -137,7 +137,7 @@ function ListItemPrefix(props: { block: DocumentBlockListItem; children: React.R
className={tcls(
textStyle.textSize,
textStyle.lineHeight,
'mr-1 flex min-h-[1lh] min-w-6 items-center justify-center text-tint'
'mr-1 flex min-h-lh min-w-6 items-center justify-center text-tint'
)}
>
{props.children}
@@ -165,7 +165,7 @@ function PseudoBefore(props: {
}) {
return (
<div
className="before:font-var before:content-[--pseudoBefore--content]"
className="before:font-var before:content-(--pseudoBefore--content)"
style={
{
'--pseudoBefore--content': `'${props.content}'`,
@@ -1,4 +1,5 @@
@import "@scalar/api-client-react/style.css";
@reference "../../RootLayout/globals.css";
html,
body {
@@ -1,3 +1,5 @@
@reference "../../RootLayout/globals.css";
/* Layout Components */
.openapi-operation,
.openapi-schemas,
@@ -32,7 +34,7 @@
.openapi-deprecated,
.openapi-stability {
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none;
}
.openapi-stability-alpha {
@@ -70,7 +72,7 @@
}
.openapi-markdown code {
@apply py-px px-1 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
@apply py-px px-1 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none;
}
.openapi-markdown pre code {
@@ -79,7 +81,7 @@
/* Markdown Base */
.openapi-markdown {
@apply prose text-tint-strong prose-p:!my-2 dark:prose-invert max-w-none whitespace-normal;
@apply prose text-tint-strong prose-p:my-2! dark:prose-invert max-w-none whitespace-normal;
}
.openapi-markdown > *:first-child {
@@ -158,7 +160,7 @@
}
.openapi-schema-root-description.openapi-markdown {
@apply prose-sm text-balance mt-1.5 !text-[0.813rem] text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
@apply prose-sm text-balance mt-1.5 text-[0.813rem]! text-tint overflow-hidden font-normal! select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description {
@@ -273,7 +275,7 @@
/* Schema Description */
.openapi-schema-description.openapi-markdown {
@apply prose-sm text-tint overflow-hidden text-pretty !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
@apply prose-sm text-tint overflow-hidden text-pretty font-normal! select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-schema-description.openapi-markdown pre:has(code) {
@@ -302,7 +304,7 @@
.openapi-schema-pattern code,
.openapi-schema-enum-value code,
.openapi-schema-default code {
@apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
@apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none;
}
/* Authentication */
@@ -335,7 +337,7 @@
}
.openapi-securities-description.openapi-markdown {
@apply prose-sm text-tint !font-normal select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
@apply prose-sm text-tint font-normal! select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-securities-label {
@@ -366,7 +368,7 @@
}
.openapi-requestbody-description.openapi-markdown {
@apply prose-sm text-tint !font-normal text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
@apply prose-sm text-tint font-normal! text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
}
/* Responses */
@@ -379,21 +381,23 @@
}
.openapi-response-tab-content {
@apply flex items-baseline truncate grow shrink max-w-max basis-[60%] mr-auto;
@apply flex items-baseline truncate grow shrink max-w-max basis-full mr-auto;
@apply text-left text-pretty relative leading-tight text-tint select-text;
}
.openapi-response-description.openapi-markdown {
@apply text-left truncate prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
@apply text-left prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-tab-content {
@apply basis-full;
}
.openapi-disclosure-group-trigger[aria-expanded="true"]
.openapi-disclosure-group-trigger[aria-expanded="false"] {
.openapi-response-description.openapi-markdown {
@apply whitespace-normal;
@apply truncate;
@apply [&>*:not(:first-child)]:hidden *:truncate *:!p-0 *:!m-0;
}
.openapi-response-tab-content {
@apply basis-[60%]
}
}
.openapi-response-body {
@@ -465,7 +469,7 @@
}
.openapi-path-variable {
@apply p-px min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-none before:!content-none after:!content-none;
@apply p-px min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-none before:content-none! after:!content-none;
}
.openapi-path-server {
@@ -551,6 +555,10 @@
@apply text-[0.813rem] truncate text-tint font-normal;
}
.openapi-response-examples-statuscode-label {
@apply truncate;
}
.openapi-response-examples-panel,
.openapi-codesample-panel {
@apply flex-1 text-sm relative focus-visible:outline-none;
@@ -583,10 +591,6 @@ body:has(.openapi-select-popover) {
@apply shrink truncate flex items-center;
}
.openapi-select > button > span.react-aria-SelectValue span:not(.openapi-statuscode) {
@apply truncate;
}
.openapi-select > button .openapi-markdown {
@apply *:leading-none;
}
@@ -610,18 +614,18 @@ body:has(.openapi-select-popover) {
.openapi-select-item {
@apply text-sm flex items-center cursor-pointer px-1.5 overflow-hidden py-1 *:truncate text-tint ring-0 border-none rounded !outline-none;
@apply hover:bg-tint-hover theme-gradient:hover:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
@apply hover:bg-tint-hover hover:theme-gradient:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
}
.openapi-select button .openapi-markdown,
.openapi-select-item .openapi-markdown {
@apply text-[0.813rem] *:truncate;
@apply text-[0.813rem] *:truncate *:!p-0 *:!m-0 [&>*:not(:first-child)]:hidden;
}
.openapi-select-item-selected,
.openapi-select-item-selected .openapi-markdown {
@apply text-primary-subtle hover:text-primary hover:bg-primary-hover;
@apply theme-muted:hover:bg-primary-active theme-gradient:hover:bg-primary-active tint:font-semibold;
@apply hover:theme-muted:bg-primary-active hover:theme-gradient:bg-primary-active tint:font-semibold;
@apply contrast-more:text-primary contrast-more:hover:text-primary-strong contrast-more:font-semibold;
}
@@ -693,7 +697,7 @@ body:has(.openapi-select-popover) {
}
.openapi-tabs-tab[aria-selected="true"] {
@apply !text-primary-subtle after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
@apply text-primary-subtle! after:absolute after:-bottom-[calc(0.375rem+1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
}
.openapi-tabs-panel {
@@ -769,7 +773,7 @@ body:has(.openapi-select-popover) {
}
.openapi-schema .openapi-schema-properties .openapi-schema {
@apply animate-fadeIn [animation-fill-mode:both];
animation: fadeIn 0.2s ease-in-out both;
}
.openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg {
@@ -830,7 +834,8 @@ body:has(.openapi-select-popover) {
.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label span,
.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label span {
@apply block animate-fadeIn;
@apply block;
animation: fadeIn 0.2s ease-in-out both;
}
@media (hover: none) {
@@ -26,7 +26,11 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
}
const { reusableContent } = resolved;
if (!reusableContent || !reusableContent.revisionReusableContent.document) {
if (
!reusableContent ||
!('document' in reusableContent.revisionReusableContent) ||
!reusableContent.revisionReusableContent.document
) {
return null;
}
@@ -24,6 +24,8 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
return '-mt-9';
case 'heading-2':
return '-mt-[calc(1.25rem+1px)]';
case 'heading-3':
return '-mt-[calc(0.50rem+1px)]';
default:
return '';
}
@@ -40,7 +42,7 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
>
{index + 1}
</div>
<div className="can-override-bg absolute top-9 bottom-2 left-[0.875rem] w-px bg-primary-7 theme-muted:bg-primary-subtle" />
<div className="can-override-bg absolute top-9 bottom-2 left-3.5 w-px bg-primary-7 theme-muted:bg-primary-subtle" />
</div>
<Blocks
{...contextProps}
@@ -50,8 +50,8 @@ export async function RecordCard(
'bg-tint-base',
'w-[calc(100%+2px)]',
'h-[calc(100%+2px)]',
'inset-[-1px]',
'rounded',
'-inset-px',
'rounded-sm',
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'overflow-hidden',
@@ -65,11 +65,11 @@ export async function RecordCard(
// - If the file is square or portrait, we display it left with 40% of the card width
coverIsSquareOrPortrait
? [
'grid-cols-[40%,_1fr]',
'grid-cols-[40%__1fr]',
'min-[432px]:grid-cols-none',
'min-[432px]:grid-rows-[auto,1fr]',
'min-[432px]:grid-rows-[auto_1fr]',
]
: 'grid-rows-[auto,1fr]'
: 'grid-rows-[auto_1fr]'
)}
>
{cover ? (
@@ -23,9 +23,9 @@ import type { TableRecordKV } from './Table';
import { type VerticalAlignment, getColumnAlignment } from './utils';
const alignmentMap: Record<'text-left' | 'text-center' | 'text-right', string> = {
'text-left': '[&_*]:text-left text-left',
'text-center': '[&_*]:text-center text-center',
'text-right': '[&_*]:text-right text-right',
'text-left': '**:text-left text-left',
'text-center': '**:text-center text-center',
'text-right': '**:text-right text-right',
};
/**
@@ -193,7 +193,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
>
{contentType === 'image' ? (
<Image
style={['max-h-[1lh]', 'h-[1lh]']}
style={['max-h-lh', 'h-lh']}
alt={ref.text}
sizes={[{ width: 24 }]}
resize={context.contentContext?.imageResizer}
@@ -314,7 +314,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
className={tcls(
'text-sm',
'whitespace-pre',
'rounded',
'rounded-sm',
'py-1',
'px-2',
'bg-primary',
@@ -6,7 +6,7 @@ export const RecordCardStyles = [
'shadow-1xs',
'shadow-tint-9/1',
'depth-flat:shadow-none',
'rounded',
'rounded-sm',
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'dark:shadow-transparent',
@@ -1,3 +1,5 @@
@reference "../../RootLayout/globals.css";
/* Detect whether a scrollbar exists on the table */
@keyframes detect-scroll {
from,
@@ -144,7 +144,7 @@ export function DynamicTabs(
<div
className={tcls(
'rounded-lg',
'straight-corners:rounded-sm',
'straight-corners:rounded-xs',
'ring-1',
'ring-inset',
'ring-tint-subtle',
@@ -161,7 +161,7 @@ export function DynamicTabs(
'inline-flex',
'flex-row',
'self-stretch',
'after:flex-[1]',
'after:flex-1',
'after:bg-tint-12/1',
// if last tab is selected, apply rounded to :after element
'[&:has(button.active-tab:last-of-type):after]:rounded-bl-md'
@@ -180,7 +180,7 @@ export function DynamicTabs(
'[&:has(+_.active-tab)]:rounded-br-md',
//next from active-tab
'[.active-tab_+_&]:rounded-bl-md',
'[.active-tab+&]:rounded-bl-md',
//next from active-tab
'[.active-tab_+_:after]:rounded-br-md',
@@ -193,7 +193,7 @@ export function DynamicTabs(
'after:border-tint',
'after:top-[15%]',
'after:h-[70%]',
'after:w-[1px]',
'after:w-px',
'px-3.5',
'py-2',
@@ -212,9 +212,9 @@ export function DynamicTabs(
'active-tab',
'text-tint-strong',
'bg-transparent',
'after:[&.active-tab]:border-transparent',
'after:[:has(+_&.active-tab)]:border-transparent',
'after:[:has(&_+)]:border-transparent',
'[&.active-tab]:after:border-transparent',
'[:has(+_&.active-tab)]:after:border-transparent',
'[:has(&_+)]:after:border-transparent',
]
: null
)}
@@ -232,7 +232,7 @@ export function DynamicTabs(
'inline-block',
'text-sm',
'transition-[color]',
'font-[500]',
'font-medium',
'relative',
'max-w-full',
'truncate'
@@ -71,11 +71,7 @@ function Strikethrough(props: MarkedLeafProps<DocumentMarkStrikethrough>) {
}
function Keyboard(props: MarkedLeafProps<DocumentMarkKeyboard>) {
return (
<kbd className="rounded border px-1 font-mono shadow-[0_1px_0_0_theme(borderColor.DEFAULT)]">
{props.children}
</kbd>
);
return <kbd className="rounded-xs border border-b-2 px-1 font-mono">{props.children}</kbd>;
}
function Superscript(props: MarkedLeafProps<DocumentMarkSuperscript>) {
@@ -90,9 +86,9 @@ function Code(props: MarkedLeafProps<DocumentMarkCode>) {
return (
<code
className={tcls(
'py-[1px]',
'py-px',
'px-1.5',
'min-w-[1.625rem]',
'min-w-6.5',
'justify-center',
'items-center',
'leading-normal',
@@ -100,7 +96,7 @@ function Code(props: MarkedLeafProps<DocumentMarkCode>) {
'ring-inset',
'ring-tint',
'bg-tint',
'rounded',
'rounded-sm',
// Text size is proportional to the font-size of the parent element
'text-[.875em]',
// We ensure that the code is not making the parent bigger, especially in headings
@@ -36,16 +36,16 @@ export function Footer(props: { context: GitBookSiteContext }) {
<div className={tcls(CONTAINER_STYLE, 'px-4', 'py-8', 'lg:py-12', 'mx-auto')}>
<div
className={tcls(
'lg:!max-w-none mx-auto grid max-w-3xl site-width-wide:max-w-screen-2xl justify-between gap-12',
'mx-auto grid max-w-3xl site-width-wide:max-w-screen-2xl justify-between gap-12 lg:max-w-none!',
'grid-cols-[auto_auto]',
'lg:grid-cols-[18rem_minmax(auto,_48rem)_auto]',
'xl:grid-cols-[18rem_minmax(auto,_48rem)_14rem]',
'site-width-wide:lg:grid-cols-[18rem_minmax(auto,_80rem)_auto]',
'site-width-wide:xl:grid-cols-[18rem_minmax(auto,_80rem)_14rem]',
'page-no-toc:lg:grid-cols-[minmax(auto,_48rem)_auto]',
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]',
'[body:has(.site-width-wide,.page-no-toc)_&]:lg:grid-cols-[minmax(auto,_90rem)_auto]',
'[body:has(.site-width-wide,.page-no-toc)_&]:xl:grid-cols-[14rem_minmax(auto,_90rem)_14rem]'
'lg:grid-cols-[18rem_minmax(auto,48rem)_auto]',
'xl:grid-cols-[18rem_minmax(auto,48rem)_14rem]',
'lg:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_auto]',
'xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_14rem]',
'lg:page-no-toc:grid-cols-[minmax(auto,48rem)_auto]',
'xl:page-no-toc:grid-cols-[14rem_minmax(auto,48rem)_14rem]',
'lg:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[minmax(auto,90rem)_auto]',
'xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[14rem_minmax(auto,90rem)_14rem]'
)}
>
{
@@ -74,8 +74,8 @@ export function Footer(props: { context: GitBookSiteContext }) {
'lg:max-h-12',
'object-contain',
'object-left',
'rounded',
'straight-corners:rounded-sm',
'rounded-sm',
'straight-corners:rounded-xs',
]}
sizes={[
{
@@ -103,7 +103,7 @@ export function Footer(props: { context: GitBookSiteContext }) {
customization.footer.groups?.length > 0 ? (
<div
className={tcls(
'col-span-2 page-has-toc:lg:col-span-1 page-has-toc:lg:col-start-2 page-no-toc:xl:col-span-1 page-no-toc:xl:col-start-2'
'col-span-2 lg:page-has-toc:col-span-1 lg:page-has-toc:col-start-2 xl:page-no-toc:col-span-1 xl:page-no-toc:col-start-2'
)}
>
<div className="mx-auto flex max-w-3xl site-width-wide:max-w-screen-2xl flex-col gap-10 sm:flex-row sm:gap-6">
@@ -85,7 +85,7 @@ export function Header(props: {
'text-tint-strong',
'theme-bold:text-header-link',
'hover:bg-tint-hover',
'theme-bold:hover:bg-header-link/3',
'hover:theme-bold:bg-header-link/3',
'page-no-toc:hidden'
)}
/>
@@ -110,7 +110,7 @@ export function Header(props: {
'lg:max-w-lg',
'lg:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem)
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'page-no-toc:lg:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
'lg:page-no-toc:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
'md:mr-auto',
'order-last',
'md:order-[unset]',
@@ -119,7 +119,7 @@ function HeaderItemButton(
className={tcls(
{
'button-primary':
'theme-bold:bg-header-link theme-bold:text-header-background theme-bold:shadow-none theme-bold:hover:bg-header-link theme-bold:hover:text-header-background theme-bold:hover:shadow-none',
'theme-bold:bg-header-link theme-bold:text-header-background theme-bold:shadow-none hover:theme-bold:bg-header-link hover:theme-bold:text-header-background hover:theme-bold:shadow-none',
'button-secondary': '',
}[linkStyle]
)}
@@ -157,7 +157,7 @@ function getHeaderLinkClassName(_props: { headerPreset: CustomizationHeaderPrese
'links-accent:py-0.5', // Prevent underline from being cut off at the bottom
'theme-bold:text-header-link',
'theme-bold:hover:!text-header-link/7'
'hover:theme-bold:text-header-link/7!'
);
}
@@ -53,7 +53,7 @@ export async function HeaderLogo(props: HeaderLogoProps) {
'min-w-0',
'max-w-40',
'lg:max-w-64',
'site-header-none:page-no-toc:lg:max-w-56',
'lg:site-header-none:page-no-toc:max-w-56',
'max-h-10',
'lg:max-h-12',
'h-full',
@@ -47,7 +47,7 @@ export function HeaderMobileMenu(props: Partial<React.ButtonHTMLAttributes<HTMLB
aria-label={tString(language, 'table_of_contents_button_label')}
onClick={toggleNavigation}
className={tcls(
'flex flex-row items-center rounded straight-corners:rounded-sm px-2 py-1',
'flex flex-row items-center rounded-sm straight-corners:rounded-xs px-2 py-1',
props.className
)}
>
@@ -107,7 +107,7 @@ export async function PDFPage(props: {
'p-4',
'rounded-full',
'bg-white',
'shadow-sm',
'shadow-xs',
'hover:shadow-md',
'border-slate-300',
'border'
@@ -132,7 +132,7 @@ export async function PDFPage(props: {
'p-4',
'rounded-full',
'bg-white',
'shadow-sm',
'shadow-xs',
'hover:shadow-md',
'border-slate-300',
'border'
@@ -280,7 +280,7 @@ function PrintPage(
'print:p-0',
'shadow-xl',
'print:shadow-none',
'rounded-sm',
'rounded-xs',
'bg-white',
'min-h-[29.7cm]',
'print:min-h-0',
@@ -99,7 +99,7 @@ export function PageControlButtons(props: {
'bg-yellow-100',
'border-yellow-400',
'text-yellow-800',
'shadow-sm',
'shadow-xs',
'border',
'rounded-md',
'p-4',
@@ -140,7 +140,7 @@ export function PageControlButtons(props: {
'py-2',
'bg-slate-100',
'rounded-full',
'shadow-sm',
'shadow-xs',
'border-slate-300',
'border'
)}
@@ -48,15 +48,15 @@ export function PageAside(props: {
'group/aside',
'hidden',
'xl:flex',
'chat-open:xl:max-3xl:hidden',
'chat-open:xl:max-3xl:opacity-0',
'xl:max-3xl:chat-open:hidden',
'xl:max-3xl:chat-open:opacity-0',
'max-w-56',
'chat-open:xl:max-3xl:*:w-56',
'chat-open:xl:max-3xl:max-w-0',
'chat-open:xl:max-3xl:ml-0',
'xl:max-3xl:*:chat-open:w-56',
'xl:max-3xl:chat-open:max-w-0',
'xl:max-3xl:chat-open:ml-0',
'motion-safe:xl:transition-all motion-safe:xl:duration-300',
'motion-safe:[transition-behavior:allow-discrete]',
'motion-safe:transition-discrete',
'flex-col',
'basis-56',
@@ -73,29 +73,29 @@ export function PageAside(props: {
'lg:max-h-screen',
// With header
'site-header:lg:top-16',
'site-header:lg:max-h-[calc(100vh_-_4rem)]',
'lg:site-header:top-16',
'lg:site-header:max-h-[calc(100vh-4rem)]',
// With header & sections
'site-header-sections:lg:top-[6.75rem]',
'site-header-sections:lg:max-h-[calc(100vh_-_6.75rem)]',
'lg:site-header-sections:top-27',
'lg:site-header-sections:max-h-[calc(100vh-6.75rem)]',
// When in api page mode, we display it as an overlay on non-large resolutions
'page-api-block:xl:max-2xl:z-10',
'page-api-block:xl:max-2xl:fixed',
'page-api-block:xl:max-2xl:right-8',
'page-api-block:xl:max-2xl:w-56',
'page-api-block:xl:max-2xl:bg-tint/9',
'page-api-block:xl:max-2xl:contrast-more:bg-tint',
'page-api-block:xl:max-2xl:backdrop-blur-lg',
'page-api-block:xl:max-2xl:border',
'page-api-block:xl:max-2xl:border-tint',
'page-api-block:xl:max-2xl:hover:shadow-lg',
'page-api-block:xl:max-2xl:hover:shadow-tint-12/1',
'page-api-block:xl:max-2xl:dark:hover:shadow-tint-1/1',
'page-api-block:xl:max-2xl:rounded-md',
'page-api-block:xl:max-2xl:h-auto',
'page-api-block:xl:max-2xl:my-9',
'xl:max-2xl:page-api-block:z-10',
'xl:max-2xl:page-api-block:fixed',
'xl:max-2xl:page-api-block:right-8',
'xl:max-2xl:page-api-block:w-56',
'xl:max-2xl:page-api-block:bg-tint/9',
'xl:max-2xl:contrast-more:page-api-block:bg-tint',
'xl:max-2xl:page-api-block:backdrop-blur-lg',
'xl:max-2xl:page-api-block:border',
'xl:max-2xl:page-api-block:border-tint',
'xl:max-2xl:page-api-block:hover:shadow-lg',
'xl:max-2xl:page-api-block:hover:shadow-tint-12/1',
'xl:max-2xl:dark:page-api-block:hover:shadow-tint-1/1',
'xl:max-2xl:page-api-block:rounded-md',
'xl:max-2xl:page-api-block:h-auto',
'xl:max-2xl:page-api-block:my-9',
'page-api-block:px-2',
'page-api-block:py-1.5'
)}
@@ -105,7 +105,7 @@ export function PageAside(props: {
<div
className={tcls(
'hidden',
'page-api-block:xl:max-2xl:flex',
'xl:max-2xl:page-api-block:flex',
'text-xs',
'tracking-wide',
'font-semibold',
@@ -124,7 +124,7 @@ export function PageAside(props: {
'size-3',
'opacity-6',
'ml-auto',
'page-api-block:xl:max-2xl:group-hover/aside:hidden'
'xl:max-2xl:page-api-block:group-hover/aside:hidden'
)}
/>
</div>
@@ -140,16 +140,16 @@ export function PageAside(props: {
'sticky',
'lg:top:0',
'site-header:lg:top-16',
'site-header-sections:lg:top-[6.75rem]',
'lg:site-header:top-16',
'lg:site-header-sections:top-27',
'gap-6',
'pt-8',
'pt-10',
'page-api-block:xl:max-2xl:py-0',
'xl:max-2xl:page-api-block:py-0',
// Hide it for api page, until hovered
'page-api-block:xl:max-2xl:hidden',
'page-api-block:xl:max-2xl:group-hover/aside:flex'
'xl:max-2xl:page-api-block:hidden',
'xl:max-2xl:page-api-block:group-hover/aside:flex'
)}
>
{document ? (
@@ -166,15 +166,15 @@ export function PageAside(props: {
'border-t',
'first:border-none',
'border-tint-subtle',
'py-4',
'py-6',
'first:pt-0',
'page-api-block:xl:max-2xl:px-3',
'xl:max-2xl:page-api-block:px-3',
'empty:hidden'
)}
>
{withPageFeedback ? (
<React.Suspense fallback={null}>
<PageFeedbackForm pageId={page.id} className={tcls('mt-2')} />
<PageFeedbackForm pageId={page.id} />
</React.Suspense>
) : null}
{customization.git.showEditLink && space.gitSync?.url && page.git ? (
@@ -237,8 +237,8 @@ export function PageAside(props: {
) : null}
<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 pb-4 page-api-block:xl:max-2xl:hidden page-api-block:xl:max-2xl:pb-0 page-api-block:xl:max-2xl:group-hover/aside:flex [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'page-api-block:xl:max-2xl:bg-transparent'
'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 pb-4 xl:max-2xl:page-api-block:hidden xl:max-2xl:page-api-block:pb-0 xl:max-2xl:page-api-block:group-hover/aside:flex [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'xl:max-2xl:page-api-block:bg-transparent'
)}
>
{/* Mode Switcher */}
@@ -128,7 +128,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
>
{section.tag ? (
<span
className={`-mt-0.5 openapi-method !text-xs openapi-method-${section.tag.toLowerCase()}`}
className={`-mt-0.5 openapi-method text-xs! openapi-method-${section.tag.toLowerCase()}`}
>
{section.tag}
</span>
@@ -50,7 +50,7 @@ export function PageBody(props: {
<main
className={tcls(
'relative min-w-0 flex-1',
'mx-auto max-w-screen-2xl py-8',
'mx-auto max-w-screen-2xl py-11',
// Allow words to break if they are too long.
'break-anywhere',
pageWidthWide ? 'page-width-wide 2xl:px-8' : 'page-width-default',
@@ -77,7 +77,7 @@ export async function PageBodyBlankslate(props: {
'gap-4',
'grid-cols-1',
'sm:grid-cols-2',
'page-width-wide:md:grid-cols-3'
'md:page-width-wide:grid-cols-3'
)}
>
{pageElements}
@@ -89,7 +89,7 @@ export async function PageCover(props: {
'lg:-ml-12',
!page.layout.tableOfContents &&
context.customization.header.preset !== 'none'
? 'xl:-ml-[19rem]'
? 'xl:-ml-76'
: null,
]
: [
@@ -108,7 +108,7 @@ function NavigationCard(
reversed ? 'pl-4' : null,
'border',
'border-tint-subtle',
'rounded',
'rounded-sm',
'circular-corners:rounded-2xl',
'straight-corners:rounded-none',
'hover:border-primary',
@@ -45,8 +45,8 @@ export async function PageHeader(props: {
(withAIChat || pageActions.markdown || pageActions.externalAI) ? (
<div
className={tcls(
'float-right mb-2 ml-4 page-api-block:xl:max-2xl:mr-[14.5rem]',
ancestors.length > 0 ? '-mt-2' : 'xs:mt-2'
'float-right mb-2 ml-4 xl:max-2xl:page-api-block:mr-58',
ancestors.length > 0 ? '-mt-2' : '-mt-3 xs:mt-2'
)}
>
<AIActionsDropdown
@@ -94,7 +94,7 @@ export function PageFeedbackForm(props: {
<textarea
ref={inputRef}
name="comment"
className="max-h-40 min-h-16 grow rounded 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"
className="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)}
@@ -151,11 +151,11 @@ function RatingButton(
<button
className={tcls(
'p-2 first:rounded-l-full first:pl-2.5 last:rounded-r-full last:pr-2.5 hover:bg-primary-hover hover:text-primary-strong',
'disabled:cursor-not-allowed disabled:hover:bg-inherit disabled:hover:text-inherit disabled:dark:hover:text-inherit',
'disabled:cursor-not-allowed disabled:hover:bg-inherit disabled:hover:text-inherit dark:disabled:hover:text-inherit',
'ring-tint contrast-more:hover:ring-1',
active
? 'bg-primary-active text-primary-strong disabled:hover:bg-primary-active disabled:hover:text-primary-strong contrast-more:ring-2 contrast-more:hover:ring-2'
: 'disabled:opacity-7 disabled:contrast-more:ring-0'
: 'disabled:opacity-7 contrast-more:disabled:ring-0'
)}
type="button"
{...attr}
@@ -4,6 +4,7 @@ import type React from 'react';
import { TranslateContext } from '@/intl/client';
import type { TranslationLanguage } from '@/intl/translations';
import { TooltipProvider } from '@radix-ui/react-tooltip';
import { LoadingStateProvider } from '../primitives/LoadingStateProvider';
export function ClientContexts(props: {
@@ -14,7 +15,9 @@ export function ClientContexts(props: {
return (
<TranslateContext.Provider value={language}>
<LoadingStateProvider>{children}</LoadingStateProvider>
<TooltipProvider delayDuration={200}>
<LoadingStateProvider>{children}</LoadingStateProvider>
</TooltipProvider>
</TranslateContext.Provider>
);
}
@@ -1,6 +1,213 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@config '../../../tailwind.config.ts';
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility hide-scroll {
/* hide scroll bar but still scroll */
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-ms-overflow-style: none;
}
@utility linear-mask-gradient {
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
}
@utility linear-mask-util {
mask-image: linear-gradient(to bottom, white, white);
mask-image: linear-gradient(to bottom, white, white);
}
@utility grid-area-1-1 {
grid-area: 1 / 1;
grid-area: 1 / 1;
}
@utility gutter-stable {
scrollbar-gutter: stable;
scrollbar-gutter: stable;
}
@utility triangle {
position: relative;
background-color: orange;
text-align: left;
transform: rotate(-60deg) skewX(-30deg) scale(1, 0.866);
&:before {
content: "";
position: absolute;
background-color: inherit;
}
&:after {
content: "";
position: absolute;
background-color: inherit;
}
width: inherit;
height: inherit;
border-top-right-radius: 30%;
&:before {
width: inherit;
height: inherit;
border-top-right-radius: 30%;
}
&:after {
width: inherit;
height: inherit;
border-top-right-radius: 30%;
}
&:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414, 0.707) translate(0, -50%);
}
&:after {
transform: rotate(135deg) skewY(-45deg) scale(0.707, 1.414) translate(50%);
}
position: relative;
background-color: orange;
text-align: left;
transform: rotate(-60deg) skewX(-30deg) scale(1, 0.866);
&:before {
content: "";
position: absolute;
background-color: inherit;
}
&:after {
content: "";
position: absolute;
background-color: inherit;
}
width: inherit;
height: inherit;
border-top-right-radius: 30%;
&:before {
width: inherit;
height: inherit;
border-top-right-radius: 30%;
}
&:after {
width: inherit;
height: inherit;
border-top-right-radius: 30%;
}
&:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414, 0.707) translate(0, -50%);
}
&:after {
transform: rotate(135deg) skewY(-45deg) scale(0.707, 1.414) translate(50%);
}
}
@utility break-anywhere {
word-break: break-word;
@supports (overflow-wrap: anywhere) {
word-break: break-word;
overflow-wrap: anywhere;
}
word-break: break-word;
@supports (overflow-wrap: anywhere) {
word-break: break-word;
overflow-wrap: anywhere;
}
}
@utility bg-gradient-primary {
@apply bg-linear-to-bl from-primary-4 to-tint-base to-60% bg-fixed;
@apply bg-linear-to-bl from-primary-4 to-tint-base to-60% bg-fixed;
}
@utility bg-gradient-tint {
@apply bg-linear-to-bl from-tint-4 to-tint-base to-60% bg-fixed;
@apply bg-linear-to-bl from-tint-4 to-tint-base to-60% bg-fixed;
}
@utility scroll-nojump {
/**
* Utility to prevent horizontal jumps when the vertical scrollbar appears
* It should be used on main containers of the layout, while preserving horizontal delimiters.
* It basically adds a margin-left with the width of the scrollbar, and a width that compensates for it; when a scrollbar is displayed.
* Inspired by https://aykevl.nl/2014/09/fix-jumping-scrollbar, but adapted to work on child elements of "html"
*/
width: 100%;
@media screen and (min-width: 1536px) {
margin-left: var(--scrollbar-width);
margin-right: 0;
width: calc(100% - var(--scrollbar-width));
body:has(.page-full-width) & {
margin-left: 0;
width: 100%;
}
}
/**
* Utility to prevent horizontal jumps when the vertical scrollbar appears
* It should be used on main containers of the layout, while preserving horizontal delimiters.
* It basically adds a margin-left with the width of the scrollbar, and a width that compensates for it; when a scrollbar is displayed.
* Inspired by https://aykevl.nl/2014/09/fix-jumping-scrollbar, but adapted to work on child elements of "html"
*/
width: 100%;
@media screen and (min-width: 1536px) {
margin-left: var(--scrollbar-width);
margin-right: 0;
width: calc(100% - var(--scrollbar-width));
}
}
@utility page-full-width {
@media screen and (min-width: 1536px) {
body:has(&) .scroll-nojump {
margin-left: 0;
width: 100%;
}
}
}
@utility elevate-link {
& a[href]:not(.link-overlay) {
position: relative;
z-index: 20;
}
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer base {
:root {
@@ -28,7 +235,12 @@
button,
input,
textarea {
@apply outline-2 outline-primary;
@apply focus-visible:outline-2 focus-visible:outline-primary;
}
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
code,
@@ -54,92 +266,10 @@
}
.flip-heading-hash {
@apply [&:is(h1,h2,h3,h4)]:flex-row-reverse;
@apply [&:is(h1,h2,h3,h4)]:[justify-content:start];
@apply [&:is(h1,h2,h3,h4)>div:first-child]:ml-2;
}
}
@layer utilities {
.linear-mask-gradient {
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
}
.linear-mask-util {
mask-image: linear-gradient(to bottom, white, white);
}
.grid-area-1-1 {
grid-area: 1 / 1;
}
.gutter-stable {
scrollbar-gutter: stable;
}
.triangle {
position: relative;
background-color: orange;
text-align: left;
transform: rotate(-60deg) skewX(-30deg) scale(1, 0.866);
}
.triangle:before,
.triangle:after {
content: "";
position: absolute;
background-color: inherit;
}
.triangle,
.triangle:before,
.triangle:after {
width: inherit;
height: inherit;
border-top-right-radius: 30%;
}
.triangle:before {
transform: rotate(-135deg) skewX(-45deg) scale(1.414, 0.707) translate(0, -50%);
}
.triangle:after {
transform: rotate(135deg) skewY(-45deg) scale(0.707, 1.414) translate(50%);
}
.break-anywhere {
word-break: break-word;
}
@supports (overflow-wrap: anywhere) {
.break-anywhere {
word-break: break-word;
overflow-wrap: anywhere;
}
}
.bg-gradient-primary {
@apply bg-gradient-to-bl from-primary-4 to-tint-base to-60% bg-fixed;
}
.bg-gradient-tint {
@apply bg-gradient-to-bl from-tint-4 to-tint-base to-60% bg-fixed;
}
/**
* Utility to prevent horizontal jumps when the vertical scrollbar appears
* It should be used on main containers of the layout, while preserving horizontal delimiters.
* It basically adds a margin-left with the width of the scrollbar, and a width that compensates for it; when a scrollbar is displayed.
* Inspired by https://aykevl.nl/2014/09/fix-jumping-scrollbar, but adapted to work on child elements of "html"
*/
.scroll-nojump {
width: 100%;
}
@media screen and (min-width: 1536px) {
.scroll-nojump {
margin-left: var(--scrollbar-width);
margin-right: 0;
width: calc(100% - var(--scrollbar-width));
}
}
.elevate-link {
& a[href]:not(.link-overlay) {
position: relative;
z-index: 20;
}
@apply [h1,h2,h3,h4]:content-start;
@apply [h1,h2,h3,h4]:auto-cols-[auto_1fr];
@apply [&:is(h1,h2,h3,h4)>div:first-child]:[grid-area:1/2];
@apply [&:is(h1,h2,h3,h4)>div:first-child]:ml-1;
}
}
@@ -160,83 +290,83 @@ html.announcement-hidden #announcement-banner {
/* Shiki theme */
:root {
--shiki-color-text: theme("colors.tint.11");
--shiki-token-punctuation: theme("colors.tint.11");
--shiki-token-comment: theme("colors.neutral.9/.7");
--shiki-token-link: theme("colors.primary.10");
--shiki-color-text: rgb(var(--tint-11));
--shiki-token-punctuation: rgb(var(--tint-11));
--shiki-token-comment: rgb(var(--neutral-9));
--shiki-token-link: rgb(var(--primary-10));
--shiki-token-constant: theme("colors.warning.10");
--shiki-token-string: theme("colors.warning.10");
--shiki-token-string-expression: theme("colors.success.10");
--shiki-token-keyword: theme("colors.danger.10");
--shiki-token-parameter: theme("colors.warning.10");
--shiki-token-function: theme("colors.primary.10");
--shiki-token-constant: rgb(var(--warning-10));
--shiki-token-string: rgb(var(--warning-10));
--shiki-token-string-expression: rgb(var(--success-10));
--shiki-token-keyword: rgb(var(--danger-10));
--shiki-token-parameter: rgb(var(--warning-10));
--shiki-token-function: rgb(var(--primary-10));
--shiki-token-inserted: theme("colors.success.10");
--shiki-token-deleted: theme("colors.danger.10");
--shiki-token-changed: theme("colors.tint.12");
--shiki-token-inserted: rgb(var(--success-10));
--shiki-token-deleted: rgb(var(--danger-10));
--shiki-token-changed: rgb(var(--tint-12));
}
@media (prefers-contrast: more) {
:root {
--shiki-color-text: theme("colors.tint.12");
--shiki-token-punctuation: theme("colors.tint.12");
--shiki-token-comment: theme("colors.neutral.11");
--shiki-token-link: theme("colors.primary.11");
--shiki-color-text: rgb(var(--tint-12));
--shiki-token-punctuation: rgb(var(--tint-12));
--shiki-token-comment: rgb(var(--neutral-11));
--shiki-token-link: rgb(var(--primary-11));
--shiki-token-constant: theme("colors.warning.11");
--shiki-token-string: theme("colors.warning.11");
--shiki-token-string-expression: theme("colors.success.11");
--shiki-token-keyword: theme("colors.danger.11");
--shiki-token-parameter: theme("colors.warning.11");
--shiki-token-function: theme("colors.primary.11");
--shiki-token-constant: rgb(var(--warning-11));
--shiki-token-string: rgb(var(--warning-11));
--shiki-token-string-expression: rgb(var(--success-11));
--shiki-token-keyword: rgb(var(--danger-11));
--shiki-token-parameter: rgb(var(--warning-11));
--shiki-token-function: rgb(var(--primary-11));
--shiki-token-inserted: theme("colors.success.11");
--shiki-token-deleted: theme("colors.danger.11");
--shiki-token-changed: theme("colors.tint.12");
--shiki-token-inserted: rgb(var(--success-11));
--shiki-token-deleted: rgb(var(--danger-11));
--shiki-token-changed: rgb(var(--tint-12));
}
}
html.dark {
/* Override select colors to have more contrast */
--shiki-token-comment: theme("colors.neutral.9");
--shiki-token-comment: rgb(var(--neutral-9));
--shiki-token-constant: theme("colors.warning.11");
--shiki-token-string: theme("colors.warning.11");
--shiki-token-string-expression: theme("colors.success.11");
--shiki-token-keyword: theme("colors.danger.11");
--shiki-token-parameter: theme("colors.warning.11");
--shiki-token-function: theme("colors.primary.11");
--shiki-token-constant: rgb(var(--warning-11));
--shiki-token-string: rgb(var(--warning-11));
--shiki-token-string-expression: rgb(var(--success-11));
--shiki-token-keyword: rgb(var(--danger-11));
--shiki-token-parameter: rgb(var(--warning-11));
--shiki-token-function: rgb(var(--primary-11));
}
.code-monochrome {
--shiki-token-constant: theme("colors.tint.11");
--shiki-token-string: theme("colors.tint.12");
--shiki-token-string-expression: theme("colors.tint.12");
--shiki-token-keyword: theme("colors.primary.10");
--shiki-token-parameter: theme("colors.tint.9");
--shiki-token-function: theme("colors.primary.9");
--shiki-token-constant: rgb(var(--tint-11));
--shiki-token-string: rgb(var(--tint-12));
--shiki-token-string-expression: rgb(var(--tint-12));
--shiki-token-keyword: rgb(var(--primary-10));
--shiki-token-parameter: rgb(var(--tint-9));
--shiki-token-function: rgb(var(--primary-9));
}
html.dark.code-monochrome {
--shiki-token-constant: theme("colors.tint.11");
--shiki-token-string: theme("colors.tint.12");
--shiki-token-string-expression: theme("colors.tint.12");
--shiki-token-keyword: theme("colors.primary.11");
--shiki-token-parameter: theme("colors.tint.10");
--shiki-token-function: theme("colors.primary.10");
--shiki-token-constant: rgb(var(--tint-11));
--shiki-token-string: rgb(var(--tint-12));
--shiki-token-string-expression: rgb(var(--tint-12));
--shiki-token-keyword: rgb(var(--primary-11));
--shiki-token-parameter: rgb(var(--tint-10));
--shiki-token-function: rgb(var(--primary-10));
}
/* Code blocks */
.highlight-line {
@apply grid [grid-template-columns:subgrid] col-span-2 relative ring-1 ring-transparent hover:ring-tint-hover hover:z-[1] rounded;
@apply grid grid-cols-subgrid col-span-2 relative ring-1 ring-transparent hover:ring-tint-hover hover:z-1 rounded-sm;
@apply only:hover:ring-transparent;
@apply [counter-increment:line];
&.highlighted {
@apply bg-tint-active;
@apply first:rounded-t-md *:first:mt-1;
@apply last:rounded-b-md *:last:mb-1;
@apply first:rounded-t-md first:*:mt-1;
@apply last:rounded-b-md last:*:mb-1;
@apply rounded-none;
}
@@ -254,11 +384,11 @@ html.dark.code-monochrome {
}
.highlight-line-number {
@apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-gradient-to-r from-80% from-tint-subtle contrast-more:from-tint-base theme-muted:from-tint-base [html.theme-bold.sidebar-filled_&]:from-tint-base to-transparent;
@apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-linear-to-r from-80% from-tint-subtle contrast-more:from-tint-base theme-muted:from-tint-base [html.theme-bold.sidebar-filled_&]:from-tint-base to-transparent;
@apply before:text-tint before:content-[counter(line)];
.highlight-line.highlighted > & {
@apply before:text-tint bg-gradient-to-r from-80% from-tint-active to-transparent;
@apply before:text-tint bg-linear-to-r from-80% from-tint-active to-transparent;
}
}
@@ -21,8 +21,8 @@ export function HighlightQuery(props: {
'px-0.5',
'-mx-0.5',
'py-0.5',
'rounded',
'straight-corners:rounded-sm',
'rounded-sm',
'straight-corners:rounded-xs',
'transition-colors',
'group-hover:bg-primary-active',
'group-hover:text-contrast-primary-active',
@@ -15,7 +15,7 @@ import { useTrackEvent } from '../Insights';
import { Link } from '../primitives';
import { useSearchAskContext } from './SearchAskContext';
import { type AskAnswerResult, type AskAnswerSource, streamAskQuestion } from './server-actions';
import { useSearch, useSearchLink } from './useSearch';
import { useSearchLink } from './useSearch';
export type SearchAskState =
| {
@@ -37,7 +37,6 @@ export function SearchAskAnswer(props: { query: string }) {
const language = useLanguage();
const trackEvent = useTrackEvent();
const [, setSearchState] = useSearch();
const [askState, setAskState] = useSearchAskContext();
React.useEffect(() => {
@@ -51,10 +50,6 @@ export function SearchAskAnswer(props: { query: string }) {
query,
});
// When we pass in "ask" mode, the query could still be updated by the client
// we ensure that the query is up-to-date before starting the stream.
setSearchState((prev) => (prev ? { ...prev, query, ask: true } : null));
const { stream } = await streamAskQuestion({ question: query });
for await (const chunk of readStreamableValue(stream)) {
if (cancelled) {
@@ -80,7 +75,7 @@ export function SearchAskAnswer(props: { query: string }) {
cancelled = true;
}
};
}, [query, setAskState, setSearchState, trackEvent]);
}, [query, setAskState, trackEvent]);
React.useEffect(() => {
return () => {
@@ -186,7 +181,7 @@ function AnswerFollowupQuestions(props: { followupQuestions: string[] }) {
'px-4',
'-mx-4',
'py-2',
'rounded',
'rounded-sm',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
'text-tint',
@@ -194,8 +189,7 @@ function AnswerFollowupQuestions(props: { followupQuestions: string[] }) {
'focus-within:bg-tint-hover'
)}
{...getSearchLinkProps({
query: question,
ask: true,
ask: question,
})}
>
<Icon
@@ -16,6 +16,7 @@ import { SearchScopeToggle } from './SearchScopeToggle';
import { useSearch } from './useSearch';
interface SearchContainerProps {
spaceId: string;
spaceTitle: string;
isMultiVariants: boolean;
aiMode: CustomizationAIMode;
@@ -26,9 +27,10 @@ interface SearchContainerProps {
* Client component to render the search input and results.
*/
export function SearchContainer(props: SearchContainerProps) {
const { spaceTitle, isMultiVariants, aiMode, className } = props;
const { spaceId, spaceTitle, isMultiVariants, aiMode, className } = props;
const [state, setSearchState] = useSearch();
const withAIChat = aiMode === CustomizationAIMode.Assistant;
const [state, setSearchState] = useSearch(withAIChat);
const searchAsk = useSearchAskState();
const router = useRouter();
const trackEvent = useTrackEvent();
@@ -40,17 +42,32 @@ export function SearchContainer(props: SearchContainerProps) {
// Derive open state from search state
const open = state?.open ?? false;
const onClose = async (to?: string) => {
if (state?.query === '') {
await setSearchState(null);
} else if (state) {
await setSearchState({ ...state, open: false });
React.useEffect(() => {
if (state?.ask && aiMode === CustomizationAIMode.Assistant) {
// Close the pop-over when we switch to “ask” mode
setSearchState((prev) => (prev ? { ...prev, open: !state.ask } : prev));
} else if (state?.ask && aiMode === CustomizationAIMode.None) {
// Rewrite ask to query when the site doesn't support AI
setSearchState((prev) => (prev ? { ...prev, query: prev.ask, ask: null } : prev));
}
}, [state?.ask, setSearchState, aiMode]);
if (to) {
router.push(to);
}
};
const onClose = React.useCallback(
async (to?: string) => {
if (state) {
await setSearchState({
...state,
open: false,
query: state.query === '' ? null : state.query,
});
}
if (to) {
router.push(to);
}
},
[state, setSearchState, router]
);
useHotkeys(
'mod+k',
@@ -61,21 +78,21 @@ export function SearchContainer(props: SearchContainerProps) {
[]
);
const onOpen = () => {
const onOpen = React.useCallback(() => {
if (open) {
return;
}
setSearchState((prev) => ({
ask: prev?.ask ?? false,
ask: aiMode === CustomizationAIMode.Assistant && prev ? prev.ask : null,
global: prev?.global ?? false,
query: prev?.query ?? '',
query: (!withAIChat ? prev?.ask : null) ?? prev?.query ?? '',
open: true,
}));
trackEvent({
type: 'search_open',
});
};
}, [open, setSearchState, trackEvent, withAIChat]);
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -105,7 +122,7 @@ export function SearchContainer(props: SearchContainerProps) {
const onChange = (value: string) => {
setSearchState((prev) => ({
ask: false, // When typing, we go back to the default search mode
ask: aiMode === CustomizationAIMode.Assistant && prev ? prev.ask : null, // When typing, we go back to the default search mode
query: value,
global: prev?.global ?? false,
open: true,
@@ -113,7 +130,10 @@ export function SearchContainer(props: SearchContainerProps) {
};
// We trim the query to avoid invalidating the search when the user is typing between words.
const normalizedQuery = state?.query.trim() ?? '';
const normalizedQuery = state?.query?.trim() ?? '';
const normalizedAsk = state?.ask?.trim() ?? '';
const showAsk = aiMode === CustomizationAIMode.Search && normalizedAsk;
return (
<SearchAskProvider value={searchAsk}>
@@ -122,18 +142,19 @@ export function SearchContainer(props: SearchContainerProps) {
// Only show content if there's a query or Ask is enabled
(state?.query || aiMode !== CustomizationAIMode.None) && open ? (
<React.Suspense fallback={null}>
{isMultiVariants && !state?.ask ? (
{isMultiVariants && !showAsk ? (
<SearchScopeToggle spaceTitle={spaceTitle} />
) : null}
{state !== null && !state.ask ? (
{state !== null && !showAsk ? (
<SearchResults
ref={resultsRef}
query={normalizedQuery}
global={state?.global ?? false}
aiMode={aiMode}
spaceId={spaceId}
/>
) : null}
{state?.ask ? <SearchAskAnswer query={normalizedQuery} /> : null}
{showAsk ? <SearchAskAnswer query={normalizedAsk} /> : null}
</React.Suspense>
) : null
}
@@ -145,7 +166,7 @@ export function SearchContainer(props: SearchContainerProps) {
onOpenAutoFocus: (event) => event.preventDefault(),
align: 'start',
className:
'bg-tint-base has-[.empty]:hidden scroll-py-2 w-[32rem] p-2 max-h-[min(32rem,var(--radix-popover-content-available-height))] max-w-[min(var(--radix-popover-content-available-width),32rem)]',
'bg-tint-base has-[.empty]:hidden scroll-py-2 w-128 p-2 max-h-[min(32rem,var(--radix-popover-content-available-height))] max-w-[min(var(--radix-popover-content-available-width),32rem)]',
onInteractOutside: (event) => {
// Don't close if clicking on the search input itself
if (searchInputRef.current?.contains(event.target as Node)) {
@@ -168,7 +189,7 @@ export function SearchContainer(props: SearchContainerProps) {
>
<SearchInput
ref={searchInputRef}
value={state?.query ?? ''}
value={(!withAIChat ? state?.ask : null) ?? state?.query ?? ''}
onFocus={onOpen}
onChange={onChange}
onKeyDown={onKeyDown}
@@ -59,8 +59,8 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
variantClasses.header,
sizeClasses,
// Additional custom styles
'has-[input:focus]:-translate-y-px h-9 grow cursor-pointer px-2.5 has-[input:focus]:bg-tint-base depth-subtle:has-[input:focus]:shadow-lg depth-subtle:has-[input:focus]:shadow-primary-subtle has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-primary-hover md:cursor-text',
'theme-bold:border-header-link/3 theme-bold:has-[input:focus-visible]:border-header-link/5 theme-bold:has-[input:focus-visible]:bg-header-link/3 theme-bold:has-[input:focus-visible]:ring-header-link/5',
'has-[input:focus]:-translate-y-px h-9 grow 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 md:cursor-text',
'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.
'relative z-30 shrink grow justify-start max-md:absolute max-md:right-0',
isOpen ? 'max-md:w-56' : 'max-md:w-[38px]',
@@ -74,14 +74,17 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
size="medium"
iconOnly
icon="circle-xmark"
className="-ml-1.5 -mr-1 animate-scaleIn px-1.5"
className="-ml-1.5 -mr-1 animate-scale-in px-1.5"
onClick={() => {
onChange('');
inputRef.current?.focus();
}}
/>
) : (
<Icon icon="magnifying-glass" className="size-4 shrink-0 animate-scaleIn" />
<Icon
icon="magnifying-glass"
className="size-4 shrink-0 animate-scale-in"
/>
)}
<input
@@ -91,11 +94,12 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
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, aiMode === CustomizationAIMode.Search ? 'search_or_ask' : 'search')}...`}
placeholder={`${tString(language, aiMode === CustomizationAIMode.Search ? '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-none transition-[width] duration-300 contain-paint placeholder:text-tint theme-bold:placeholder:text-current theme-bold:placeholder:opacity-7',
'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-md:opacity-0'
)}
ref={inputRef}
@@ -127,18 +131,18 @@ function Shortcut() {
<div
aria-busy={operatingSystem === null ? 'true' : undefined}
className={tcls(
`shortcut -mr-1 relative z-10 hidden justify-end gap-0.5 whitespace-nowrap text-xs [font-feature-settings:"calt",_"case"] after:absolute after:right-full after:z-20 after:h-full after:w-8 after:bg-gradient-to-r after:from-transparent after:to-tint-base theme-bold:after:to-transparent after:content-[''] contrast-more:text-tint-strong md:flex`,
`shortcut -mr-1 relative z-10 hidden justify-end gap-0.5 whitespace-nowrap text-xs [font-feature-settings:"calt","case"] after:absolute after:right-full after:z-20 after:h-full after:w-8 after:bg-linear-to-r after:from-transparent after:to-tint-base theme-bold:after:to-transparent after:content-[''] contrast-more:text-tint-strong md:flex`,
operatingSystem
? 'motion-safe:animate-fadeIn motion-reduce:opacity-11'
? 'motion-safe:animate-fade-in motion-reduce:opacity-11'
: 'opacity-0'
)}
>
<kbd
className={`flex h-5 min-w-5 items-center justify-center rounded border border-tint-subtle theme-bold:border-header-link/5 bg-tint-base theme-bold:bg-header-background px-1 ${operatingSystem === 'mac' ? 'text-sm' : ''}`}
className={`flex h-5 min-w-5 items-center justify-center rounded-sm border border-tint-subtle theme-bold:border-header-link/5 bg-tint-base theme-bold:bg-header-background px-1 ${operatingSystem === 'mac' ? 'text-sm' : ''}`}
>
{operatingSystem === 'mac' ? '⌘' : 'Ctrl'}
</kbd>
<kbd className="flex size-5 items-center justify-center rounded border border-tint-subtle theme-bold:border-header-link/5 bg-tint-base theme-bold:bg-header-background px-1">
<kbd className="flex size-5 items-center justify-center rounded-sm border border-tint-subtle theme-bold:border-header-link/5 bg-tint-base theme-bold:bg-header-background px-1">
K
</kbd>
</div>
@@ -30,6 +30,7 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
ref={ref}
href={item.href}
active={active}
data-testid="search-page-result"
action={tString(language, 'view')}
leadingIcon={<Icon icon="memo" className="size-4" />}
insights={{
@@ -2,10 +2,9 @@ import React from 'react';
import { t, tString, useLanguage } from '@/intl/client';
import { Icon } from '@gitbook/icons';
import { useAIChatController } from '../AI';
import { AIChatIcon } from '../AIChat/';
import { SearchResultItem } from './SearchResultItem';
import { useSearch, useSearchLink } from './useSearch';
import { useSearchLink } from './useSearch';
export const SearchQuestionResultItem = React.forwardRef(function SearchQuestionResultItem(
props: {
@@ -19,31 +18,19 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion
const { question, recommended = false, active, withAIChat } = props;
const language = useLanguage();
const getLinkProp = useSearchLink();
const chatController = useAIChatController();
const [, setSearchState] = useSearch();
return (
<SearchResultItem
size={recommended ? 'small' : 'medium'}
action={tString(language, 'ask', '')}
ref={ref}
data-testid="search-result-item"
data-testid={recommended ? 'search-recommended-question' : 'search-ask-question'}
scroll={false}
{...(withAIChat
? { href: '#' }
: getLinkProp({
ask: true,
open: true,
query: question,
}))}
onClick={() => {
if (withAIChat) {
// If AI Chat is enabled, hijack to open the chat and post the question
chatController.open();
chatController.postMessage({ message: question });
setSearchState(null); // Close the search modal
}
}}
{...getLinkProp({
ask: question,
open: false,
query: null,
})}
active={active}
leadingIcon={
recommended ? (
@@ -56,13 +56,7 @@ export const SearchResultItem = React.forwardRef(function SearchResultItem(
<div className="size-4 shrink-0 text-tint-subtle">{leadingIcon}</div>
<div className="grow">{children}</div>
{active ? (
<Button
label={action}
iconOnly
variant="primary"
className="p-2"
icon="arrow-turn-down-left"
/>
<Button label={action} iconOnly variant="primary" icon="arrow-turn-down-left" />
) : (
<div className="flex size-8 shrink-0 items-center justify-center text-tint-subtle">
<Icon icon="chevron-right" className="size-3" />
@@ -8,6 +8,7 @@ import { t, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { CustomizationAIMode } from '@gitbook/api';
import { assert } from 'ts-essentials';
import { useTrackEvent } from '../Insights';
import { Loading } from '../primitives';
import { SearchPageResultItem } from './SearchPageResultItem';
@@ -33,9 +34,11 @@ type ResultType =
/**
* We cache the recommended questions globally to avoid calling the API multiple times
* when re-opening the search modal.
* when re-opening the search modal. The cache is per space, so that we can
* have different recommended questions for different spaces of the same site.
* It should not be used outside of an useEffect.
*/
let cachedRecommendedQuestions: null | ResultType[] = null;
const cachedRecommendedQuestions: Map<string, ResultType[]> = new Map();
/**
* Fetch the results of the keyboard navigable elements to display for a query:
@@ -49,10 +52,11 @@ export const SearchResults = React.forwardRef(function SearchResults(
query: string;
global: boolean;
aiMode: CustomizationAIMode;
spaceId: string;
},
ref: React.Ref<SearchResultsRef>
) {
const { children, query, aiMode, global } = props;
const { children, query, aiMode, global, spaceId } = props;
const language = useLanguage();
const trackEvent = useTrackEvent();
@@ -73,8 +77,10 @@ export const SearchResults = React.forwardRef(function SearchResults(
return;
}
if (cachedRecommendedQuestions) {
setResultsState({ results: cachedRecommendedQuestions, fetching: false });
if (cachedRecommendedQuestions.has(spaceId)) {
const results = cachedRecommendedQuestions.get(spaceId);
assert(results, `Cached recommended questions should be set for space ${spaceId}`);
setResultsState({ results, fetching: false });
return;
}
@@ -92,7 +98,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
return;
}
const response = await streamRecommendedQuestions();
const response = await streamRecommendedQuestions(spaceId);
for await (const entry of readStreamableValue(response.stream)) {
if (!entry) {
continue;
@@ -109,7 +115,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
id: question,
question,
});
cachedRecommendedQuestions = recommendedQuestions;
cachedRecommendedQuestions.set(spaceId, recommendedQuestions);
if (!cancelled) {
setResultsState({ results: [...recommendedQuestions], fetching: false });
@@ -23,6 +23,7 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe
size="small"
active={active}
action={tString(language, 'view')}
data-testid="search-page-section-result"
insights={{
type: 'search_open_result',
query,
@@ -49,7 +50,7 @@ function highlightQueryInBody(body: string, query: string) {
// Ensure the query to be highlighted is visible in the body.
return (
<p className="relative line-clamp-3 text-sm [overflow-wrap:anywhere]">
<p className="wrap-anywhere relative line-clamp-3 text-sm">
<HighlightQuery query={query} text={idx < 20 ? body : `...${body.slice(idx - 10)}`} />
</p>
);
@@ -1,2 +1,3 @@
export * from './SearchInput';
export * from './SearchContainer';
export * from './useSearch';
@@ -185,8 +185,9 @@ export async function streamAskQuestion({
/**
* Stream a list of suggested questions for the site.
* Optionally scoped to a specific space.
*/
export async function streamRecommendedQuestions() {
export async function streamRecommendedQuestions(spaceId?: string) {
const siteURLData = await getSiteURLDataFromMiddleware();
const context = await getServerActionBaseContext();
@@ -196,7 +197,10 @@ export async function streamRecommendedQuestions() {
const apiClient = await context.dataFetcher.api();
const apiStream = apiClient.orgs.streamRecommendedQuestionsInSite(
siteURLData.organization,
siteURLData.site
siteURLData.site,
{
spaceId,
}
);
for await (const chunk of apiStream) {
@@ -338,7 +342,7 @@ async function transformAnswer(
document={answer.answer.document}
context={{
mode: 'default',
contentContext: undefined,
contentContext: context,
wrapBlocksInSuspense: false,
shouldRenderLinkPreviews: false, // We don't want to render link previews in the AI answer.
}}
@@ -5,8 +5,8 @@ import type { LinkProps } from '../primitives';
export interface SearchState {
// URL-backed state
query: string;
ask: boolean;
query: string | null;
ask: string | null;
global: boolean;
// Local UI state
@@ -16,7 +16,7 @@ export interface SearchState {
// KeyMap needs to be statically defined to avoid `setRawState` being redefined on every render.
const keyMap = {
q: parseAsString,
ask: parseAsBoolean,
ask: parseAsString,
global: parseAsBoolean,
};
@@ -27,25 +27,37 @@ export type UpdateSearchState = (
/**
* Hook to access the current search query and update it.
*/
export function useSearch(): [SearchState | null, UpdateSearchState] {
export function useSearch(withAIChat = false): [SearchState | null, UpdateSearchState] {
const [rawState, setRawState] = useQueryStates(keyMap, {
history: 'replace',
});
// Handle legacy ask=true format by converting it to the new format
React.useEffect(() => {
if (rawState?.ask === 'true' && rawState?.q) {
// Convert legacy format: q=query&ask=true -> ask=query&q=null
setRawState({
q: null,
ask: rawState.q,
global: rawState.global,
});
}
}, [rawState, setRawState]);
// Separate local state for open (not synchronized with URL)
// Default to true if there's already a query in the URL
const [open, setIsOpen] = React.useState(() => {
return rawState?.q !== null;
return rawState?.q !== null || (!withAIChat && rawState?.ask !== null);
});
const state = React.useMemo<SearchState | null>(() => {
if (rawState === null || rawState.q === null) {
if (rawState === null || (rawState.q === null && rawState.ask === null)) {
return null;
}
return {
query: rawState.q,
ask: !!rawState.ask,
ask: rawState.ask,
global: !!rawState.global,
open: open,
};
@@ -78,7 +90,7 @@ export function useSearch(): [SearchState | null, UpdateSearchState] {
return setRawState({
q: update.query,
ask: update.ask ? true : null,
ask: update.ask,
global: update.global ? true : null,
});
},
@@ -97,8 +109,8 @@ export function useSearchLink(): (query: Partial<SearchState>) => LinkProps {
return React.useCallback(
(query) => {
const searchParams = new URLSearchParams();
searchParams.set('q', query.query ?? '');
query.ask ? searchParams.set('ask', 'true') : searchParams.delete('ask');
query.query ? searchParams.set('q', query.query) : searchParams.delete('q');
query.ask ? searchParams.set('ask', query.ask) : searchParams.delete('ask');
query.global ? searchParams.set('global', 'true') : searchParams.delete('global');
return {
href: `?${searchParams.toString()}`,
@@ -106,10 +118,10 @@ export function useSearchLink(): (query: Partial<SearchState>) => LinkProps {
onClick: (event) => {
event.preventDefault();
setSearch((prev) => ({
query: '',
ask: false,
query: null,
ask: null,
global: false,
open: true,
open: query.open ?? prev?.open ?? false,
...(prev ?? {}),
...query,
}));
@@ -30,7 +30,9 @@ export async function SiteLayout(props: {
}) {
const { context, nonce, forcedTheme, withTracking, visitorAuthClaims, children } = props;
const { scripts, customization } = context;
const { customization } = context;
// Scripts are disabled when tracking is disabled
const scripts = withTracking ? context.scripts : [];
ReactDOM.preconnect(GITBOOK_API_PUBLIC_URL);
ReactDOM.preconnect(GITBOOK_ICONS_URL);
@@ -8,10 +8,10 @@ import { useScrollPage } from '@/components/hooks';
/**
* Client component to initialize interactivity for a page.
*/
export function PageClientLayout(props: { withSections?: boolean }) {
export function PageClientLayout() {
// We use this hook in the page layout to ensure the elements for the blocks
// are rendered before we scroll to a hash or to the top of the page
useScrollPage({ scrollMarginTop: props.withSections ? 108 : 64 });
useScrollPage();
useStripFallbackQueryParam();
return null;
@@ -11,6 +11,7 @@ import { getPagePath } from '@/lib/pages';
import { isPageIndexable, isSiteIndexable } from '@/lib/seo';
import { getResizedImageURL } from '@/lib/images';
import { tcls } from '@/lib/tailwind';
import { PageContextProvider } from '../PageContext';
import { PageClientLayout } from './PageClientLayout';
import { type PagePathParams, fetchPageData, getPathnameParam } from './fetch';
@@ -70,7 +71,14 @@ export async function SitePage(props: SitePageProps) {
<PageCover as="full" page={page} cover={page.cover} context={context} />
) : null}
{/* We use a flex row reverse to render the aside first because the page is streamed. */}
<div className="flex grow flex-row-reverse justify-end">
<div
className={tcls(
'flex grow flex-row-reverse justify-end',
withSections
? '[--content-scroll-margin:calc(var(--spacing)*27)]'
: '[--content-scroll-margin:calc(var(--spacing)*16)]'
)}
>
<PageAside
page={page}
document={document}
@@ -88,7 +96,7 @@ export async function SitePage(props: SitePageProps) {
/>
</div>
<React.Suspense fallback={null}>
<PageClientLayout withSections={withSections} />
<PageClientLayout />
</React.Suspense>
</PageContextProvider>
);
@@ -32,7 +32,7 @@ export function SiteSectionList(props: { sections: ClientSiteSections; className
<nav
aria-label="Sections"
className={tcls(
'-mx-5 before:contents[] relative border-tint-subtle border-b from-transparent sidebar-filled:to-tint-subtle theme-muted:to-tint-subtle to-tint-base text-sm text-tint before:pointer-events-none before:absolute before:right-2 before:bottom-0 before:left-0 before:h-12 before:bg-gradient-to-b [html.sidebar-filled.theme-bold.tint_&]:to-tint-base [html.sidebar-filled.theme-bold.tint_&]:to-tint-subtle [html.sidebar-filled.theme-muted_&]:to-tint-base',
'-mx-5 before:contents[] relative border-tint-subtle border-b from-transparent sidebar-filled:to-tint-subtle theme-muted:to-tint-subtle to-tint-base text-sm text-tint before:pointer-events-none before:absolute before:right-2 before:bottom-0 before:left-0 before:h-12 before:bg-linear-to-b [html.sidebar-filled.theme-bold.tint_&]:to-tint-base [html.sidebar-filled.theme-bold.tint_&]:to-tint-subtle [html.sidebar-filled.theme-muted_&]:to-tint-base',
className
)}
>
@@ -108,7 +108,7 @@ export function SiteSectionListItem(props: {
>
<div
className={tcls(
'flex size-8 shrink-0 items-center justify-center rounded-md straight-corners:rounded-none bg-tint-subtle text-lg text-tint leading-none shadow-sm shadow-tint ring-1 ring-tint-subtle transition-transform group-hover/section-link:scale-110 group-hover/section-link:ring-tint-hover group-active/section-link:scale-90 group-active/section-link:shadow-none contrast-more:text-tint-strong dark:shadow-none',
'flex size-8 shrink-0 items-center justify-center rounded-md straight-corners:rounded-none bg-tint-subtle text-lg text-tint leading-none shadow-tint shadow-xs ring-1 ring-tint-subtle transition-transform group-hover/section-link:scale-110 group-hover/section-link:ring-tint-hover group-active/section-link:scale-90 group-active/section-link:shadow-none contrast-more:text-tint-strong dark:shadow-none',
isActive
? 'bg-primary tint:bg-primary-solid text-primary-subtle tint:text-contrast-primary-solid shadow-md shadow-primary ring-primary group-hover/section-link:ring-primary-hover, contrast-more:text-primary contrast-more:ring-2 contrast-more:ring-primary'
: null
@@ -162,7 +162,7 @@ export function SiteSectionGroupItem(props: {
>
<div
className={tcls(
'flex size-8 shrink-0 items-center justify-center rounded-md straight-corners:rounded-none bg-tint-subtle text-lg text-tint leading-none shadow-sm shadow-tint ring-1 ring-tint-subtle transition-transform group-hover/section-link:scale-110 group-hover/section-link:ring-tint-hover group-active/section-link:scale-90 group-active/section-link:shadow-none contrast-more:text-tint-strong dark:shadow-none',
'flex size-8 shrink-0 items-center justify-center rounded-md straight-corners:rounded-none bg-tint-subtle text-lg text-tint leading-none shadow-tint shadow-xs ring-1 ring-tint-subtle transition-transform group-hover/section-link:scale-110 group-hover/section-link:ring-tint-hover group-active/section-link:scale-90 group-active/section-link:shadow-none contrast-more:text-tint-strong dark:shadow-none',
isActiveGroup
? 'bg-primary tint:bg-primary-solid text-primary tint:text-contrast-primary-solid shadow-md shadow-primary ring-primary group-hover/section-link:ring-primary-hover, contrast-more:text-primary-strong contrast-more:ring-2 contrast-more:ring-primary'
: null
@@ -183,7 +183,7 @@ export function SiteSectionGroupItem(props: {
'group',
'relative',
'rounded-full',
'straight-corners:rounded-sm',
'straight-corners:rounded-xs',
'w-5',
'h-5',
'after:grid-area-1-1',
@@ -202,10 +202,10 @@ export function SiteSectionGroupItem(props: {
icon="chevron-right"
className={tcls(
'grid',
'flex-shrink-0',
'shrink-0',
'size-3',
'm-1',
'transition-[opacity]',
'transition-opacity',
'text-current',
'transition-transform',
'opacity-6',
@@ -96,7 +96,7 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
icon={icon as IconName}
/>
</NavigationMenu.Trigger>
<NavigationMenu.Content className="absolute top-0 left-0 z-20 w-full data-[motion=from-end]:motion-safe:animate-enterFromRight data-[motion=from-start]:motion-safe:animate-enterFromLeft data-[motion=to-end]:motion-safe:animate-exitToRight data-[motion=to-start]:motion-safe:animate-exitToLeft md:w-max">
<NavigationMenu.Content className="absolute top-0 left-0 z-20 w-full motion-safe:data-[motion=from-end]:animate-enter-from-right motion-safe:data-[motion=from-start]:animate-enter-from-left motion-safe:data-[motion=to-end]:animate-exit-to-right motion-safe:data-[motion=to-start]:animate-exit-to-left md:w-max">
<SectionGroupTileList
sections={sectionOrGroup.sections}
currentSection={currentSection}
@@ -118,7 +118,7 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
);
})}
<NavigationMenu.Indicator
className="fixed top-full z-50 flex h-3 items-end justify-center duration-150 motion-safe:transition-[width,_transform] data-[state=hidden]:motion-safe:animate-fadeOut data-[state=visible]:motion-safe:animate-fadeIn"
className="fixed top-full z-50 flex h-3 items-end justify-center duration-150 motion-safe:transition-[width,transform] motion-safe:data-[state=hidden]:animate-fade-out motion-safe:data-[state=visible]:animate-fade-in"
aria-hidden
>
<div className="relative top-1/2 size-3 rotate-45 rounded-tl-sm border-tint-subtle border-t border-l bg-tint-base" />
@@ -133,7 +133,7 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
}}
>
<NavigationMenu.Viewport
className="relative mt-3 h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100vw_-_2rem)] origin-[top_center] overflow-hidden rounded-lg straight-corners:rounded-sm bg-tint-base depth-flat:shadow-none shadow-lg shadow-tint-10/6 ring-1 ring-tint-subtle duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,_height,_transform] data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn md:mx-0 md:w-[var(--radix-navigation-menu-viewport-width)] dark:shadow-tint-1/6"
className="relative mt-3 h-(--radix-navigation-menu-viewport-height) w-[calc(100vw-2rem)] origin-[top_center] overflow-hidden rounded-lg straight-corners:rounded-xs bg-tint-base depth-flat:shadow-none shadow-lg shadow-tint-10/6 ring-1 ring-tint-subtle duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,height,transform] motion-safe:data-[state=closed]:animate-scale-out motion-safe:data-[state=open]:animate-scale-in md:mx-0 md:w-(--radix-navigation-menu-viewport-width) dark:shadow-tint-1/6"
style={{
translate:
undefined /* don't move this to a Tailwind class as Radix renders viewport incorrectly for a few frames */,
@@ -157,7 +157,7 @@ const SectionTab = React.forwardRef(function SectionTab(
ref={ref}
{...rest}
className={tcls(
'group relative my-2 flex select-none items-center justify-between rounded circular-corners:rounded-full straight-corners:rounded-none px-3 py-1',
'group relative my-2 flex select-none items-center justify-between circular-corners:rounded-full rounded-corners:rounded-xs px-3 py-1',
isActive
? 'text-primary-subtle'
: 'text-tint hover:bg-tint-hover hover:text-tint-strong'
@@ -186,7 +186,7 @@ const SectionGroupTab = React.forwardRef(function SectionGroupTab(
ref={ref}
{...rest}
className={tcls(
'group relative my-2 flex select-none items-center justify-between rounded circular-corners:rounded-full straight-corners:rounded-none px-3 py-1 transition-colors hover:cursor-default',
'group relative my-2 flex select-none items-center justify-between circular-corners:rounded-full rounded-sm straight-corners:rounded-none px-3 py-1 transition-colors hover:cursor-default',
isActive
? 'text-primary-subtle'
: 'text-tint hover:bg-tint-hover hover:text-tint-strong'
@@ -252,7 +252,7 @@ function SectionGroupTile(props: { section: ClientSiteSection; isActive: boolean
<Link
href={url}
className={tcls(
'flex w-full select-none flex-col gap-1 rounded straight-corners:rounded-none px-3 py-2 transition-colors hover:bg-tint-hover',
'flex w-full select-none flex-col gap-1 rounded-sm straight-corners:rounded-none px-3 py-2 transition-colors hover:bg-tint-hover',
isActive ? 'text-primary' : 'text-tint-strong'
)}
>
@@ -58,6 +58,7 @@ export function SpaceLayout(props: {
aiMode={aiMode}
isMultiVariants={siteSpaces.length > 1}
spaceTitle={siteSpace.title}
spaceId={siteSpace.space.id}
/>
</React.Suspense>
{aiMode === CustomizationAIMode.Assistant ? (
@@ -144,7 +145,7 @@ export function SpaceLayout(props: {
className={tcls(
'w-full',
'page-no-toc:hidden',
'site-header-none:page-no-toc:flex'
'page-no-toc:site-header-none:flex'
)}
/>
)}
@@ -14,11 +14,11 @@ export function PageGroupItem(props: { page: ClientTOCPageGroup }) {
<li className="group/page-group-item flex flex-col">
<div
className={tcls(
'-top-5 group-first/page-group-item:-mt-5 sticky z-[1] flex items-center gap-3 px-3 pt-6',
'-top-5 group-first/page-group-item:-mt-5 sticky z-1 flex items-center gap-3 px-3 pt-6',
'font-semibold text-xs uppercase tracking-wide',
'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-image: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.
'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',
'sidebar-filled:bg-tint-subtle',
'theme-muted:bg-tint-subtle',
@@ -13,14 +13,14 @@ export function TOCPageIcon({ page }: { page: Pick<RevisionPage, 'emoji' | 'icon
page={page}
style={tcls(
'text-base',
'[.toclink_&]:text-tint-strong/6',
'in-[.toclink]:text-tint-strong/6',
'group-aria-current-page/toclink:text-primary-subtle',
'contrast-more:group-aria-current-page/toclink:text-primary',
!page.emoji
? [
'theme-gradient:bg-gradient-to-b',
'theme-bold:bg-gradient-to-b',
'theme-gradient:bg-linear-to-b',
'theme-bold:bg-linear-to-b',
'theme-gradient:bg-fixed',
'theme-bold:bg-fixed',
@@ -29,7 +29,7 @@ export function TOCPageIcon({ page }: { page: Pick<RevisionPage, 'emoji' | 'icon
'tint:from-tint-7',
'tint:to-tint-10',
'group-aria-current-page/toclink:bg-none',
'group-aria-current-page/toclink:bg-none!',
]
: '',
@@ -33,41 +33,42 @@ export async function TableOfContents(props: {
'shrink-0',
'basis-full',
'lg:basis-72',
'page-no-toc:lg:basis-56',
'lg:page-no-toc:basis-56',
'relative',
'z-[1]',
'z-1',
'lg:sticky',
'lg:mr-12',
// Server-side static positioning
'lg:top-0',
'lg:h-screen',
'announcement:lg:h-[calc(100vh-4.25rem)]',
'lg:announcement:h-[calc(100vh-4.25rem)]',
'site-header:lg:top-16',
'site-header:lg:h-[calc(100vh-4rem)]',
'announcement:site-header:lg:h-[calc(100vh-4rem-4.25rem)]',
// With header
'lg:site-header:top-16',
'lg:site-header:h-[calc(100vh-4rem)]',
'lg:announcement:site-header:h-[calc(100vh-4rem-4.25rem)]',
'site-header-sections:lg:top-[6.75rem]',
'site-header-sections:lg:h-[calc(100vh-6.75rem)]',
'announcement:site-header-sections:lg:h-[calc(100vh-6.75rem-4.25rem)]',
'lg:site-header-sections:top-27',
'lg:site-header-sections:h-[calc(100vh-6.75rem)]',
'lg:site-header-sections:announcement:h-[calc(100vh-6.75rem-4.25rem)]',
// Client-side dynamic positioning (CSS vars applied by script)
'[html[style*="--toc-top-offset"]_&]:lg:!top-[var(--toc-top-offset)]',
'[html[style*="--toc-height"]_&]:lg:!h-[var(--toc-height)]',
'lg:[html[style*="--toc-top-offset"]_&]:top-(--toc-top-offset)!',
'lg:[html[style*="--toc-height"]_&]:h-(--toc-height)!',
'pt-6',
'pb-4',
'sidebar-filled:lg:pr-6',
'page-no-toc:lg:pr-0',
'lg:sidebar-filled:pr-6',
'lg:page-no-toc:pr-0',
'hidden',
'navigation-open:!flex',
'navigation-open:flex!',
'lg:flex',
'page-no-toc:lg:hidden',
'page-no-toc:xl:flex',
'site-header-none:page-no-toc:lg:flex',
'lg:page-no-toc:hidden',
'xl:page-no-toc:flex',
'lg:site-header-none:page-no-toc:flex',
'flex-col',
'gap-4',
@@ -79,7 +80,7 @@ export async function TableOfContents(props: {
<div // The actual sidebar, either shown with a filled bg or transparent.
className={tcls(
'lg:-ms-5',
'relative flex flex-grow flex-col overflow-hidden border-tint-subtle',
'relative flex grow flex-col overflow-hidden border-tint-subtle',
'sidebar-filled:bg-tint-subtle',
'theme-muted:bg-tint-subtle',
@@ -87,25 +88,25 @@ 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',
'page-no-toc:!bg-transparent',
'page-no-toc:!border-none',
'page-no-toc:bg-transparent!',
'page-no-toc:border-none!',
'sidebar-filled:rounded-xl',
'straight-corners:rounded-none',
'[html.sidebar-filled.circular-corners_&]:page-has-toc:rounded-3xl'
'page-has-toc:[html.sidebar-filled.circular-corners_&]:rounded-3xl'
)}
>
{innerHeader && <div className="px-5 *:my-4">{innerHeader}</div>}
<TOCScrollContainer // The scrollview inside the sidebar
className={tcls(
'flex flex-grow flex-col p-2',
'flex grow flex-col p-2',
customization.trademark.enabled && 'lg:pb-20',
'lg:gutter-stable overflow-y-auto',
'[&::-webkit-scrollbar]:bg-transparent',
'[&::-webkit-scrollbar-thumb]:bg-transparent',
'group-hover:[&::-webkit-scrollbar]:bg-tint-subtle',
'group-hover:[&::-webkit-scrollbar-thumb]:bg-tint-7',
'group-hover:[&::-webkit-scrollbar-thumb:hover]:bg-tint-8'
'[&::-webkit-scrollbar]:group-hover:bg-tint-subtle',
'[&::-webkit-scrollbar-thumb]:group-hover:bg-tint-7',
'[&::-webkit-scrollbar-thumb:hover]:group-hover:bg-tint-8'
)}
>
<PagesList
@@ -145,7 +145,7 @@ function Toggler(props: {
'group',
'relative',
'rounded-full',
'straight-corners:rounded-sm',
'straight-corners:rounded-xs',
'w-5',
'h-5',
'after:grid-area-1-1',
@@ -168,7 +168,7 @@ function Toggler(props: {
<Icon
icon="chevron-right"
className={tcls(
'm-1 grid size-3 flex-shrink-0 text-current opacity-6 transition',
'm-1 grid size-3 shrink-0 text-current opacity-6 transition',
'group-hover:opacity-11 contrast-more:opacity-11',
isOpen ? 'rotate-90' : 'rotate-0'
)}
@@ -22,7 +22,7 @@ export function Trademark(props: {
<div
className={tcls(
'relative',
'z-[2]',
'z-2',
'lg:absolute',
'left-0',
@@ -51,7 +51,7 @@ export function Trademark(props: {
'before:inset-x-0',
'before:bottom-full',
'before:h-8',
'before:bg-gradient-to-b',
'before:bg-linear-to-b',
'before:from-transparent',
'before:to-tint-base',
'sidebar-filled:before:to-tint-subtle',
@@ -59,7 +59,7 @@ export function Trademark(props: {
'[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'
'page-no-toc:before:to-transparent!'
)}
>
<TrademarkLink {...props} />
@@ -89,9 +89,9 @@ export function TrademarkLink(props: {
href={url.toString()}
className={tcls(
'text-sm',
// 'page-no-toc:lg:max-xl:text-xs',
// 'page-no-toc:lg:max-xl:px-3',
// 'page-no-toc:lg:max-xl:py-3',
// 'lg:max-xl:page-no-toc:text-xs',
// 'lg:max-xl:page-no-toc:px-3',
// 'lg:max-xl:page-no-toc:py-3',
'font-semibold',
'text-tint',
@@ -102,7 +102,7 @@ export function TrademarkLink(props: {
'py-4',
'sidebar-filled:px-3',
'sidebar-filled:page-no-toc:lg:px-5',
'lg:sidebar-filled:page-no-toc:px-5',
'rounded-lg',
'straight-corners:rounded-none',
@@ -128,14 +128,14 @@ export function TrademarkLink(props: {
icon="gitbook"
className={tcls(
'size-5',
// 'page-no-toc:lg:max-xl:size-4',
// 'lg:max-xl:page-no-toc:size-4',
'shrink-0'
)}
/>
<span
className={tcls(
'ml-3'
// 'page-no-toc:lg:max-xl:ml-2'
// 'lg:max-xl:page-no-toc:ml-2'
)}
>
{t(language, 'powered_by_gitbook')}
@@ -23,9 +23,9 @@ export const ToggleableLinkItemStyles = [
'flex flex-row justify-between',
'circular-corners:rounded-2xl rounded-md straight-corners:rounded-none p-1.5 pl-3',
'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong',
'hover:contrast-more:text-tint-strong hover:contrast-more:ring-1 hover:contrast-more:ring-tint-12',
'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 sidebar-list-default:[&+div_a]:rounded-l-none [&+div_a]:pl-5 sidebar-list-default:[&+div_a]: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 = [
@@ -45,7 +45,7 @@ export const ToggleableLinkItemActiveStyles = [
'hover:bg-primary-hover',
'hover:text-primary',
'hover:before:bg-primary-solid-hover',
'sidebar-list-pill:hover:bg-primary-hover',
'hover:sidebar-list-pill:bg-primary-hover',
'contrast-more:text-primary',
'contrast-more:hover:text-primary-strong',
@@ -67,7 +67,7 @@ function ThemeButton(props: {
aria-checked={active}
className={tcls(
'p-2',
'rounded',
'rounded-sm',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
'transition-all',
@@ -10,16 +10,13 @@ import { useHash } from './useHash';
* to the top of the page when navigating between pages (pathname)
* or sections of a page (hash).
*/
export function useScrollPage(props: { scrollMarginTop?: number }) {
export function useScrollPage() {
const hash = useHash();
const pathname = usePathname();
React.useLayoutEffect(() => {
if (hash) {
const element = document.getElementById(hash);
if (element) {
if (props.scrollMarginTop) {
element.style.scrollMarginTop = `${props.scrollMarginTop}px`;
}
element.scrollIntoView({
block: 'start',
behavior: 'smooth',
@@ -36,5 +33,5 @@ export function useScrollPage(props: { scrollMarginTop?: number }) {
}
}
};
}, [hash, pathname, props.scrollMarginTop]);
}, [hash, pathname]);
}
@@ -30,7 +30,7 @@ export const variantClasses = {
'hover:bg-primary-solid-hover',
'hover:text-contrast-primary-solid-hover',
'border-0',
'contrast-more:border-1',
'contrast-more:border',
'disabled:bg-tint',
'disabled:text-tint/8',
],
@@ -38,12 +38,11 @@ export const variantClasses = {
'bg-transparent',
'text-tint',
'border-0',
'!shadow-none',
'shadow-none!',
'hover:bg-primary-hover',
'hover:text-primary-strong',
'hover:scale-1',
'contrast-more:bg-tint-subtle',
'depth-subtle:hover:translate-y-0',
'hover:depth-subtle:translate-y-0',
'disabled:text-tint/8',
'disabled:bg-transparent',
],
@@ -52,7 +51,7 @@ export const variantClasses = {
'depth-flat:bg-transparent',
'text-tint',
'hover:bg-tint-hover',
'depth-flat:hover:bg-tint-hover',
'hover:depth-flat:bg-tint-hover',
'hover:text-primary',
'contrast-more:bg-tint-subtle',
'disabled:bg-transparent',
@@ -60,22 +59,22 @@ export const variantClasses = {
],
header: [
'bg-tint-base text-tint',
'theme-clean:hover:bg-tint-subtle',
'hover:theme-clean:bg-tint-subtle',
'theme-bold:bg-header-link/2',
'theme-bold:text-header-link',
'theme-bold:!shadow-none',
'theme-bold:shadow-none!',
'theme-bold:border-header-link/4',
'theme-bold:hover:bg-header-link/3',
'theme-bold:hover:text-header-link',
'theme-bold:hover:shadow-none',
'theme-bold:hover:border-header-link/5',
'hover:theme-bold:bg-header-link/3',
'hover:theme-bold:text-header-link',
'hover:theme-bold:shadow-none',
'hover:theme-bold:border-header-link/5',
'theme-bold:contrast-more:bg-header-background',
'theme-bold:contrast-more:text-header-link',
'theme-bold:contrast-more:border-header-link',
'theme-bold:contrast-more:hover:border-header-link',
'contrast-more:theme-bold:bg-header-background',
'contrast-more:theme-bold:text-header-link',
'contrast-more:theme-bold:border-header-link',
'contrast-more:hover:theme-bold:border-header-link',
],
};
@@ -111,7 +110,7 @@ export const Button = React.forwardRef<
ref
) => {
const sizes = {
default: ['text-base', 'font-semibold', 'px-5', 'py-2'],
default: ['text-base', 'font-semibold', iconOnly ? 'px-2' : 'px-5', 'py-2'],
medium: ['text-sm', iconOnly ? 'px-2' : 'px-3.5', 'py-1.5'],
small: ['text-xs', 'py-2', iconOnly ? 'px-2' : 'px-3'],
xsmall: ['text-xs', 'py-1', iconOnly ? 'px-1.5' : 'px-2'],
@@ -191,7 +190,7 @@ export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonProps>(({ chil
<div
ref={ref}
className={tcls(
'*:!transform-none *:!shadow-none flex h-fit items-stretch justify-start',
'flex h-fit items-stretch justify-start *:translate-y-0! *:shadow-none!',
'[&>*:not(:first-child)]:border-l-0 [&>*:not(:first-child,:last-child)]:rounded-none [&>*:not(:only-child):first-child]:rounded-r-none [&>*:not(:only-child):last-child]:rounded-l-none'
)}
>
@@ -23,7 +23,7 @@ export const Checkbox = React.forwardRef<
className={tcls(
'peer',
'shrink-0',
'rounded-sm',
'rounded-xs',
'straight-corners:rounded-none',
'ring-1',
'ring-tint-12/4',
@@ -82,7 +82,7 @@ export function DropdownMenu(props: {
onMouseLeave={() => setHovered(false)}
align={align}
side={side}
className="z-40 animate-scaleIn border-tint pt-2"
className="z-40 animate-scale-in border-tint pt-2"
>
<div
className={tcls(
@@ -153,11 +153,11 @@ export function DropdownMenuItem(
const { children, active = false, href, className, insights, target, ...rest } = props;
const itemClassName = tcls(
'rounded straight-corners:rounded-sm circular-corners:rounded-lg px-3 py-1 text-sm flex gap-2 items-center',
'rounded-xs straight-corners:rounded-xs circular-corners:rounded-lg px-3 py-1 text-sm flex gap-2 items-center',
active
? 'bg-primary text-primary-strong data-[highlighted]:bg-primary-hover'
: 'data-[highlighted]:bg-tint-hover',
'focus:outline-none',
? 'bg-primary text-primary-strong data-highlighted:bg-primary-hover'
: 'data-highlighted:bg-tint-hover',
'focus:outline-hidden',
props.disabled ? 'opacity-7 cursor-not-allowed' : 'cursor-pointer',
className
);
@@ -184,7 +184,7 @@ export function DropdownSubMenu(props: { children: React.ReactNode; label: React
return (
<RadixDropdownMenu.Sub>
<RadixDropdownMenu.SubTrigger className="flex cursor-pointer items-center justify-between rounded straight-corners:rounded-sm px-3 py-1 text-sm focus:outline-none data-[highlighted]:bg-tint-hover">
<RadixDropdownMenu.SubTrigger className="flex cursor-pointer items-center justify-between rounded-sm straight-corners:rounded-xs px-3 py-1 text-sm focus:outline-hidden data-highlighted:bg-tint-hover">
{label}
<Icon icon="chevron-right" className="size-3 shrink-0 opacity-6" />
</RadixDropdownMenu.SubTrigger>
@@ -0,0 +1,32 @@
'use client';
import { tcls } from '@/lib/tailwind';
import * as Tooltip from '@radix-ui/react-tooltip';
export function HoverCardRoot(props: Tooltip.TooltipProps) {
return <Tooltip.Root delayDuration={200} {...props} />;
}
export function HoverCardTrigger(props: { children: React.ReactNode }) {
return <Tooltip.Trigger asChild>{props.children}</Tooltip.Trigger>;
}
export function HoverCard(props: {
children: React.ReactNode;
className?: string;
}) {
return (
<Tooltip.Portal>
<Tooltip.Content className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto">
<div
className={tcls(
'overflow-hidden rounded-md straight-corners:rounded-none bg-tint-base shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1',
props.className
)}
>
{props.children}
</div>
<Tooltip.Arrow className="fill-tint-1" />
</Tooltip.Content>
</Tooltip.Portal>
);
}

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