mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b87f9f3ff | |||
| 0af8f181e9 | |||
| 02d370a558 | |||
| d10b9643fa | |||
| c3d8f08a0c | |||
| 56c25587db | |||
| cf4efc7213 | |||
| 1ef71609e7 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Show the theme toggle in the footer whenever the outline column that hosts the other toggle isn't pinned open, so it stays reachable on laptop-sized screens in wide layouts and while the AI chat is open.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Upgrade react-hotkeys-hook to v5 and use its native `useKey` option so keyboard shortcuts match the produced key on non-QWERTY layouts.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Load the OpenAPI, ContentKit, admin toolbar and PDF print stylesheets only on the pages that need them, cutting published pages from 8 render-blocking stylesheets to 3.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix the search field losing focus if it was focused just before the page finished hydrating.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Self-host the default fonts and inline only the families a site uses, instead of shipping the `@font-face` rules for all 23 supported fonts on every page.
|
||||
@@ -171,7 +171,7 @@
|
||||
"quick-lru": "^7.0.1",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"react-hotkeys-hook": "^5.3.3",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
@@ -198,6 +198,7 @@
|
||||
"@argos-ci/playwright": "^7.2.0",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@scalar/api-client-react": "catalog:",
|
||||
"@tailwindcss/cli": "4.1.11",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/jsontoxml": "^1.0.5",
|
||||
@@ -1068,6 +1069,32 @@
|
||||
|
||||
"@panzoom/panzoom": ["@panzoom/panzoom@4.6.1", "", {}, "sha512-ogf/KhHHjj+DYAvHfaf3TXMQ8OE36pJtKpabLlx1OmpjcgtpCvkUoCiNONA8kuVRPGJdLiqMf0n8LRFXj1OyuA=="],
|
||||
|
||||
"@parcel/watcher": ["@parcel/watcher@2.6.0", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.4" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.6.0", "@parcel/watcher-darwin-arm64": "2.6.0", "@parcel/watcher-darwin-x64": "2.6.0", "@parcel/watcher-freebsd-x64": "2.6.0", "@parcel/watcher-linux-arm-glibc": "2.6.0", "@parcel/watcher-linux-arm-musl": "2.6.0", "@parcel/watcher-linux-arm64-glibc": "2.6.0", "@parcel/watcher-linux-arm64-musl": "2.6.0", "@parcel/watcher-linux-x64-glibc": "2.6.0", "@parcel/watcher-linux-x64-musl": "2.6.0", "@parcel/watcher-win32-arm64": "2.6.0", "@parcel/watcher-win32-x64": "2.6.0" } }, "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w=="],
|
||||
|
||||
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.6.0", "", { "os": "android", "cpu": "arm64" }, "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA=="],
|
||||
|
||||
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.6.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw=="],
|
||||
|
||||
"@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.6.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw=="],
|
||||
|
||||
"@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.6.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ=="],
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.6.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg=="],
|
||||
|
||||
"@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.6.0", "", { "os": "linux", "cpu": "arm" }, "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw=="],
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g=="],
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA=="],
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A=="],
|
||||
|
||||
"@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw=="],
|
||||
|
||||
"@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.6.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ=="],
|
||||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.6.0", "", { "os": "win32", "cpu": "x64" }, "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A=="],
|
||||
|
||||
"@phosphor-icons/core": ["@phosphor-icons/core@2.1.1", "", {}, "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
@@ -1316,6 +1343,8 @@
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tailwindcss/cli": ["@tailwindcss/cli@4.1.11", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "enhanced-resolve": "^5.18.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.11" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -1948,7 +1977,7 @@
|
||||
|
||||
"detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
@@ -2654,6 +2683,8 @@
|
||||
|
||||
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"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=="],
|
||||
@@ -2862,7 +2893,7 @@
|
||||
|
||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||
|
||||
"react-hotkeys-hook": ["react-hotkeys-hook@4.5.1", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg=="],
|
||||
"react-hotkeys-hook": ["react-hotkeys-hook@5.3.3", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-aswgyWUnE25hmhzHTfKDmKzsaSE5DJ4LKaU/o6rQSXkDd/1Bh9TfAFQbHkf6fLy11HvlYkp+cDDarGdhmCDhoQ=="],
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
@@ -3460,6 +3491,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.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"@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/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
@@ -3478,6 +3511,8 @@
|
||||
|
||||
"@oxc-transform/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@parcel/watcher/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"@playwright/test/playwright": ["playwright@1.61.1", "", { "dependencies": { "playwright-core": "1.61.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ=="],
|
||||
|
||||
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
@@ -3582,6 +3617,8 @@
|
||||
|
||||
"@tailwindcss/node/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
||||
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -3800,6 +3837,8 @@
|
||||
|
||||
"jsonwebtoken/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
|
||||
|
||||
"lightningcss/detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"lowlight/highlight.js": ["highlight.js@11.9.0", "", {}, "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw=="],
|
||||
|
||||
"mcp-handler/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
@@ -3890,8 +3929,6 @@
|
||||
|
||||
"send/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
|
||||
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"side-channel-list/object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"side-channel-map/get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
@@ -3988,8 +4025,6 @@
|
||||
|
||||
"@argos-ci/core/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="],
|
||||
|
||||
"@argos-ci/core/sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"@argos-ci/core/sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"quick-lru": "^7.0.1",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"react-hotkeys-hook": "^5.3.3",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
@@ -89,37 +89,39 @@
|
||||
"@argos-ci/playwright": "^7.2.0",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@scalar/api-client-react": "catalog:",
|
||||
"@tailwindcss/cli": "4.1.11",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/jsontoxml": "^1.0.5",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/negotiator": "^0.6.4",
|
||||
"@types/node": "^20",
|
||||
"@types/object-hash": "^3.0.6",
|
||||
"@types/parse-cache-control": "^1.0.4",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/rison": "^0.0.9",
|
||||
"@types/negotiator": "^0.6.4",
|
||||
"bun-types": "catalog:",
|
||||
"deepmerge": "^4.3.1",
|
||||
"diff": "^8.0.2",
|
||||
"env-cmd": "^10.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"postcss": "^8",
|
||||
"rss-parser": "^3.13.0",
|
||||
"stylelint": "^16.16.0",
|
||||
"stylelint-no-unsupported-browser-features": "^8.1.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"ts-essentials": "^10.0.1",
|
||||
"typescript": "catalog:",
|
||||
"vercel": "50.37.3",
|
||||
"wrangler": "^4.79.0",
|
||||
"rss-parser": "^3.13.0"
|
||||
"wrangler": "^4.79.0"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "./scripts/generate.sh",
|
||||
"generate:assets": "bun ./scripts/generate-mermaid-runtime.ts && bun ./scripts/generate-scalar-runtime.ts",
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math && rm -rf ./public/~gitbook/static/mermaid && rm -rf ./public/~gitbook/static/scalar",
|
||||
"generate:assets": "bun ./scripts/generate-mermaid-runtime.ts && bun ./scripts/generate-scalar-runtime.ts && bun ./scripts/generate-block-css.ts && bun ./scripts/download-fonts.ts",
|
||||
"generate:fonts": "bun ./scripts/generate-font-faces.ts",
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math && rm -rf ./public/~gitbook/static/mermaid && rm -rf ./public/~gitbook/static/scalar && rm -rf ./public/~gitbook/static/css && rm -rf ./public/~gitbook/static/fonts",
|
||||
"dev": "bun run generate:assets && env-cmd --silent -f ../../.env.local next --webpack",
|
||||
"build": "bun run generate:assets && next build --webpack",
|
||||
"build:local": "bun run generate:assets && GITBOOK_URL=http://localhost:3000 next build --webpack",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { copyFile, mkdir, readdir, rm, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import sourcesData from '../src/fonts/generated/sources.json' with { type: 'json' };
|
||||
import type { FontSourcesData } from '../src/fonts/types';
|
||||
|
||||
const CONCURRENCY = 8;
|
||||
const ATTEMPTS = 3;
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const fontsDir = join(scriptDir, '../src/fonts');
|
||||
const outputDir = join(scriptDir, '../public/~gitbook/static/fonts');
|
||||
|
||||
const { google, local } = sourcesData as FontSourcesData;
|
||||
const sources = new Map<string, string>(Object.entries(local));
|
||||
for (const [googleId, { prefix, files }] of Object.entries(google)) {
|
||||
for (const file of files) {
|
||||
sources.set(`${googleId}/${file}`, `${prefix}/${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Faces move between releases; stale files would otherwise pile up in the deployed assets.
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
for (const entry of await readdir(outputDir, { recursive: true, withFileTypes: true })) {
|
||||
if (entry.isFile()) {
|
||||
const file = relative(outputDir, join(entry.parentPath, entry.name));
|
||||
if (!sources.has(file)) {
|
||||
await rm(join(outputDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pending = [...sources].filter(([file]) => !Bun.file(join(outputDir, file)).size);
|
||||
if (pending.length > 0) {
|
||||
console.log(`Downloading ${pending.length} font files…`);
|
||||
}
|
||||
|
||||
const queue = pending.values();
|
||||
await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
|
||||
|
||||
async function worker() {
|
||||
for (const [file, source] of queue) {
|
||||
const target = join(outputDir, file);
|
||||
await mkdir(dirname(target), { recursive: true });
|
||||
|
||||
if (source.startsWith('./')) {
|
||||
await copyFile(join(fontsDir, source), target);
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(target, await fetchWithRetries(source));
|
||||
}
|
||||
}
|
||||
|
||||
// Google Fonts intermittently refuses connections when a build asks for hundreds of files at once,
|
||||
// and a single miss fails the whole build.
|
||||
async function fetchWithRetries(url: string): Promise<Buffer> {
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
return Buffer.from(await response.arrayBuffer());
|
||||
} catch (error) {
|
||||
if (attempt >= ATTEMPTS) {
|
||||
throw new Error(`Unable to download ${url}: ${error}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { mkdir, rm } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { BLOCK_STYLESHEETS } from '../src/components/DocumentView/blockStylesheets';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const sourceDir = join(scriptDir, '../src');
|
||||
const outputDir = join(scriptDir, '../public/~gitbook/static/css');
|
||||
const tailwindCli = join(
|
||||
dirname(createRequire(import.meta.url).resolve('@tailwindcss/cli/package.json')),
|
||||
'dist/index.mjs'
|
||||
);
|
||||
|
||||
await rm(outputDir, { force: true, recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
for (const [name, source] of Object.entries(BLOCK_STYLESHEETS)) {
|
||||
const result = Bun.spawnSync(
|
||||
[
|
||||
'node',
|
||||
tailwindCli,
|
||||
'--input',
|
||||
join(sourceDir, source),
|
||||
'--output',
|
||||
join(outputDir, `${name}.css`),
|
||||
'--minify',
|
||||
],
|
||||
{ stdout: 'pipe', stderr: 'pipe' }
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Unable to build ${source}: ${result.stderr.toString()}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { basename, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { ABC_FAVORIT, FONT_DEFINITIONS, type FontDefinition } from '../src/fonts/definitions';
|
||||
import type {
|
||||
FontFacesData,
|
||||
FontFallbackFaceData,
|
||||
FontSourcesData,
|
||||
FontVariantData,
|
||||
} from '../src/fonts/types';
|
||||
|
||||
// Google Fonts picks the file format from the user agent — the same modern Chrome `next/font` sends,
|
||||
// so we keep getting compact woff2 (and vector rather than bitmap emoji).
|
||||
const USER_AGENT =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36';
|
||||
|
||||
// The precalculated metrics `next/font/google` uses, so the fallback faces stay identical.
|
||||
const { calculateSizeAdjustValues } = createRequire(import.meta.url)(
|
||||
'next/dist/server/font-utils'
|
||||
) as {
|
||||
calculateSizeAdjustValues: (family: string) => {
|
||||
ascent: string;
|
||||
descent: string;
|
||||
lineGap: string;
|
||||
fallbackFont: string;
|
||||
sizeAdjust: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ResolvedFace = {
|
||||
weight: string;
|
||||
style: string;
|
||||
file: string;
|
||||
source: string;
|
||||
unicodeRange: string;
|
||||
};
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const faces: FontFacesData = {};
|
||||
const sources: FontSourcesData = { google: {}, local: {} };
|
||||
|
||||
for (const [name, definition] of Object.entries(FONT_DEFINITIONS)) {
|
||||
const resolved = definition.googleId
|
||||
? await getGoogleFaces(definition)
|
||||
: await getABCFavoritFaces();
|
||||
|
||||
if (resolved.length === 0) {
|
||||
throw new Error(`No font faces resolved for ${name}`);
|
||||
}
|
||||
|
||||
recordSources(definition, resolved);
|
||||
|
||||
const subsets = [...new Set(resolved.map((face) => face.unicodeRange))];
|
||||
const variants = new Map<string, FontVariantData>();
|
||||
|
||||
for (const face of resolved) {
|
||||
const key = `${face.weight}|${face.style}`;
|
||||
let variant = variants.get(key);
|
||||
if (!variant) {
|
||||
variant = { weight: face.weight, style: face.style, files: [] };
|
||||
variants.set(key, variant);
|
||||
}
|
||||
variant.files[subsets.indexOf(face.unicodeRange)] = face.file;
|
||||
}
|
||||
|
||||
faces[name] = {
|
||||
family: definition.family,
|
||||
variable: definition.variable,
|
||||
fontFamilyValue: [
|
||||
`"${definition.family}"`,
|
||||
...(definition.adjustFallback ? [`"${definition.family} Fallback"`] : []),
|
||||
...definition.fallback,
|
||||
].join(','),
|
||||
subsets,
|
||||
variants: [...variants.values()],
|
||||
fallbackFace: definition.adjustFallback ? getFallbackFace(name, definition.family) : null,
|
||||
...(definition.googleId ? {} : { ascentOverride: ABC_FAVORIT.ascentOverride }),
|
||||
};
|
||||
}
|
||||
|
||||
const generatedDir = join(scriptDir, '../src/fonts/generated');
|
||||
await writeFile(join(generatedDir, 'faces.json'), `${JSON.stringify(faces, null, 4)}\n`);
|
||||
await writeFile(join(generatedDir, 'sources.json'), `${JSON.stringify(sources, null, 4)}\n`);
|
||||
|
||||
/** Google serves every file of a family from one versioned directory, so only the names differ. */
|
||||
function recordSources(definition: FontDefinition, resolved: ResolvedFace[]) {
|
||||
if (!definition.googleId) {
|
||||
for (const face of resolved) {
|
||||
sources.local[face.file] = face.source;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const prefixes = new Set(
|
||||
resolved.map((face) => face.source.slice(0, face.source.lastIndexOf('/')))
|
||||
);
|
||||
if (prefixes.size !== 1) {
|
||||
throw new Error(`${definition.family} spans several Google Fonts directories`);
|
||||
}
|
||||
|
||||
sources.google[definition.googleId] = {
|
||||
prefix: [...prefixes][0] as string,
|
||||
files: [...new Set(resolved.map((face) => basename(face.file)))],
|
||||
};
|
||||
}
|
||||
|
||||
async function getGoogleFaces(definition: FontDefinition): Promise<ResolvedFace[]> {
|
||||
const { family, googleId, weights } = definition;
|
||||
const url = `https://fonts.googleapis.com/css2?family=${family.replaceAll(' ', '+')}:wght@${weights.join(';')}&display=swap`;
|
||||
|
||||
const response = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to fetch ${family} from Google Fonts: ${response.status} (${url})`);
|
||||
}
|
||||
|
||||
const resolved = [...(await response.text()).matchAll(/@font-face\s*\{([^}]*)\}/g)].map(
|
||||
(match) => {
|
||||
const block = match[1] ?? '';
|
||||
const source = read(block, 'src')?.match(/url\((https:[^)]+\.woff2)\)/)?.[1];
|
||||
const weight = read(block, 'font-weight');
|
||||
const unicodeRange = read(block, 'unicode-range');
|
||||
|
||||
if (!source || !weight || !unicodeRange) {
|
||||
throw new Error(`Unexpected @font-face for ${family}: ${block}`);
|
||||
}
|
||||
|
||||
return {
|
||||
weight,
|
||||
style: read(block, 'font-style') ?? 'normal',
|
||||
// Google's filenames are content-addressed, so the asset can stay immutable.
|
||||
file: `${googleId}/${basename(new URL(source).pathname)}`,
|
||||
source,
|
||||
unicodeRange: unicodeRange.toLowerCase(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const missing = weights.filter((weight) => !resolved.some((face) => face.weight === weight));
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Google Fonts returned no ${missing.join('/')} weight for ${family}`);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function getABCFavoritFaces(): Promise<ResolvedFace[]> {
|
||||
return Promise.all(
|
||||
ABC_FAVORIT.sources.map(async (source) => {
|
||||
const path = join(scriptDir, '../src/fonts/ABCFavorit', source.file);
|
||||
const digest = createHash('sha256')
|
||||
.update(await readFile(path))
|
||||
.digest('hex');
|
||||
|
||||
return {
|
||||
weight: source.weight,
|
||||
style: source.style,
|
||||
file: `abcfavorit/${digest.slice(0, 16)}.woff2`,
|
||||
source: `./ABCFavorit/${source.file}`,
|
||||
unicodeRange: '',
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getFallbackFace(name: string, family: string): FontFallbackFaceData {
|
||||
if (name === 'ABCFavorit') {
|
||||
return { family: `${family} Fallback`, local: 'Arial', ...ABC_FAVORIT.fallbackMetrics };
|
||||
}
|
||||
|
||||
const metrics = calculateSizeAdjustValues(family);
|
||||
return {
|
||||
family: `${family} Fallback`,
|
||||
local: metrics.fallbackFont,
|
||||
ascentOverride: `${metrics.ascent}%`,
|
||||
descentOverride: `${metrics.descent}%`,
|
||||
lineGapOverride: `${metrics.lineGap}%`,
|
||||
sizeAdjust: `${metrics.sizeAdjust}%`,
|
||||
};
|
||||
}
|
||||
|
||||
function read(block: string, property: string): string | undefined {
|
||||
return block.match(new RegExp(`${property}\\s*:\\s*([^;]+);`))?.[1]?.trim();
|
||||
}
|
||||
@@ -63,7 +63,9 @@ export function AIChat() {
|
||||
() => {
|
||||
chatController.close();
|
||||
},
|
||||
[]
|
||||
{
|
||||
useKey: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Track the view of the AI chat
|
||||
|
||||
@@ -77,9 +77,9 @@ export function AIChatInput(props: {
|
||||
},
|
||||
{
|
||||
enableOnFormTags: true,
|
||||
// Match the logical character so Dvorak ⌘-C (physical "I" key) copies
|
||||
// Match the produced key so Dvorak ⌘-C (physical "I" key) copies
|
||||
// instead of focusing the Assistant input. RND-11340.
|
||||
ignoreEventWhen: (e) => e.key.toLowerCase() !== 'i',
|
||||
useKey: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AdminToolbarClient } from './AdminToolbarClient';
|
||||
import { AdminToolbarLazy } from './AdminToolbarLazy';
|
||||
import { getToolbarChangedPages } from './changedPages';
|
||||
import type { AdminToolbarContext } from './types';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
@@ -62,5 +62,5 @@ export async function AdminToolbar(props: AdminToolbarProps) {
|
||||
changedPages,
|
||||
};
|
||||
|
||||
return <AdminToolbarClient context={minimalContext} />;
|
||||
return <AdminToolbarLazy context={minimalContext} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import type { AdminToolbarClientProps } from './types';
|
||||
|
||||
// The toolbar only ever appears after hydration (`useToolbarVisibility` reads cookies and local
|
||||
// storage), so nothing is lost by deferring it — and it takes its CSS modules out of the route's
|
||||
// render-blocking stylesheets, where every published page was paying for them.
|
||||
const AdminToolbarClient = dynamic(
|
||||
() => import('./AdminToolbarClient').then((mod) => mod.AdminToolbarClient),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function AdminToolbarLazy(props: AdminToolbarClientProps) {
|
||||
return <AdminToolbarClient {...props} />;
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
// Only the server entrypoint: re-exporting the client modules would pull their CSS modules back
|
||||
// into the route's render-blocking stylesheets, defeating the lazy boundary.
|
||||
export * from './AdminToolbar';
|
||||
export * from './AdminToolbarClient';
|
||||
export * from './IframeWrapper';
|
||||
export * from './Toolbar';
|
||||
export * from './transitions';
|
||||
export * from './utils';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { BlockStylesheetName } from './blockStylesheets';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
import { buildVersion } from '@/lib/build';
|
||||
|
||||
export function getBlockStylesheetURL(name: BlockStylesheetName): string {
|
||||
return getAssetURL(`css/${name}.css?v=${buildVersion()}`);
|
||||
}
|
||||
|
||||
// `href` + `precedence` opt into React's stylesheet hoisting: the tag moves to `<head>`, dedupes
|
||||
// across every block on the page, and holds paint until it loads, so the block never flashes
|
||||
// unstyled. `blocks` sorts after Next's own `next` precedence, keeping the previous cascade order.
|
||||
export function BlockStylesheet(props: { name: BlockStylesheetName }) {
|
||||
return <link rel="stylesheet" href={getBlockStylesheetURL(props.name)} precedence="blocks" />;
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import type { DocumentBlockIntegration, RenderIntegrationUI } from '@gitbook/api
|
||||
import { ContentKit, ContentKitOutput } from '@gitbook/react-contentkit';
|
||||
|
||||
import type { BlockProps } from '../Block';
|
||||
import { BlockStylesheet } from '../BlockStylesheet';
|
||||
import { getWebframePageContext, integrationBlockContainsWebframe } from './adaptive';
|
||||
import { contentKitServerContext } from './contentkit';
|
||||
import './contentkit.css';
|
||||
import {
|
||||
ContentKitWithClientContext,
|
||||
type WebframeLinkerData,
|
||||
@@ -105,6 +105,7 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
|
||||
|
||||
return (
|
||||
<div className={tcls(style)}>
|
||||
<BlockStylesheet name="contentkit" />
|
||||
{useClientContext ? (
|
||||
<ContentKitWithClientContext
|
||||
{...contentKitProps}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BlockProps } from '../Block';
|
||||
import { BlockStylesheet } from '../BlockStylesheet';
|
||||
import { getOpenAPIBlockClientProps } from './context';
|
||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
||||
@@ -11,9 +12,12 @@ import { tcls } from '@/lib/tailwind';
|
||||
export async function OpenAPIOperation(props: BlockProps<AnyOpenAPIOperationsBlock>) {
|
||||
const { style } = props;
|
||||
return (
|
||||
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||
<OpenAPIOperationBody {...props} />
|
||||
</div>
|
||||
<>
|
||||
<BlockStylesheet name="openapi" />
|
||||
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||
<OpenAPIOperationBody {...props} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BlockProps } from '../Block';
|
||||
import { BlockStylesheet } from '../BlockStylesheet';
|
||||
import { getOpenAPIBlockClientProps } from './context';
|
||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
||||
@@ -11,9 +12,12 @@ import { tcls } from '@/lib/tailwind';
|
||||
export async function OpenAPISchemas(props: BlockProps<OpenAPISchemasBlock>) {
|
||||
const { style } = props;
|
||||
return (
|
||||
<div className={tcls('flex w-full', style, 'max-w-full')}>
|
||||
<OpenAPISchemasBody {...props} />
|
||||
</div>
|
||||
<>
|
||||
<BlockStylesheet name="openapi" />
|
||||
<div className={tcls('flex w-full', style, 'max-w-full')}>
|
||||
<OpenAPISchemasBody {...props} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BlockProps } from '../Block';
|
||||
import { BlockStylesheet } from '../BlockStylesheet';
|
||||
import { getOpenAPIBlockClientProps } from './context';
|
||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
|
||||
@@ -11,9 +12,12 @@ import { tcls } from '@/lib/tailwind';
|
||||
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
|
||||
const { style } = props;
|
||||
return (
|
||||
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||
<OpenAPIWebhookBody {...props} />
|
||||
</div>
|
||||
<>
|
||||
<BlockStylesheet name="openapi" />
|
||||
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||
<OpenAPIWebhookBody {...props} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
import { Heading } from '../Heading';
|
||||
import type { OpenAPIBlockClientContextProps } from './OpenAPIBlockClient';
|
||||
import './style.css';
|
||||
import { SCALAR_RUNTIME_PATH } from './scalar-runtime-path';
|
||||
import { DEFAULT_LOCALE, getSpaceLocale } from '@/intl/server';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Built as standalone assets by `scripts/generate-block-css.ts` rather than imported: an import
|
||||
// would put them in the route's render-blocking CSS on every page, block or no block.
|
||||
// Values are the sources to compile, relative to `src/`.
|
||||
export const BLOCK_STYLESHEETS = {
|
||||
openapi: 'components/DocumentView/OpenAPI/style.css',
|
||||
contentkit: 'components/DocumentView/Integration/contentkit.css',
|
||||
} as const;
|
||||
|
||||
export type BlockStylesheetName = keyof typeof BLOCK_STYLESHEETS;
|
||||
@@ -30,8 +30,11 @@ export function Footer(props: { context: GitBookSiteContext }) {
|
||||
data-gb-site-footer
|
||||
className={tcls(
|
||||
'border-tint-subtle border-t',
|
||||
// If the footer only contains a mode toggle, we only show it on smaller screens
|
||||
mobileOnly ? 'xl:hidden' : null
|
||||
// If the footer only contains a mode toggle, we only show it where the outline
|
||||
// column — which carries its own toggle — isn't pinned open.
|
||||
mobileOnly
|
||||
? 'layout-default:xl:chat-closed:hidden layout-default:3xl:hidden layout-wide:3xl:chat-closed:hidden page-api-block:page-has-outline:min-[96rem]:hidden'
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<div className="lg:chat-open:pr-(--ai-chat-width) transition-[padding] duration-300 motion-reduce:transition-none">
|
||||
@@ -96,7 +99,9 @@ export function Footer(props: { context: GitBookSiteContext }) {
|
||||
{
|
||||
// Theme Toggle
|
||||
customization.themes.toggeable ? (
|
||||
<div className="-col-start-2 row-start-1 flex items-start @xs:justify-end xl:hidden">
|
||||
// Hidden where the outline column is pinned open (see PageAside),
|
||||
// since that column carries its own toggle.
|
||||
<div className="page-api-block:page-has-outline:min-[96rem]:hidden -col-start-2 row-start-1 flex items-start @xs:justify-end layout-default:xl:chat-closed:hidden layout-default:3xl:hidden layout-wide:3xl:chat-closed:hidden">
|
||||
<React.Suspense fallback={null}>
|
||||
<ThemeToggler />
|
||||
</React.Suspense>
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from '../primitives/DropdownMenu';
|
||||
import { SiteAuthLoginDropdownMenuItem } from '../SiteAuth/SiteAuthLoginLink';
|
||||
import { HeaderLinkMoreDropdown } from './HeaderLinkMoreClient';
|
||||
import styles from './headerLinks.module.css';
|
||||
import { isSiteAuthLoginHref } from '@/lib/auth-login-link';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
@@ -34,7 +33,7 @@ export function HeaderLinkMore(props: {
|
||||
const { label, links, context, socialAccounts } = props;
|
||||
|
||||
return (
|
||||
<div className={`${styles.linkEllipsis} z-20 items-center`}>
|
||||
<div className="header-links-ellipsis z-20 items-center">
|
||||
<HeaderLinkMoreDropdown
|
||||
label={label}
|
||||
dropdownClassName={tcls(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type React from 'react';
|
||||
|
||||
import styles from './headerLinks.module.css';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
interface HeaderLinksProps {
|
||||
@@ -12,7 +11,7 @@ export async function HeaderLinks({ children, style }: HeaderLinksProps) {
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
styles.containerHeaderlinks,
|
||||
'header-links',
|
||||
'@4xl:[&>.button+.button]:-ml-2 z-20 ml-auto flex min-w-9 shrink grow @7xl:grow-0 items-center justify-end @4xl:gap-x-6 gap-x-4',
|
||||
style
|
||||
)}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
@container header (width < 1280px) {
|
||||
.containerHeaderlinks {
|
||||
container-type: inline-size;
|
||||
container-name: headerlinks;
|
||||
}
|
||||
}
|
||||
|
||||
.linkEllipsis {
|
||||
display: none;
|
||||
& div > a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container headerlinks (width < 150px) {
|
||||
.containerHeaderlinks > :nth-child(n + 1) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 1) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 1) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 300px) {
|
||||
.containerHeaderlinks > :nth-child(n + 2) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 2) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 2) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 450px) {
|
||||
.containerHeaderlinks > :nth-child(n + 3) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 3) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 3) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 550px) {
|
||||
.containerHeaderlinks > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 4) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 4) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 650px) {
|
||||
.containerHeaderlinks > :nth-child(n + 5) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 5) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 5) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 750px) {
|
||||
.containerHeaderlinks > :nth-child(n + 6) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 6) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 6) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import { sanitizeGitBookAppURL } from '@/lib/app';
|
||||
import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context';
|
||||
import { getPageDocument } from '@/lib/data';
|
||||
import { type GitBookLinker, createLinker, linkerWithAbsoluteURLs } from '@/lib/links';
|
||||
import './pdf.css';
|
||||
import { resolvePageId } from '@/lib/pages';
|
||||
import { getLinkerForSiteSpace } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CustomizationDefaultThemeMode } from '@gitbook/api';
|
||||
|
||||
import { PRINT_STYLES } from './printStyles';
|
||||
import { CustomizationRootLayout } from '@/components/RootLayout';
|
||||
import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context';
|
||||
|
||||
@@ -17,6 +18,9 @@ export async function PDFRootLayout(props: {
|
||||
context={context}
|
||||
forcedTheme={CustomizationDefaultThemeMode.Light}
|
||||
>
|
||||
<style href="pdf-print" precedence="blocks">
|
||||
{PRINT_STYLES}
|
||||
</style>
|
||||
{children}
|
||||
</CustomizationRootLayout>
|
||||
);
|
||||
|
||||
+4
@@ -1,3 +1,6 @@
|
||||
// Inlined rather than imported as CSS: a `.css` import lands in the route's render-blocking
|
||||
// stylesheets, and webpack was attaching this one to the content pages too.
|
||||
export const PRINT_STYLES = `
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 60pt 60pt;
|
||||
@@ -56,3 +59,4 @@
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import type { JSONDocument, RevisionPageDocument, SiteInsightsDisplayContext } from '@gitbook/api';
|
||||
|
||||
import { DocumentView, DocumentViewSkeleton } from '../DocumentView';
|
||||
import { getBlockStylesheetURL } from '../DocumentView/BlockStylesheet';
|
||||
import { CurrentPageProvider } from '../hooks/useCurrentPage';
|
||||
import { TrackPageViewEvent } from '../Insights';
|
||||
import { CONTENT_STYLE } from '../layout';
|
||||
@@ -69,6 +72,14 @@ export async function PageBody(props: {
|
||||
// to API-reference pages only (matching the `page-api-block` styling), so other pages keep the
|
||||
// header structure untouched.
|
||||
const hasAPIBlocks = document ? hasAPIBlock(document) : false;
|
||||
|
||||
// The block renders its own stylesheet link, but that only reaches the browser once the block
|
||||
// streams in — start the request from the shell instead. Top-level blocks only, which is the
|
||||
// common case; nested ones still get the sheet from the link.
|
||||
if (hasAPIBlocks) {
|
||||
ReactDOM.preload(getBlockStylesheetURL('openapi'), { as: 'style' });
|
||||
}
|
||||
|
||||
const wideLayout = wideContent || page.layout.width === 'wide';
|
||||
const language = await getSpaceLanguage(context);
|
||||
const updatedAt = page.updatedAt ?? page.createdAt;
|
||||
|
||||
@@ -26,8 +26,12 @@ import { AnnouncementDismissedScript } from '../Announcement';
|
||||
import { SelectStateScript } from '../Select';
|
||||
import { OperatingSystemClassScript } from './OperatingSystemClassScript';
|
||||
import { RootLayoutClientContexts } from './RootLayoutClientContexts';
|
||||
import { type FontData, getFontData } from '@/fonts';
|
||||
import { fontNotoColorEmoji, fonts } from '@/fonts/default';
|
||||
import {
|
||||
DEFAULT_MONOSPACE_FONT,
|
||||
type FontData,
|
||||
generateEmojiFontFacesCSS,
|
||||
getFontData,
|
||||
} from '@/fonts';
|
||||
import './globals.css';
|
||||
import { getContentLocale, getSpaceLanguage } from '@/intl/server';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
@@ -91,12 +95,10 @@ export async function CustomizationRootLayout(props: {
|
||||
const fontData = getFontData(customization.styling.font, 'content');
|
||||
// Temporarily add a if here while the cache is being warmed up.
|
||||
// We can remove the condition after 14-07-2025.
|
||||
const monospaceFontData = customization.styling.monospaceFont
|
||||
? getFontData(customization.styling.monospaceFont, 'mono')
|
||||
: {
|
||||
type: 'default' as const,
|
||||
variable: fonts.IBMPlexMono.variable,
|
||||
};
|
||||
const monospaceFontData = getFontData(
|
||||
customization.styling.monospaceFont ?? DEFAULT_MONOSPACE_FONT,
|
||||
'mono'
|
||||
);
|
||||
|
||||
// Preconnect and preload custom fonts if needed
|
||||
preloadFont(fontData);
|
||||
@@ -127,10 +129,8 @@ export async function CustomizationRootLayout(props: {
|
||||
sidebarStyles.list && `sidebar-list-${sidebarStyles.list}`,
|
||||
'links' in customization.styling && `links-${customization.styling.links}`,
|
||||
'depth' in customization.styling && `depth-${customization.styling.depth}`,
|
||||
fontNotoColorEmoji.variable,
|
||||
monospaceFontData.type === 'default' ? monospaceFontData.variable : null,
|
||||
fontData.type === 'default'
|
||||
? [fontData.variable, `font-${customization.styling.font}`]
|
||||
typeof customization.styling.font === 'string'
|
||||
? `font-${customization.styling.font}`
|
||||
: null,
|
||||
|
||||
// Set the dark/light class statically to avoid flashing and make it work when JS is disabled
|
||||
@@ -150,11 +150,10 @@ export async function CustomizationRootLayout(props: {
|
||||
{/* Apply the visitor's content selection to <html> before first paint (no flash) */}
|
||||
<SelectStateScript />
|
||||
|
||||
{/* Inject custom font @font-face rules */}
|
||||
{fontData.type === 'custom' ? <style>{fontData.fontFaceRules}</style> : null}
|
||||
{monospaceFontData.type === 'custom' ? (
|
||||
<style>{monospaceFontData.fontFaceRules}</style>
|
||||
) : null}
|
||||
{/* Only the picked families, so the head never carries every font we support */}
|
||||
<style>{generateEmojiFontFacesCSS()}</style>
|
||||
<style>{fontData.fontFaceRules}</style>
|
||||
<style>{monospaceFontData.fontFaceRules}</style>
|
||||
|
||||
{/* Inject a script to detect if the announcmeent banner has been dismissed */}
|
||||
{'announcement' in customization && customization.announcement?.enabled ? (
|
||||
|
||||
@@ -552,3 +552,104 @@ html.dark .highlight-line.diff-deleted .highlight-line-content::before {
|
||||
.button-group > :is(button, a):not(:first-child):has(~ :is(button, a)) {
|
||||
@apply rounded-none!;
|
||||
}
|
||||
|
||||
/* Zoomable images */
|
||||
html:has(.zoom-modal) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zoom-img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.zoom-image-active {
|
||||
view-transition-name: zoom-image;
|
||||
}
|
||||
|
||||
.zoom-modal img {
|
||||
view-transition-name: zoom-image;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
/* Header links collapse into the "more" dropdown one by one as the header narrows. Each breakpoint
|
||||
hides one more link and reveals the ellipsis holding the ones that no longer fit. */
|
||||
@container header (width < 1280px) {
|
||||
.header-links {
|
||||
container-type: inline-size;
|
||||
container-name: headerlinks;
|
||||
}
|
||||
}
|
||||
|
||||
.header-links-ellipsis {
|
||||
display: none;
|
||||
& div > a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container headerlinks (width < 150px) {
|
||||
.header-links > :nth-child(n + 1) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 1) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 1) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 300px) {
|
||||
.header-links > :nth-child(n + 2) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 2) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 2) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 450px) {
|
||||
.header-links > :nth-child(n + 3) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 3) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 3) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 550px) {
|
||||
.header-links > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 4) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 4) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 650px) {
|
||||
.header-links > :nth-child(n + 5) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 5) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 5) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 750px) {
|
||||
.header-links > :nth-child(n + 6) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 6) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 6) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +75,9 @@ export function SearchContainer({
|
||||
},
|
||||
{
|
||||
enableOnFormTags: true,
|
||||
// Match the logical character typed, not the physical key position, so
|
||||
// non-QWERTY layouts don't trigger the shortcut by position (e.g. on
|
||||
// Dvorak the physical "K"/"I" keys produce other characters). RND-11340.
|
||||
ignoreEventWhen: (e) => e.key.toLowerCase() !== 'k',
|
||||
// Match the produced key, not the physical key position, so non-QWERTY
|
||||
// layouts don't trigger the shortcut by position. RND-11340.
|
||||
useKey: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -94,9 +93,9 @@ export function SearchContainer({
|
||||
},
|
||||
{
|
||||
enableOnFormTags: true,
|
||||
// Match the logical character so Dvorak ⌘-C (physical "I" key) copies
|
||||
// Match the produced key so Dvorak ⌘-C (physical "I" key) copies
|
||||
// instead of opening the Assistant. RND-11340.
|
||||
ignoreEventWhen: (e) => e.key.toLowerCase() !== 'i',
|
||||
useKey: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -46,11 +46,22 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
} = props;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const isFrame = mode === 'frame';
|
||||
const isInitialMountRef = useRef(true);
|
||||
|
||||
const language = useLanguage();
|
||||
|
||||
useEffect(() => {
|
||||
const isInitialMount = isInitialMountRef.current;
|
||||
isInitialMountRef.current = false;
|
||||
|
||||
if (!isOpen) {
|
||||
// A user can focus the SSR'd input natively before hydration attaches
|
||||
// React's focus handler; syncing to "open" instead of blurring avoids
|
||||
// stealing that focus back on mount.
|
||||
if (isInitialMount && document.activeElement === inputRef.current) {
|
||||
onFocus?.();
|
||||
return;
|
||||
}
|
||||
inputRef.current?.blur();
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +80,7 @@ export const SearchInput = React.forwardRef<HTMLDivElement, SearchInputProps>(
|
||||
|
||||
const timeout = window.setTimeout(focusInput, 150);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [isFrame, isOpen, value.length]);
|
||||
}, [isFrame, isOpen, value.length, onFocus]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
html:has(.zoomModal) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zoomImg {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.zoomImageActive {
|
||||
view-transition-name: zoom-image;
|
||||
}
|
||||
|
||||
.zoomModal {
|
||||
/** Unstyled */
|
||||
}
|
||||
|
||||
.zoomModal img {
|
||||
view-transition-name: zoom-image;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import ReactDOM from 'react-dom';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
|
||||
import styles from './ZoomImage.module.css';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
const PADDING = 32; // Padding around the image in the modal, in pixels
|
||||
@@ -159,8 +158,8 @@ export function ZoomImage(
|
||||
}}
|
||||
className={classNames(
|
||||
props.className,
|
||||
zoomable ? styles.zoomImg : null,
|
||||
active ? styles.zoomImageActive : null
|
||||
zoomable ? 'zoom-img' : null,
|
||||
active ? 'zoom-image-active' : null
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -200,7 +199,7 @@ function ZoomImageModal(props: {
|
||||
<div
|
||||
data-testid="zoom-image-modal"
|
||||
className={classNames(
|
||||
styles.zoomModal,
|
||||
'zoom-modal',
|
||||
tcls(
|
||||
'fixed',
|
||||
'inset-0',
|
||||
|
||||
@@ -1,297 +1,83 @@
|
||||
import {
|
||||
DM_Mono,
|
||||
Fira_Code,
|
||||
Fira_Sans_Extra_Condensed,
|
||||
IBM_Plex_Mono,
|
||||
IBM_Plex_Serif,
|
||||
Inconsolata,
|
||||
Inter,
|
||||
JetBrains_Mono,
|
||||
Lato,
|
||||
Merriweather,
|
||||
Noto_Color_Emoji,
|
||||
Noto_Sans,
|
||||
Open_Sans,
|
||||
Overpass,
|
||||
Poppins,
|
||||
Raleway,
|
||||
Roboto,
|
||||
Roboto_Mono,
|
||||
Roboto_Slab,
|
||||
Source_Code_Pro,
|
||||
Source_Sans_3,
|
||||
Space_Mono,
|
||||
Ubuntu,
|
||||
} from 'next/font/google';
|
||||
import localFont from 'next/font/local';
|
||||
import { CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
|
||||
import { CustomizationDefaultFont, CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
import { EMOJI_FONT, type FontName } from './definitions';
|
||||
import faces from './generated/faces.json';
|
||||
import type { FontFacesData, FontFamilyData } from './types';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
|
||||
export const fontNotoColorEmoji = Noto_Color_Emoji({
|
||||
variable: '--font-noto-color-emoji',
|
||||
weight: ['400'],
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
});
|
||||
const fontFaces = faces as FontFacesData;
|
||||
|
||||
/*
|
||||
Fonts are downloaded and loaded by next/font.
|
||||
// The rules only vary by font, and every page renders a few dozen of them.
|
||||
const cache = new Map<FontName, string>();
|
||||
|
||||
We can't use "preload: true" as otherwise Next will preload all the fonts on the page
|
||||
while spaces only use one font at a time.
|
||||
*/
|
||||
/** Used until the cache has warmed up for sites saved before the setting existed. */
|
||||
export const DEFAULT_MONOSPACE_FONT = CustomizationDefaultMonospaceFont.IBMPlexMono;
|
||||
|
||||
const inter = Inter({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
// Emitted per picked family and inlined in the head, so a page never carries the other 20 families
|
||||
// the way a shared `next/font` stylesheet did.
|
||||
export function generateDefaultFontFacesCSS(font: FontName): string {
|
||||
const cached = cache.get(font);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const firaSans = Fira_Sans_Extra_Condensed({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
const family = fontFaces[font];
|
||||
if (!family) {
|
||||
throw new Error(`Missing generated font faces for ${font}`);
|
||||
}
|
||||
|
||||
const ibmPlexSerif = IBM_Plex_Serif({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['serif'],
|
||||
});
|
||||
const css = [
|
||||
...family.variants.flatMap((variant) =>
|
||||
variant.files.map((file, subset) =>
|
||||
generateFace(family, variant.weight, variant.style, file, family.subsets[subset])
|
||||
)
|
||||
),
|
||||
generateFallbackFace(family),
|
||||
`:root { ${family.variable}: ${family.fontFamilyValue}; }`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
const lato = Lato({
|
||||
weight: ['400', '700', '900'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
cache.set(font, css);
|
||||
return css;
|
||||
}
|
||||
|
||||
const merriweather = Merriweather({
|
||||
weight: ['400', '700', '900'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['serif'],
|
||||
});
|
||||
export function generateEmojiFontFacesCSS(): string {
|
||||
return generateDefaultFontFacesCSS(EMOJI_FONT);
|
||||
}
|
||||
|
||||
const notoSans = Noto_Sans({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
function generateFace(
|
||||
family: FontFamilyData,
|
||||
weight: string,
|
||||
style: string,
|
||||
file: string,
|
||||
unicodeRange: string | undefined
|
||||
): string {
|
||||
return `@font-face {
|
||||
font-family: "${family.family}";
|
||||
font-style: ${style};
|
||||
font-weight: ${weight};
|
||||
font-display: swap;${family.ascentOverride ? `\n ascent-override: ${family.ascentOverride};` : ''}
|
||||
src: url(${getAssetURL(`fonts/${file}`)}) format("woff2");${
|
||||
unicodeRange ? `\n unicode-range: ${unicodeRange};` : ''
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
const openSans = Open_Sans({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
// A `local()` face with the real font's metrics: the page keeps the system font's glyphs but the
|
||||
// picked font's proportions, so swapping it in barely shifts the layout.
|
||||
function generateFallbackFace(family: FontFamilyData): string {
|
||||
const fallback = family.fallbackFace;
|
||||
if (!fallback) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const overpass = Overpass({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const poppins = Poppins({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const raleway = Raleway({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const robotoSlab = Roboto_Slab({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const sourceSansPro = Source_Sans_3({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const ubuntu = Ubuntu({
|
||||
weight: ['400', '500', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const abcFavorit = localFont({
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
src: [
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-Variable.woff2',
|
||||
weight: '400 700',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-BoldItalic.woff2',
|
||||
weight: '700',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-MediumItalic.woff2',
|
||||
weight: '500',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-RegularItalic.woff2',
|
||||
weight: '400',
|
||||
style: 'italic',
|
||||
},
|
||||
],
|
||||
declarations: [{ prop: 'ascent-override', value: '100%' }],
|
||||
});
|
||||
|
||||
const ibmPlexMono = IBM_Plex_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const dmMono = DM_Mono({
|
||||
weight: ['400', '500'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const firaCode = Fira_Code({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const inconsolata = Inconsolata({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const jetBrainsMono = JetBrains_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const robotoMono = Roboto_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const sourceCodePro = Source_Code_Pro({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const spaceMono = Space_Mono({
|
||||
weight: ['400', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Font definitions.
|
||||
*/
|
||||
export const fonts: {
|
||||
[fontName in CustomizationDefaultFont | CustomizationDefaultMonospaceFont]: {
|
||||
variable: string;
|
||||
};
|
||||
} = {
|
||||
[CustomizationDefaultFont.Inter]: inter,
|
||||
[CustomizationDefaultFont.FiraSans]: firaSans,
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: ibmPlexSerif,
|
||||
[CustomizationDefaultFont.Lato]: lato,
|
||||
[CustomizationDefaultFont.Merriweather]: merriweather,
|
||||
[CustomizationDefaultFont.NotoSans]: notoSans,
|
||||
[CustomizationDefaultFont.OpenSans]: openSans,
|
||||
[CustomizationDefaultFont.Overpass]: overpass,
|
||||
[CustomizationDefaultFont.Poppins]: poppins,
|
||||
[CustomizationDefaultFont.Raleway]: raleway,
|
||||
[CustomizationDefaultFont.Roboto]: roboto,
|
||||
[CustomizationDefaultFont.RobotoSlab]: robotoSlab,
|
||||
[CustomizationDefaultFont.SourceSansPro]: sourceSansPro,
|
||||
[CustomizationDefaultFont.Ubuntu]: ubuntu,
|
||||
[CustomizationDefaultFont.ABCFavorit]: abcFavorit,
|
||||
[CustomizationDefaultMonospaceFont.IBMPlexMono]: ibmPlexMono,
|
||||
[CustomizationDefaultMonospaceFont.DMMono]: dmMono,
|
||||
[CustomizationDefaultMonospaceFont.FiraCode]: firaCode,
|
||||
[CustomizationDefaultMonospaceFont.Inconsolata]: inconsolata,
|
||||
[CustomizationDefaultMonospaceFont.JetBrainsMono]: jetBrainsMono,
|
||||
[CustomizationDefaultMonospaceFont.RobotoMono]: robotoMono,
|
||||
[CustomizationDefaultMonospaceFont.SourceCodePro]: sourceCodePro,
|
||||
[CustomizationDefaultMonospaceFont.SpaceMono]: spaceMono,
|
||||
};
|
||||
return `@font-face {
|
||||
font-family: "${fallback.family}";
|
||||
src: local("${fallback.local}");
|
||||
ascent-override: ${fallback.ascentOverride};
|
||||
descent-override: ${fallback.descentOverride};
|
||||
line-gap-override: ${fallback.lineGapOverride};
|
||||
size-adjust: ${fallback.sizeAdjust};
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { CustomizationDefaultFont, CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
|
||||
/** The emoji font is always loaded, alongside whichever content and monospace fonts a site picked. */
|
||||
export const EMOJI_FONT = 'NotoColorEmoji';
|
||||
|
||||
export type FontName =
|
||||
| CustomizationDefaultFont
|
||||
| CustomizationDefaultMonospaceFont
|
||||
| typeof EMOJI_FONT;
|
||||
|
||||
export interface FontDefinition {
|
||||
/** `font-family` to declare the faces under. */
|
||||
family: string;
|
||||
/** Family id on Google Fonts, or `null` for the fonts we ship ourselves. */
|
||||
googleId: string | null;
|
||||
weights: string[];
|
||||
/** CSS variable the family is exposed as. */
|
||||
variable: '--font-content' | '--font-mono' | '--font-noto-color-emoji';
|
||||
/** Families appended after the (metric-adjusted) fallback. */
|
||||
fallback: string[];
|
||||
/** Declare a `<family> Fallback` face with metric overrides, so the swap shifts layout as
|
||||
* little as possible. Monospace fonts opt out, as they did under `next/font`. */
|
||||
adjustFallback: boolean;
|
||||
}
|
||||
|
||||
const content = (
|
||||
family: string,
|
||||
googleId: string,
|
||||
weights: string[],
|
||||
fallback: string[] = ['system-ui', 'arial']
|
||||
): FontDefinition => ({
|
||||
family,
|
||||
googleId,
|
||||
weights,
|
||||
variable: '--font-content',
|
||||
fallback,
|
||||
adjustFallback: true,
|
||||
});
|
||||
|
||||
const mono = (family: string, googleId: string, weights: string[]): FontDefinition => ({
|
||||
family,
|
||||
googleId,
|
||||
weights,
|
||||
variable: '--font-mono',
|
||||
fallback: ['monospace'],
|
||||
adjustFallback: false,
|
||||
});
|
||||
|
||||
export const FONT_DEFINITIONS: Record<FontName, FontDefinition> = {
|
||||
[CustomizationDefaultFont.Inter]: content('Inter', 'inter', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.FiraSans]: content(
|
||||
'Fira Sans Extra Condensed',
|
||||
'fira-sans-extra-condensed',
|
||||
['400', '500', '600', '700']
|
||||
),
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: content(
|
||||
'IBM Plex Serif',
|
||||
'ibm-plex-serif',
|
||||
['400', '500', '600', '700'],
|
||||
['serif']
|
||||
),
|
||||
[CustomizationDefaultFont.Lato]: content('Lato', 'lato', ['400', '700', '900']),
|
||||
[CustomizationDefaultFont.Merriweather]: content(
|
||||
'Merriweather',
|
||||
'merriweather',
|
||||
['400', '700', '900'],
|
||||
['serif']
|
||||
),
|
||||
[CustomizationDefaultFont.NotoSans]: content('Noto Sans', 'noto-sans', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.OpenSans]: content('Open Sans', 'open-sans', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Overpass]: content('Overpass', 'overpass', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Poppins]: content('Poppins', 'poppins', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.Raleway]: content('Raleway', 'raleway', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.Roboto]: content('Roboto', 'roboto', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.RobotoSlab]: content('Roboto Slab', 'roboto-slab', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.SourceSansPro]: content('Source Sans 3', 'source-sans-3', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Ubuntu]: content('Ubuntu', 'ubuntu', ['400', '500', '700']),
|
||||
[CustomizationDefaultFont.ABCFavorit]: {
|
||||
family: 'abcFavorit',
|
||||
googleId: null,
|
||||
weights: [],
|
||||
variable: '--font-content',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
adjustFallback: true,
|
||||
},
|
||||
|
||||
[CustomizationDefaultMonospaceFont.IBMPlexMono]: mono('IBM Plex Mono', 'ibm-plex-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.DMMono]: mono('DM Mono', 'dm-mono', ['400', '500']),
|
||||
[CustomizationDefaultMonospaceFont.FiraCode]: mono('Fira Code', 'fira-code', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.Inconsolata]: mono('Inconsolata', 'inconsolata', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.JetBrainsMono]: mono('JetBrains Mono', 'jetbrains-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.RobotoMono]: mono('Roboto Mono', 'roboto-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.SourceCodePro]: mono('Source Code Pro', 'source-code-pro', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.SpaceMono]: mono('Space Mono', 'space-mono', ['400', '700']),
|
||||
|
||||
[EMOJI_FONT]: {
|
||||
family: 'Noto Color Emoji',
|
||||
googleId: 'noto-color-emoji',
|
||||
weights: ['400'],
|
||||
variable: '--font-noto-color-emoji',
|
||||
fallback: [],
|
||||
adjustFallback: true,
|
||||
},
|
||||
};
|
||||
|
||||
// `abcFavorit` is licensed, so it ships in the repo instead of coming from Google Fonts. The
|
||||
// fallback metrics are the ones `next/font/local` measured from these exact files with fontkit.
|
||||
export const ABC_FAVORIT = {
|
||||
sources: [
|
||||
{ file: 'ABCFavorit-Variable.woff2', weight: '400 700', style: 'normal' },
|
||||
{ file: 'ABCFavorit-BoldItalic.woff2', weight: '700', style: 'italic' },
|
||||
{ file: 'ABCFavorit-MediumItalic.woff2', weight: '500', style: 'italic' },
|
||||
{ file: 'ABCFavorit-RegularItalic.woff2', weight: '400', style: 'italic' },
|
||||
],
|
||||
/** The design sits low in its em box; without this the fallback swap jumps vertically. */
|
||||
ascentOverride: '100%',
|
||||
fallbackMetrics: {
|
||||
ascentOverride: '90.97%',
|
||||
descentOverride: '37.34%',
|
||||
lineGapOverride: '0.00%',
|
||||
sizeAdjust: '104.43%',
|
||||
},
|
||||
} as const;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,341 @@
|
||||
{
|
||||
"google": {
|
||||
"inter": {
|
||||
"prefix": "https://fonts.gstatic.com/s/inter/v20",
|
||||
"files": [
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2"
|
||||
]
|
||||
},
|
||||
"fira-sans-extra-condensed": {
|
||||
"prefix": "https://fonts.gstatic.com/s/firasansextracondensed/v11",
|
||||
"files": [
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fKuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fuuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fOuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fyuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fCuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fGuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1f-uulWcrE4.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3i-oWR9e2U.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3i-oWR9e2U.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3i-oWR9e2U.woff2"
|
||||
]
|
||||
},
|
||||
"ibm-plex-serif": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ibmplexserif/v20",
|
||||
"files": [
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUS2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUb2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUQ2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUR2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUf2zcZiVbJ.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI0q1vjitOh.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI0q1vjitOh.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI0q1vjitOh.woff2"
|
||||
]
|
||||
},
|
||||
"lato": {
|
||||
"prefix": "https://fonts.gstatic.com/s/lato/v25",
|
||||
"files": [
|
||||
"S6uyw4BMUTPHjxAwXiWtFCfQ7A.woff2",
|
||||
"S6uyw4BMUTPHjx4wXiWtFCc.woff2",
|
||||
"S6u9w4BMUTPHh6UVSwaPGQ3q5d0N7w.woff2",
|
||||
"S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2",
|
||||
"S6u9w4BMUTPHh50XSwaPGQ3q5d0N7w.woff2",
|
||||
"S6u9w4BMUTPHh50XSwiPGQ3q5d0.woff2"
|
||||
]
|
||||
},
|
||||
"merriweather": {
|
||||
"prefix": "https://fonts.gstatic.com/s/merriweather/v33",
|
||||
"files": [
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqnJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSequJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqlJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqkJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqqJ-mXq1Gi.woff2"
|
||||
]
|
||||
},
|
||||
"noto-sans": {
|
||||
"prefix": "https://fonts.gstatic.com/s/notosans/v42",
|
||||
"files": [
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aPdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5ardu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a_du3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aLdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a3du3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aHdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aDdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a7du3mhPy0.woff2"
|
||||
]
|
||||
},
|
||||
"open-sans": {
|
||||
"prefix": "https://fonts.gstatic.com/s/opensans/v44",
|
||||
"files": [
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-mu0SC55I.woff2"
|
||||
]
|
||||
},
|
||||
"overpass": {
|
||||
"prefix": "https://fonts.gstatic.com/s/overpass/v19",
|
||||
"files": [
|
||||
"qFdH35WCmI96Ajtm81GoU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GhU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GqU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GrU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GlU9vgwBcI.woff2"
|
||||
]
|
||||
},
|
||||
"poppins": {
|
||||
"prefix": "https://fonts.gstatic.com/s/poppins/v24",
|
||||
"files": [
|
||||
"pxiEyp8kv8JHgFVrJJbecnFHGPezSQ.woff2",
|
||||
"pxiEyp8kv8JHgFVrJJnecnFHGPezSQ.woff2",
|
||||
"pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z1xlFd2JQEk.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z1xlFd2JQEk.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2"
|
||||
]
|
||||
},
|
||||
"raleway": {
|
||||
"prefix": "https://fonts.gstatic.com/s/raleway/v37",
|
||||
"files": [
|
||||
"1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyC0IT4ttDfA.woff2"
|
||||
]
|
||||
},
|
||||
"roboto": {
|
||||
"prefix": "https://fonts.gstatic.com/s/roboto/v51",
|
||||
"files": [
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBHMdazQ.woff2"
|
||||
]
|
||||
},
|
||||
"roboto-slab": {
|
||||
"prefix": "https://fonts.gstatic.com/s/robotoslab/v36",
|
||||
"files": [
|
||||
"BngMUXZYTXPIvIBgJJSb6ufA5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufJ5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufB5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufO5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufC5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufD5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufN5qWr4xCC.woff2"
|
||||
]
|
||||
},
|
||||
"source-sans-3": {
|
||||
"prefix": "https://fonts.gstatic.com/s/sourcesans3/v19",
|
||||
"files": [
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wIaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wsaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wMaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wwaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wAaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wEaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3w8aZejf5Hc.woff2"
|
||||
]
|
||||
},
|
||||
"ubuntu": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ubuntu/v21",
|
||||
"files": [
|
||||
"4iCs6KVjbNBYlgoKcg72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKew72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKcw72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKfA72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKcQ72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKfw72nU6AFw.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvWyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jtGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3js2yNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvmyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jsGyNPYZvgw.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvWyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjtGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjs2yNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvmyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjsGyNPYZvgw.woff2"
|
||||
]
|
||||
},
|
||||
"ibm-plex-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ibmplexmono/v20",
|
||||
"files": [
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iIq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1isq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iAq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iEq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1i8q131nj-o.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwlBFgsAXHNk.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwlBFgsAXHNk.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwlBFgsAXHNk.woff2"
|
||||
]
|
||||
},
|
||||
"dm-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/dmmono/v16",
|
||||
"files": [
|
||||
"aFTU7PB1QTsUX8KYthSQBK6PYK3EXw.woff2",
|
||||
"aFTU7PB1QTsUX8KYthqQBK6PYK0.woff2",
|
||||
"aFTR7PB1QTsUX8KYvumzEY2tbYf-Vlh3uA.woff2",
|
||||
"aFTR7PB1QTsUX8KYvumzEYOtbYf-Vlg.woff2"
|
||||
]
|
||||
},
|
||||
"fira-code": {
|
||||
"prefix": "https://fonts.gstatic.com/s/firacode/v27",
|
||||
"files": [
|
||||
"uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2"
|
||||
]
|
||||
},
|
||||
"inconsolata": {
|
||||
"prefix": "https://fonts.gstatic.com/s/inconsolata/v37",
|
||||
"files": [
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyxq15IDhunJ_o.woff2",
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyx615IDhunJ_o.woff2",
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyya15IDhunA.woff2"
|
||||
]
|
||||
},
|
||||
"jetbrains-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/jetbrainsmono/v24",
|
||||
"files": [
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwgknk-4.woff2"
|
||||
]
|
||||
},
|
||||
"roboto-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/robotomono/v31",
|
||||
"files": [
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2"
|
||||
]
|
||||
},
|
||||
"source-code-pro": {
|
||||
"prefix": "https://fonts.gstatic.com/s/sourcecodepro/v31",
|
||||
"files": [
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMOvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlOevWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMevWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlPuvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMuvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlM-vWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevWnsUnxg.woff2"
|
||||
]
|
||||
},
|
||||
"space-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/spacemono/v17",
|
||||
"files": [
|
||||
"i7dPIFZifjKcF5UAWdDRYE58RXi4EwSsbg.woff2",
|
||||
"i7dPIFZifjKcF5UAWdDRYE98RXi4EwSsbg.woff2",
|
||||
"i7dPIFZifjKcF5UAWdDRYEF8RXi4EwQ.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFqaHi6WZ3S_Yg.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFuaHi6WZ3S_Yg.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFWaHi6WZ3Q.woff2"
|
||||
]
|
||||
},
|
||||
"noto-color-emoji": {
|
||||
"prefix": "https://fonts.gstatic.com/s/notocoloremoji/v40",
|
||||
"files": [
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.0.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.1.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.2.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.3.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.4.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.5.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.6.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.7.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.8.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.9.woff2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"abcfavorit/7bb31c73cb8d820e.woff2": "./ABCFavorit/ABCFavorit-Variable.woff2",
|
||||
"abcfavorit/aed849de6e333605.woff2": "./ABCFavorit/ABCFavorit-BoldItalic.woff2",
|
||||
"abcfavorit/b435fb7330f5e171.woff2": "./ABCFavorit/ABCFavorit-MediumItalic.woff2",
|
||||
"abcfavorit/aee27c224ccd5167.woff2": "./ABCFavorit/ABCFavorit-RegularItalic.woff2"
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,32 @@ import type {
|
||||
} from '@gitbook/api';
|
||||
|
||||
import { generateFontFacesCSS, getFontSourcesToPreload } from './custom';
|
||||
import { fonts } from './default';
|
||||
import { generateDefaultFontFacesCSS } from './default';
|
||||
|
||||
export { DEFAULT_MONOSPACE_FONT, generateEmojiFontFacesCSS } from './default';
|
||||
|
||||
/**
|
||||
* Represents font data for either a default font or a custom font
|
||||
*/
|
||||
export type FontData = DefaultFontData | CustomFontData;
|
||||
|
||||
interface BaseFontData {
|
||||
/** `@font-face` rules and the CSS variable, to inline in the document head. */
|
||||
fontFaceRules: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font data for a default font, currently handle with next/font
|
||||
* Font data for a default font, self-hosted from our own assets
|
||||
*/
|
||||
interface DefaultFontData {
|
||||
interface DefaultFontData extends BaseFontData {
|
||||
type: 'default';
|
||||
variable: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font data for a custom font with @font-face rules
|
||||
*/
|
||||
interface CustomFontData {
|
||||
interface CustomFontData extends BaseFontData {
|
||||
type: 'custom';
|
||||
fontFaceRules: string;
|
||||
preloadSources: CustomizationFontDefinition['fontFaces'];
|
||||
}
|
||||
|
||||
@@ -39,7 +44,7 @@ export function getFontData(
|
||||
if (typeof font === 'string') {
|
||||
return {
|
||||
type: 'default',
|
||||
variable: fonts[font].variable,
|
||||
fontFaceRules: generateDefaultFontFacesCSS(font),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export interface FontVariantData {
|
||||
weight: string;
|
||||
style: string;
|
||||
/** Path under `~gitbook/static/fonts`, index-aligned with the family's `subsets`. */
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface FontFallbackFaceData {
|
||||
family: string;
|
||||
local: string;
|
||||
ascentOverride: string;
|
||||
descentOverride: string;
|
||||
lineGapOverride: string;
|
||||
sizeAdjust: string;
|
||||
}
|
||||
|
||||
export interface FontFamilyData {
|
||||
family: string;
|
||||
variable: string;
|
||||
/** Full value for the CSS variable, fallbacks included. */
|
||||
fontFamilyValue: string;
|
||||
/** `unicode-range` per subset. Held once per family rather than repeated on every variant. */
|
||||
subsets: string[];
|
||||
variants: FontVariantData[];
|
||||
fallbackFace: FontFallbackFaceData | null;
|
||||
/** Applied to every face of the family. */
|
||||
ascentOverride?: string;
|
||||
}
|
||||
|
||||
export type FontFacesData = Record<string, FontFamilyData>;
|
||||
|
||||
/** Where `scripts/download-fonts.ts` fetches (or copies) each file from. */
|
||||
export interface FontSourcesData {
|
||||
google: Record<string, { prefix: string; files: string[] }>;
|
||||
local: Record<string, string>;
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
"outputs": [
|
||||
"public/~gitbook/static/icons/**/*",
|
||||
"public/~gitbook/static/math/**/*",
|
||||
"public/~gitbook/static/embed/**/*"
|
||||
"public/~gitbook/static/embed/**/*",
|
||||
"public/~gitbook/static/css/**/*",
|
||||
"public/~gitbook/static/fonts/**/*"
|
||||
],
|
||||
"dependsOn": ["^generate", "@gitbook/embed#build"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user