mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-14 06:35:17 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6edf3b78f0 | |||
| fecdbe8dde | |||
| 9316ccd1f4 | |||
| c3b620e975 | |||
| 7a00880e5b | |||
| b6b597564d | |||
| d410381957 | |||
| 902c3c6c1a | |||
| 4fb2a4a6d7 | |||
| 2d64c78787 | |||
| fbfcca5dae | |||
| 664ae8bff6 | |||
| fa3d2aae08 | |||
| e7a591d9f6 | |||
| 2860beacdc | |||
| 231167d3e1 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook-v2": patch
|
||||
---
|
||||
|
||||
Add docs.testgitbook.com to ADAPTIVE_CONTENT_HOSTS list
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve support for OAuth2 security type
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Update Models page styling
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Best effort at preserving current variant when navigating between sections by matching the pathname against site spaces in the new section.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Rework full-width layout, add support for full-width page option
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/fonts": minor
|
||||
---
|
||||
|
||||
Initial version of the package
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Make icons for page groups more contrasting
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix border being added to cards
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook-v2": patch
|
||||
---
|
||||
|
||||
apply customization for dynamic context
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Reverse order of feedback smileys
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix ogimage using incorrect Google Font depending on language.
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing v1
|
||||
needs: deploy-v1-cloudflare
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v1
|
||||
needs: deploy-v1-cloudflare
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2
|
||||
needs: deploy-v2-vercel
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2 (Cloudflare)
|
||||
needs: deploy-v2-cloudflare
|
||||
timeout-minutes: 8
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"name": "@gitbook/cache-tags",
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"assert-never": "^1.2.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -47,15 +47,27 @@
|
||||
"emoji-assets": "^8.0.0",
|
||||
},
|
||||
},
|
||||
"packages/fonts": {
|
||||
"name": "@gitbook/fonts",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"google-font-metadata": "^6.0.3",
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
},
|
||||
"packages/gitbook": {
|
||||
"name": "gitbook",
|
||||
"version": "0.12.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/fonts": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@gitbook/react-contentkit": "workspace:*",
|
||||
@@ -150,7 +162,7 @@
|
||||
"name": "gitbook-v2",
|
||||
"version": "0.3.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@opennextjs/cloudflare": "1.2.1",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
@@ -209,7 +221,7 @@
|
||||
"name": "@gitbook/react-contentkit",
|
||||
"version": "0.7.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"classnames": "^2.5.1",
|
||||
},
|
||||
@@ -267,10 +279,12 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
},
|
||||
"catalog": {
|
||||
"@gitbook/api": "^0.120.0",
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
|
||||
|
||||
@@ -604,6 +618,8 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
|
||||
|
||||
"@evan/concurrency": ["@evan/concurrency@0.0.3", "", {}, "sha512-vjhkm2nrXoM39G4aP/U4CC5vFv/ZlMRSjbTII0N65J9R0EpgjdGswnHKS1KSjfGAp/9zKSNaojBgi0SxKnGapw=="],
|
||||
|
||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.6.8", "", { "dependencies": { "@floating-ui/utils": "^0.2.8" } }, "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA=="],
|
||||
@@ -632,7 +648,7 @@
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
|
||||
|
||||
"@gitbook/api": ["@gitbook/api@0.119.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-l2gfJ5+1HT3Sj1OQCh72lUOGU9LV9WbDjgKPjxIFz+NgOhSest1bJ3JhmHP66Xd4ETpmm0YQqnq4y18wKRMjBQ=="],
|
||||
"@gitbook/api": ["@gitbook/api@0.120.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-FiRmPiSBwobMxmNjd14QkkOdM95BAPLDDRShgpS9Vsd8lHjNMyZfrJKVJTsJUuFcgYoi4cqNw9yu/TiUBUgv3g=="],
|
||||
|
||||
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
|
||||
|
||||
@@ -644,6 +660,8 @@
|
||||
|
||||
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.8", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.6.0" } }, "sha512-i4PgiuGyUb52Muhc52kK3aMJIMfMkA2RbPW30tre8a6M8T6mWTfYo6gafSgjNvF1vH29zcuB8oBYnF0gO4XcHA=="],
|
||||
|
||||
"@gitbook/fonts": ["@gitbook/fonts@workspace:packages/fonts"],
|
||||
|
||||
"@gitbook/icons": ["@gitbook/icons@workspace:packages/icons"],
|
||||
|
||||
"@gitbook/openapi-parser": ["@gitbook/openapi-parser@workspace:packages/openapi-parser"],
|
||||
@@ -798,6 +816,22 @@
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
|
||||
|
||||
"@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
|
||||
|
||||
"@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="],
|
||||
|
||||
"@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="],
|
||||
|
||||
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
|
||||
|
||||
"@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
|
||||
|
||||
"@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],
|
||||
|
||||
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
||||
|
||||
"@opennextjs/aws": ["@opennextjs/aws@3.6.5", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-wni+CWlRCyWfhNfekQBBPPkrDDnaGdZLN9hMybKI0wKOKTO+zhPOqR65Eh3V0pzWAi84Sureb5mdMuLwCxAAcw=="],
|
||||
|
||||
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.2.1", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.6.5", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^4.19.1" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-cOco+nHwlo/PLB1bThF8IIvaS8PgAT9MEI5ZttFO/qt6spgvr2lUaPkpjgSIQmI3sBIEG2cLUykvQ2nbbZEcVw=="],
|
||||
@@ -1470,6 +1504,8 @@
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="],
|
||||
|
||||
"better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.1.2", "", {}, "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="],
|
||||
@@ -1482,6 +1518,8 @@
|
||||
|
||||
"body-parser": ["body-parser@2.0.2", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "3.1.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.5.2", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "^3.0.0", "type-is": "~1.6.18" } }, "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"bowser": ["bowser@2.11.0", "", {}, "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="],
|
||||
|
||||
"boxen": ["boxen@4.2.0", "", { "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", "cli-boxes": "^2.2.0", "string-width": "^4.1.0", "term-size": "^2.1.0", "type-fest": "^0.8.1", "widest-line": "^3.1.0" } }, "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ=="],
|
||||
@@ -1506,6 +1544,8 @@
|
||||
|
||||
"bytes": ["bytes@3.1.0", "", {}, "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"cacheable": ["cacheable@1.8.9", "", { "dependencies": { "hookified": "^1.7.1", "keyv": "^5.3.1" } }, "sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ=="],
|
||||
|
||||
"cacheable-request": ["cacheable-request@6.1.0", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^3.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^4.1.0", "responselike": "^1.0.2" } }, "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg=="],
|
||||
@@ -1602,10 +1642,16 @@
|
||||
|
||||
"css-functions-list": ["css-functions-list@3.2.3", "", {}, "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA=="],
|
||||
|
||||
"css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="],
|
||||
|
||||
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
||||
|
||||
"css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"cva": ["cva@1.0.0-beta.2", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "typescript": ">= 4.5.5 < 6" }, "optionalPeers": ["typescript"] }, "sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg=="],
|
||||
@@ -1662,6 +1708,14 @@
|
||||
|
||||
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
|
||||
|
||||
"dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
|
||||
@@ -1694,7 +1748,7 @@
|
||||
|
||||
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"env-cmd": ["env-cmd@10.1.0", "", { "dependencies": { "commander": "^4.0.0", "cross-spawn": "^7.0.0" }, "bin": { "env-cmd": "bin/env-cmd.js" } }, "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA=="],
|
||||
|
||||
@@ -1800,6 +1854,8 @@
|
||||
|
||||
"external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
|
||||
|
||||
"fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
@@ -1904,6 +1960,8 @@
|
||||
|
||||
"google-auth-library": ["google-auth-library@5.10.1", "", { "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", "gaxios": "^2.1.0", "gcp-metadata": "^3.4.0", "gtoken": "^4.1.0", "jws": "^4.0.0", "lru-cache": "^5.0.0" } }, "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg=="],
|
||||
|
||||
"google-font-metadata": ["google-font-metadata@6.0.3", "", { "dependencies": { "@evan/concurrency": "^0.0.3", "@octokit/core": "^6.1.2", "cac": "^6.7.14", "consola": "^3.3.3", "deepmerge": "^4.3.1", "json-stringify-pretty-compact": "^4.0.0", "linkedom": "^0.18.6", "pathe": "^1.1.2", "picocolors": "^1.1.1", "playwright": "^1.49.1", "stylis": "^4.3.4", "zod": "^3.24.1" }, "bin": { "gfm": "dist/cli.mjs" } }, "sha512-62+QB+nmBKppHrxwvILZkV/EvKctEAdzkKBIJY26TtwZkUjeEqmAQnE5uHvtMTdB8odqGpQu5LAKPqqUYHI9wA=="],
|
||||
|
||||
"google-p12-pem": ["google-p12-pem@2.0.5", "", { "dependencies": { "node-forge": "^0.10.0" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" } }, "sha512-7RLkxwSsMsYh9wQ5Vb2zRtkAHvqPvfoMGag+nugl1noYO7gf0844Yr9TIFA5NEBMAeVt2Z+Imu7CQMp3oNatzQ=="],
|
||||
|
||||
"googleapis": ["googleapis@47.0.0", "", { "dependencies": { "google-auth-library": "^5.6.1", "googleapis-common": "^3.2.0" } }, "sha512-+Fnjgcc3Na/rk57dwxqW1V0HJXJFjnt3aqFlckULqAqsPkmex/AyJJe6MSlXHC37ZmlXEb9ZtPGUp5ZzRDXpHg=="],
|
||||
@@ -1980,12 +2038,16 @@
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
|
||||
|
||||
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
|
||||
|
||||
"html-tags": ["html-tags@3.3.1", "", {}, "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
|
||||
"html-whitespace-sensitive-tag-names": ["html-whitespace-sensitive-tag-names@3.0.1", "", {}, "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
|
||||
|
||||
"http-cache-semantics": ["http-cache-semantics@4.1.1", "", {}, "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="],
|
||||
|
||||
"http-errors": ["http-errors@1.4.0", "", { "dependencies": { "inherits": "2.0.1", "statuses": ">= 1.2.1 < 2" } }, "sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw=="],
|
||||
@@ -2096,6 +2158,8 @@
|
||||
|
||||
"json-stringify-deterministic": ["json-stringify-deterministic@1.0.12", "", {}, "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g=="],
|
||||
|
||||
"json-stringify-pretty-compact": ["json-stringify-pretty-compact@4.0.0", "", {}, "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="],
|
||||
|
||||
"json-xml-parse": ["json-xml-parse@1.3.0", "", {}, "sha512-MVosauc/3W2wL4dd4yaJzH5oXw+HOUfptn0+d4+bFghMiJFop7MaqIwFXJNLiRnNYJNQ6L4o7B+53n5wcvoLFw=="],
|
||||
|
||||
"jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
|
||||
@@ -2134,6 +2198,8 @@
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"linkedom": ["linkedom@0.18.11", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^10.0.0", "uhyphen": "^0.2.0" } }, "sha512-K03GU3FUlnhBAP0jPb7tN7YJl7LbjZx30Z8h6wgLXusnKF7+BEZvfEbdkN/lO9LfFzxN3S0ZAriDuJ/13dIsLA=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"lodash.castarray": ["lodash.castarray@4.4.0", "", {}, "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="],
|
||||
@@ -2364,6 +2430,8 @@
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"nuqs": ["nuqs@2.2.3", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "@remix-run/react": ">=2", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router-dom": ">=6" }, "optionalPeers": ["@remix-run/react", "next", "react-router-dom"] }, "sha512-nMCcUW06KSqEXA0xp+LiRqDpIE59BVYbjZLe0HUisJAlswfihHYSsAjYTzV0lcE1thfh8uh+LqUHGdQ8qq8rfA=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
@@ -2454,7 +2522,7 @@
|
||||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||
|
||||
"pcre-to-regexp": ["pcre-to-regexp@1.1.0", "", {}, "sha512-KF9XxmUQJ2DIlMj3TqNqY1AWvyvTuIuq11CuuekxyaYMiFuMKGgQrePYMX5bXKLhLG3sDI4CsGAYHPaT7VV7+g=="],
|
||||
|
||||
@@ -2722,6 +2790,8 @@
|
||||
|
||||
"stylelint": ["stylelint@16.16.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "@csstools/media-query-list-parser": "^4.0.2", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", "debug": "^4.3.7", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^10.0.7", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", "ignore": "^7.0.3", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.35.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg=="],
|
||||
|
||||
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -2826,6 +2896,8 @@
|
||||
|
||||
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
|
||||
|
||||
"uhyphen": ["uhyphen@0.2.0", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="],
|
||||
|
||||
"uid-promise": ["uid-promise@1.0.0", "", {}, "sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig=="],
|
||||
|
||||
"undici": ["undici@5.28.4", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g=="],
|
||||
@@ -2852,6 +2924,8 @@
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
|
||||
|
||||
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
|
||||
|
||||
"universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
@@ -4014,6 +4088,8 @@
|
||||
|
||||
"@vercel/static-config/ajv": ["ajv@8.6.3", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw=="],
|
||||
|
||||
"@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"@vue/compiler-sfc/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
|
||||
|
||||
"@vueuse/integrations/@vueuse/core": ["@vueuse/core@11.2.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "11.2.0", "@vueuse/shared": "11.2.0", "vue-demi": ">=0.14.10" } }, "sha512-JIUwRcOqOWzcdu1dGlfW04kaJhW3EXnnjJJfLTtddJanymTL7lF1C0+dVVZ/siLfc73mWn+cGP1PE1PKPruRSA=="],
|
||||
@@ -4062,6 +4138,8 @@
|
||||
|
||||
"decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"edge-runtime/async-listen": ["async-listen@3.0.1", "", {}, "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA=="],
|
||||
|
||||
"edge-runtime/picocolors": ["picocolors@1.0.0", "", {}, "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="],
|
||||
@@ -4110,6 +4188,8 @@
|
||||
|
||||
"google-auth-library/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"google-font-metadata/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"googleapis-common/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
|
||||
|
||||
"got/get-stream": ["get-stream@4.1.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w=="],
|
||||
@@ -4178,6 +4258,8 @@
|
||||
|
||||
"package-json/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"parse5/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"path-match/path-to-regexp": ["path-to-regexp@1.9.0", "", { "dependencies": { "isarray": "0.0.1" } }, "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="],
|
||||
@@ -4270,6 +4352,8 @@
|
||||
|
||||
"ts-node/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
|
||||
|
||||
"unenv/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"update-notifier/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
|
||||
"url-parse-lax/prepend-http": ["prepend-http@2.0.0", "", {}, "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="],
|
||||
|
||||
+7
-3
@@ -7,10 +7,9 @@
|
||||
"turbo": "^2.5.0",
|
||||
"vercel": "^39.3.0"
|
||||
},
|
||||
"packageManager": "bun@1.2.11",
|
||||
"packageManager": "bun@1.2.15",
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
@@ -34,7 +33,12 @@
|
||||
"download:env": "op read op://gitbook-x-dev/gitbook-open/.env.local >> .env.local",
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
"workspaces": ["packages/*"],
|
||||
"workspaces": {
|
||||
"packages": ["packages/*"],
|
||||
"catalog": {
|
||||
"@gitbook/api": "^0.120.0"
|
||||
}
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"decode-named-character-reference@1.0.2": "patches/decode-named-character-reference@1.0.2.patch",
|
||||
"@vercel/next@4.4.2": "patches/@vercel%2Fnext@4.4.2.patch"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"assert-never": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
src/data/*.json
|
||||
@@ -0,0 +1,3 @@
|
||||
# `@gitbook/fonts`
|
||||
|
||||
Utilities to lookup default fonts supported by GitBook.
|
||||
@@ -0,0 +1,91 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { APIv2 } from 'google-font-metadata';
|
||||
|
||||
import { CustomizationDefaultFont } from '@gitbook/api';
|
||||
|
||||
import type { FontDefinitions } from '../src/types';
|
||||
|
||||
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
|
||||
[CustomizationDefaultFont.Inter]: 'inter',
|
||||
[CustomizationDefaultFont.FiraSans]: 'fira-sans-extra-condensed',
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: 'ibm-plex-serif',
|
||||
[CustomizationDefaultFont.Lato]: 'lato',
|
||||
[CustomizationDefaultFont.Merriweather]: 'merriweather',
|
||||
[CustomizationDefaultFont.NotoSans]: 'noto-sans',
|
||||
[CustomizationDefaultFont.OpenSans]: 'open-sans',
|
||||
[CustomizationDefaultFont.Overpass]: 'overpass',
|
||||
[CustomizationDefaultFont.Poppins]: 'poppins',
|
||||
[CustomizationDefaultFont.Raleway]: 'raleway',
|
||||
[CustomizationDefaultFont.Roboto]: 'roboto',
|
||||
[CustomizationDefaultFont.RobotoSlab]: 'roboto-slab',
|
||||
[CustomizationDefaultFont.SourceSansPro]: 'source-sans-3',
|
||||
[CustomizationDefaultFont.Ubuntu]: 'ubuntu',
|
||||
[CustomizationDefaultFont.ABCFavorit]: 'inter',
|
||||
};
|
||||
|
||||
/**
|
||||
* Scripts to generate the list of all icons.
|
||||
*/
|
||||
async function main() {
|
||||
// @ts-expect-error - we build the object
|
||||
const output: FontDefinitions = {};
|
||||
|
||||
for (const font of Object.values(CustomizationDefaultFont)) {
|
||||
const googleFontName = googleFontsMap[font];
|
||||
const fontMetadata = APIv2[googleFontName.toLowerCase()];
|
||||
if (!fontMetadata) {
|
||||
throw new Error(`Font ${googleFontName} not found`);
|
||||
}
|
||||
|
||||
output[font] = {
|
||||
font: googleFontName,
|
||||
unicodeRange: fontMetadata.unicodeRange,
|
||||
variants: {
|
||||
'400': {},
|
||||
'700': {},
|
||||
},
|
||||
};
|
||||
|
||||
Object.keys(output[font].variants).forEach((weight) => {
|
||||
const variants = fontMetadata.variants[weight];
|
||||
const normalVariant = variants.normal;
|
||||
if (!normalVariant) {
|
||||
throw new Error(`Font ${googleFontName} has no normal variant`);
|
||||
}
|
||||
|
||||
output[font].variants[weight] = {};
|
||||
Object.entries(normalVariant).forEach(([script, url]) => {
|
||||
output[font].variants[weight][script] = url.url.woff;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await writeDataFile('fonts', JSON.stringify(output, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* We write both in dist and src as the build process might have happen already
|
||||
* and tsc doesn't copy the files.
|
||||
*/
|
||||
async function writeDataFile(name, content) {
|
||||
const srcData = path.resolve(__dirname, '../src/data');
|
||||
const distData = path.resolve(__dirname, '../dist/data');
|
||||
|
||||
// Ensure the directories exists
|
||||
await Promise.all([
|
||||
fs.mkdir(srcData, { recursive: true }),
|
||||
fs.mkdir(distData, { recursive: true }),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
fs.writeFile(path.resolve(srcData, `${name}.json`), content),
|
||||
fs.writeFile(path.resolve(distData, `${name}.json`), content),
|
||||
]);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`Error generating icons list: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@gitbook/fonts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"google-font-metadata": "^6.0.3",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "bun ./bin/generate.js",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "tsc -w",
|
||||
"clean": "rm -rf ./dist && rm -rf ./src/data",
|
||||
"unit": "bun test"
|
||||
},
|
||||
"files": ["dist", "src", "bin", "README.md", "CHANGELOG.md"],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getDefaultFont should return correct object for Latin text 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should return correct object for Cyrillic text 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZthjp-Ek-_0ewmM.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should return correct object for Greek text 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZxhjp-Ek-_0ewmM.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should handle mixed script text 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZthjp-Ek-_0ewmM.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should handle different font weights: regular 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should handle different font weights: bold 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYAZ9hjp-Ek-_0ew.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should handle different fonts: inter 1`] = `
|
||||
{
|
||||
"font": "Inter",
|
||||
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDefaultFont should handle different fonts: roboto 1`] = `
|
||||
{
|
||||
"font": "Roboto",
|
||||
"url": "https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Mu4mxMKTU1Kg.woff",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { FontDefinitions } from './types';
|
||||
|
||||
import rawFonts from './data/fonts.json' with { type: 'json' };
|
||||
|
||||
export const fonts: FontDefinitions = rawFonts;
|
||||
@@ -0,0 +1,119 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { CustomizationDefaultFont } from '@gitbook/api';
|
||||
import { getDefaultFont } from './getDefaultFont';
|
||||
|
||||
describe('getDefaultFont', () => {
|
||||
it('should return null for invalid font', () => {
|
||||
const result = getDefaultFont({
|
||||
font: 'invalid-font' as CustomizationDefaultFont,
|
||||
text: 'Hello',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for invalid weight', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello',
|
||||
weight: 999 as any,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for text not supported by any script', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: '😀', // Emoji not supported by Inter
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return correct object for Latin text', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello World',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return correct object for Cyrillic text', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Привет мир',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return correct object for Greek text', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Γεια σας',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle mixed script text', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello Привет',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle different font weights', () => {
|
||||
const regular = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello',
|
||||
weight: 400,
|
||||
});
|
||||
const bold = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello',
|
||||
weight: 700,
|
||||
});
|
||||
expect(regular).not.toBeNull();
|
||||
expect(bold).not.toBeNull();
|
||||
expect(regular).toMatchSnapshot('regular');
|
||||
expect(bold).toMatchSnapshot('bold');
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const result = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: '',
|
||||
weight: 400,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle different fonts', () => {
|
||||
const inter = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Inter,
|
||||
text: 'Hello',
|
||||
weight: 400,
|
||||
});
|
||||
const roboto = getDefaultFont({
|
||||
font: CustomizationDefaultFont.Roboto,
|
||||
text: 'Hello',
|
||||
weight: 400,
|
||||
});
|
||||
expect(inter).not.toBeNull();
|
||||
expect(roboto).not.toBeNull();
|
||||
expect(inter).toMatchSnapshot('inter');
|
||||
expect(roboto).toMatchSnapshot('roboto');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { CustomizationDefaultFont } from '@gitbook/api';
|
||||
import { fonts } from './fonts';
|
||||
import type { FontWeight } from './types';
|
||||
|
||||
/**
|
||||
* Get the URL to load a font for a text.
|
||||
*/
|
||||
export function getDefaultFont(input: {
|
||||
/**
|
||||
* GitBook font to use.
|
||||
*/
|
||||
font: CustomizationDefaultFont;
|
||||
|
||||
/**
|
||||
* Text to display with the font.
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Font weight to use.
|
||||
*/
|
||||
weight: FontWeight;
|
||||
}): { font: string; url: string } | null {
|
||||
if (!input.text.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fontDefinition = fonts[input.font];
|
||||
if (!fontDefinition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const variant = fontDefinition.variants[`${input.weight}`];
|
||||
if (!variant) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const script = getBestUnicodeRange(input.text, fontDefinition.unicodeRange);
|
||||
if (!script) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return variant[script]
|
||||
? {
|
||||
font: input.font,
|
||||
url: variant[script],
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which named @font-face unicode-range covers
|
||||
* the greatest share of the characters in `text`.
|
||||
*
|
||||
* @param text The text you want to inspect.
|
||||
* @param ranges An object whose keys are range names and whose
|
||||
* values are CSS-style comma-separated unicode-range
|
||||
* declarations (e.g. "U+0370-03FF,U+1F00-1FFF").
|
||||
* @returns The key of the best-matching range, or `null`
|
||||
* when nothing matches at all.
|
||||
*/
|
||||
function getBestUnicodeRange(text: string, ranges: Record<string, string>): string | null {
|
||||
// ---------- helper: parse "U+XXXX" or "U+XXXX-YYYY" ----------
|
||||
const parseOne = (token: string): [number, number] | null => {
|
||||
token = token.trim().toUpperCase();
|
||||
if (!token.startsWith('U+')) return null;
|
||||
|
||||
const body = token.slice(2); // drop "U+"
|
||||
const [startHex, endHex] = body.split('-');
|
||||
const start = Number.parseInt(startHex, 16);
|
||||
const end = endHex ? Number.parseInt(endHex, 16) : start;
|
||||
|
||||
if (Number.isNaN(start) || Number.isNaN(end) || end < start) return null;
|
||||
return [start, end];
|
||||
};
|
||||
|
||||
// ---------- helper: build lookup table ----------
|
||||
const parsed: Record<string, [number, number][]> = {};
|
||||
for (const [label, list] of Object.entries(ranges)) {
|
||||
parsed[label] = list
|
||||
.split(',')
|
||||
.map(parseOne)
|
||||
.filter((x): x is [number, number] => x !== null);
|
||||
}
|
||||
|
||||
// ---------- tally code-point hits ----------
|
||||
const hits: Record<string, number> = Object.fromEntries(Object.keys(parsed).map((k) => [k, 0]));
|
||||
|
||||
for (let i = 0; i < text.length; ) {
|
||||
const cp = text.codePointAt(i)!;
|
||||
i += cp > 0xffff ? 2 : 1; // advance by 1 UTF-16 code-unit (or 2 for surrogates)
|
||||
|
||||
for (const [label, rangesArr] of Object.entries(parsed)) {
|
||||
if (rangesArr.some(([lo, hi]) => cp >= lo && cp <= hi)) {
|
||||
hits[label]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- choose the "best" ----------
|
||||
let winner: string | null = null;
|
||||
let maxCount = 0;
|
||||
|
||||
for (const [label, count] of Object.entries(hits)) {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
winner = label;
|
||||
}
|
||||
}
|
||||
|
||||
return maxCount > 0 ? winner : null;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './getDefaultFont';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { CustomizationDefaultFont } from '@gitbook/api';
|
||||
|
||||
export type FontWeight = 400 | 700;
|
||||
|
||||
export type FontDefinition = {
|
||||
font: string;
|
||||
unicodeRange: {
|
||||
[script: string]: string;
|
||||
};
|
||||
variants: {
|
||||
[weight in string]: {
|
||||
[script: string]: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type FontDefinitions = { [fontName in CustomizationDefaultFont]: FontDefinition };
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "src/**/*.test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2023"],
|
||||
"module": "ESNext",
|
||||
"target": "es2022",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"generate": {
|
||||
"inputs": ["bin/**/*", "package.json"],
|
||||
"outputs": ["src/data/*.json", "dist/data/*.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,17 +49,20 @@ class GitbookIncrementalCache implements IncrementalCache {
|
||||
const localCacheEntry = await localCache.match(this.getCacheUrlKey(cacheKey));
|
||||
if (localCacheEntry) {
|
||||
span.setAttribute('cacheHit', 'local');
|
||||
return localCacheEntry.json();
|
||||
const result = (await localCacheEntry.json()) as WithLastModified<
|
||||
CacheValue<CacheType>
|
||||
>;
|
||||
return this.returnNullOn404(result);
|
||||
}
|
||||
|
||||
const r2Object = await r2.get(cacheKey);
|
||||
if (!r2Object) return null;
|
||||
|
||||
span.setAttribute('cacheHit', 'r2');
|
||||
return {
|
||||
return this.returnNullOn404({
|
||||
value: await r2Object.json(),
|
||||
lastModified: r2Object.uploaded.getTime(),
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to get from cache', e);
|
||||
return null;
|
||||
@@ -68,6 +71,18 @@ class GitbookIncrementalCache implements IncrementalCache {
|
||||
);
|
||||
}
|
||||
|
||||
//TODO: This is a workaround to handle 404 responses in the cache.
|
||||
// It should be handled by OpenNext cache interception directly. This should be removed once OpenNext cache interception is fixed.
|
||||
returnNullOn404<CacheType extends CacheEntryType = 'cache'>(
|
||||
cacheEntry: WithLastModified<CacheValue<CacheType>> | null
|
||||
): WithLastModified<CacheValue<CacheType>> | null {
|
||||
if (!cacheEntry?.value) return null;
|
||||
if ('meta' in cacheEntry.value && cacheEntry.value.meta?.status === 404) {
|
||||
return null;
|
||||
}
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
async set<CacheType extends CacheEntryType = 'cache'>(
|
||||
key: string,
|
||||
value: CacheValue<CacheType>,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@opennextjs/cloudflare": "1.2.1",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive';
|
||||
import { getDynamicCustomizationSettings } from '@/lib/customization';
|
||||
import type { SiteAPIToken } from '@gitbook/api';
|
||||
import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@v2/lib/context';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
@@ -67,6 +68,8 @@ export async function getDynamicSiteContext(params: RouteLayoutParams) {
|
||||
siteURLData
|
||||
);
|
||||
|
||||
context.customization = await getDynamicCustomizationSettings(context.customization);
|
||||
|
||||
return {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaims(siteURLData),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
export * from './api';
|
||||
export * from './types';
|
||||
export * from './pages';
|
||||
export * from './urls';
|
||||
export * from './errors';
|
||||
export * from './lookup';
|
||||
export * from './visitor';
|
||||
export * from './pages';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { GitBookDataFetcher } from './types';
|
||||
|
||||
/**
|
||||
* Wrap a function by preventing concurrent executions of the same function.
|
||||
* With a logic to work per-request in Cloudflare Workers.
|
||||
*/
|
||||
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
|
||||
dataFetcherObject: GitBookDataFetcher,
|
||||
wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType>
|
||||
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
|
||||
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
|
||||
|
||||
return (key: string, ...args: ArgsType) => {
|
||||
const globalContext = dataFetcherObject;
|
||||
|
||||
/**
|
||||
* Cache storage that is scoped to the current request when executed in Cloudflare Workers,
|
||||
* to avoid "Cannot perform I/O on behalf of a different request" errors.
|
||||
*/
|
||||
const promiseCache =
|
||||
globalPromiseCache.get(globalContext) ?? new Map<string, Promise<ReturnType>>();
|
||||
globalPromiseCache.set(globalContext, promiseCache);
|
||||
|
||||
const concurrent = promiseCache.get(key);
|
||||
if (concurrent) {
|
||||
return concurrent;
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await wrapped(key, ...args);
|
||||
return result;
|
||||
} finally {
|
||||
promiseCache.delete(key);
|
||||
}
|
||||
})();
|
||||
|
||||
promiseCache.set(key, promise);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a function by passing it a cache key that is computed from the function arguments.
|
||||
*/
|
||||
export function withCacheKey<ArgsType extends any[], ReturnType>(
|
||||
wrapped: (cacheKey: string, ...args: ArgsType) => Promise<ReturnType>
|
||||
): (...args: ArgsType) => Promise<ReturnType> {
|
||||
return (...args: ArgsType) => {
|
||||
const cacheKey = getCacheKey(args);
|
||||
return wrapped(cacheKey, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a cache key from the function arguments.
|
||||
*/
|
||||
function getCacheKey(args: any[]) {
|
||||
return JSON.stringify(deepSortValue(args));
|
||||
}
|
||||
|
||||
function deepSortValue(value: unknown): unknown {
|
||||
if (
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean' ||
|
||||
value === null ||
|
||||
value === undefined
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(deepSortValue);
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.entries(value)
|
||||
.map(([key, subValue]) => {
|
||||
return [key, deepSortValue(subValue)] as const;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a[0].localeCompare(b[0]);
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1,15 +1,30 @@
|
||||
import type { JSONDocument, RevisionPageDocument, Space } from '@gitbook/api';
|
||||
import { waitUntil } from '@/lib/waitUntil';
|
||||
import type { JSONDocument, RevisionPageDocument } from '@gitbook/api';
|
||||
import type { GitBookSiteContext, GitBookSpaceContext } from '../context';
|
||||
import { getDataOrNull } from './errors';
|
||||
import type { GitBookDataFetcher } from './types';
|
||||
|
||||
/**
|
||||
* Get the document for a page.
|
||||
*/
|
||||
export async function getPageDocument(
|
||||
dataFetcher: GitBookDataFetcher,
|
||||
space: Space,
|
||||
context: GitBookSpaceContext | GitBookSiteContext,
|
||||
page: RevisionPageDocument
|
||||
): Promise<JSONDocument | null> {
|
||||
const { dataFetcher, space } = context;
|
||||
|
||||
if (
|
||||
'site' in context &&
|
||||
(context.site.id === 'site_JOVzv' || context.site.id === 'site_IxAYj')
|
||||
) {
|
||||
return getDataOrNull(
|
||||
dataFetcher.getRevisionPageDocument({
|
||||
spaceId: space.id,
|
||||
revisionId: space.revision,
|
||||
pageId: page.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (page.documentId) {
|
||||
return getDataOrNull(
|
||||
dataFetcher.getDocument({ spaceId: space.id, documentId: page.documentId })
|
||||
@@ -26,5 +41,30 @@ export async function getPageDocument(
|
||||
);
|
||||
}
|
||||
|
||||
// Pre-fetch the document to start filling the cache before we migrate to this API.
|
||||
if (isInPercentRollout(space.id, 10)) {
|
||||
await waitUntil(
|
||||
getDataOrNull(
|
||||
dataFetcher.getRevisionPageDocument({
|
||||
spaceId: space.id,
|
||||
revisionId: space.revision,
|
||||
pageId: page.id,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isInPercentRollout(value: string, rollout: number) {
|
||||
return getRandomPercent(value) < rollout;
|
||||
}
|
||||
|
||||
function getRandomPercent(value: string) {
|
||||
const hash = value.split('').reduce((acc, char) => {
|
||||
return acc + char.charCodeAt(0);
|
||||
}, 0);
|
||||
|
||||
return hash % 100;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,15 @@ export interface GitBookDataFetcher {
|
||||
pageId: string;
|
||||
}): Promise<DataFetcherResponse<string>>;
|
||||
|
||||
/**
|
||||
* Get the document of a page by its path.
|
||||
*/
|
||||
getRevisionPageDocument(params: {
|
||||
spaceId: string;
|
||||
revisionId: string;
|
||||
pageId: string;
|
||||
}): Promise<DataFetcherResponse<api.JSONDocument>>;
|
||||
|
||||
/**
|
||||
* Get a document by its space ID and document ID.
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,8 @@ const ADAPTIVE_CONTENT_HOSTS = [
|
||||
'docs.gitbook.com',
|
||||
'adaptive-docs.gitbook-staging.com',
|
||||
'enriched-content-playground.gitbook-staging.io',
|
||||
'docs.testgitbook.com',
|
||||
'launchdarkly-site.gitbook.education',
|
||||
];
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
|
||||
@@ -204,11 +204,11 @@ const testCases: TestsCase[] = [
|
||||
contentBaseURL: 'https://sosovalue-white-paper.gitbook.io',
|
||||
tests: [{ name: 'Home', url: '/' }],
|
||||
},
|
||||
{
|
||||
name: 'docs.revrobotics.com',
|
||||
contentBaseURL: 'https://docs.revrobotics.com',
|
||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
},
|
||||
// {
|
||||
// name: 'docs.revrobotics.com',
|
||||
// contentBaseURL: 'https://docs.revrobotics.com',
|
||||
// tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
// },
|
||||
{
|
||||
name: 'chartschool.stockcharts.com',
|
||||
contentBaseURL: 'https://chartschool.stockcharts.com',
|
||||
|
||||
@@ -10,18 +10,19 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"e2e": "playwright test e2e/internal.spec.ts",
|
||||
"e2e-customers": "playwright test e2e/customers.spec.ts",
|
||||
"unit": "bun test {src,packages}",
|
||||
"unit": "bun test {src,packages} --preload ./tests/preload-bun.ts",
|
||||
"generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math",
|
||||
"copy:icons": "gitbook-icons ./public/~gitbook/static/icons",
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/fonts": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@gitbook/react-contentkit": "workspace:*",
|
||||
"@gitbook/react-math": "workspace:*",
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@/components/primitives';
|
||||
import type { ClassValue } from '@/lib/tailwind';
|
||||
|
||||
import { nullIfNever } from '@/lib/typescript';
|
||||
import { BlockContentRef } from './BlockContentRef';
|
||||
import { CodeBlock } from './CodeBlock';
|
||||
import { Columns } from './Columns';
|
||||
@@ -45,13 +46,6 @@ export interface BlockProps<Block extends DocumentBlock> extends DocumentContext
|
||||
style?: ClassValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to `assertNever` that returns `null` instead of throwing an error.
|
||||
*/
|
||||
function nullIfNever(_value: never): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
|
||||
const { block, style, isEstimatedOffscreen, context } = props;
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
|
||||
style={[
|
||||
'mx-auto w-full decoration-primary/6',
|
||||
node.data && 'fullWidth' in node.data && node.data.fullWidth
|
||||
? 'max-w-screen-xl'
|
||||
: 'max-w-3xl',
|
||||
? 'max-w-screen-2xl'
|
||||
: 'page-full-width:ml-0 max-w-3xl',
|
||||
blockStyle,
|
||||
]}
|
||||
isEstimatedOffscreen={isOffscreen}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function Columns(props: BlockProps<DocumentBlockColumns>) {
|
||||
ancestorBlocks={[...ancestorBlocks, block, columnBlock]}
|
||||
context={context}
|
||||
blockStyle="flip-heading-hash"
|
||||
style="w-full space-y-4"
|
||||
style="w-full space-y-4 *:max-w-full"
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
||||
@@ -7,5 +7,5 @@ import type { BlockProps } from './Block';
|
||||
export function Divider(props: BlockProps<DocumentBlockDivider>) {
|
||||
const { style } = props;
|
||||
|
||||
return <hr className={tcls(style, 'border-tint-subtle')} />;
|
||||
return <hr className={tcls(style, 'page-full-width:max-w-full border-tint-subtle')} />;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import type {
|
||||
DocumentInline,
|
||||
DocumentInlineAnnotation,
|
||||
DocumentInlineButton,
|
||||
DocumentInlineEmoji,
|
||||
DocumentInlineImage,
|
||||
DocumentInlineLink,
|
||||
DocumentInlineMath,
|
||||
DocumentInlineMention,
|
||||
JSONDocument,
|
||||
} from '@gitbook/api';
|
||||
import assertNever from 'assert-never';
|
||||
import type { DocumentInline, JSONDocument } from '@gitbook/api';
|
||||
|
||||
import { nullIfNever } from '@/lib/typescript';
|
||||
import { Annotation } from './Annotation/Annotation';
|
||||
import type { DocumentContextProps } from './DocumentView';
|
||||
import { Emoji } from './Emoji';
|
||||
@@ -39,16 +29,7 @@ export interface InlineProps<T extends DocumentInline> extends DocumentContextPr
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Inline<
|
||||
T extends
|
||||
| DocumentInlineImage
|
||||
| DocumentInlineAnnotation
|
||||
| DocumentInlineEmoji
|
||||
| DocumentInlineLink
|
||||
| DocumentInlineMath
|
||||
| DocumentInlineMention
|
||||
| DocumentInlineButton,
|
||||
>(props: InlineProps<T>) {
|
||||
export function Inline<T extends DocumentInline>(props: InlineProps<T>) {
|
||||
const { inline, ...contextProps } = props;
|
||||
|
||||
switch (inline.type) {
|
||||
@@ -66,7 +47,11 @@ export function Inline<
|
||||
return <InlineImage {...contextProps} inline={inline} />;
|
||||
case 'button':
|
||||
return <InlineButton {...contextProps} inline={inline} />;
|
||||
case 'expression':
|
||||
// The GitBook API should take care of evaluating expressions.
|
||||
// We should never need to render them.
|
||||
return null;
|
||||
default:
|
||||
assertNever(inline);
|
||||
return nullIfNever(inline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
|
||||
const resolved = context.contentContext
|
||||
? await resolveContentRef(inline.data.ref, context.contentContext, {
|
||||
resolveAnchorText: true,
|
||||
// We don't want to resolve the anchor text here, as it can be very expensive and will block rendering if there is a lot of anchors link.
|
||||
resolveAnchorText: false,
|
||||
})
|
||||
: null;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.openapi-schemas {
|
||||
@apply flex flex-col mb-14 flex-1;
|
||||
@apply flex flex-col mb-14 gap-0 flex-1;
|
||||
}
|
||||
|
||||
.openapi-schemas-title {
|
||||
@@ -310,6 +310,26 @@
|
||||
@apply py-2 border-b border-tint-subtle max-w-full flex-1;
|
||||
}
|
||||
|
||||
.openapi-securities-oauth-flows {
|
||||
@apply flex flex-col gap-2 divide-y divide-tint-subtle;
|
||||
}
|
||||
|
||||
.openapi-securities-oauth-content {
|
||||
@apply prose *:!prose-sm *:text-tint;
|
||||
}
|
||||
|
||||
.openapi-securities-oauth-content.openapi-markdown code {
|
||||
@apply text-xs;
|
||||
}
|
||||
|
||||
.openapi-securities-oauth-content ul {
|
||||
@apply !my-0;
|
||||
}
|
||||
|
||||
.openapi-securities-url {
|
||||
@apply ml-0.5 px-0.5 rounded hover:bg-tint transition-colors;
|
||||
}
|
||||
|
||||
.openapi-securities-body {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
@@ -728,8 +748,12 @@ body:has(.openapi-select-popover) {
|
||||
}
|
||||
|
||||
/* Disclosure */
|
||||
.openapi-schemas-disclosure {
|
||||
@apply border-t border-x last:border-b border-tint-subtle !ring-0 first:!rounded-t-xl last:!rounded-b-xl !rounded-none;
|
||||
}
|
||||
|
||||
.openapi-schemas-disclosure > .openapi-disclosure-trigger {
|
||||
@apply flex items-center font-mono transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
|
||||
@apply flex items-center font-mono transition-all font-normal text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-5 truncate -outline-offset-1;
|
||||
}
|
||||
|
||||
.openapi-schemas-disclosure > .openapi-disclosure-trigger,
|
||||
@@ -773,7 +797,6 @@ body:has(.openapi-select-popover) {
|
||||
@apply bg-tint-subtle overflow-hidden;
|
||||
}
|
||||
|
||||
.openapi-disclosure:has(> .openapi-disclosure-trigger:hover),
|
||||
.openapi-disclosure[data-expanded="true"] {
|
||||
@apply ring-1 shadow-sm;
|
||||
}
|
||||
|
||||
@@ -36,12 +36,16 @@ export function Footer(props: { context: GitBookSiteContext }) {
|
||||
<div className={tcls(CONTAINER_STYLE, 'px-4', 'py-8', 'lg:py-12', 'mx-auto')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'mx-auto grid max-w-3xl justify-between gap-12 lg:max-w-none',
|
||||
'lg:!max-w-none mx-auto grid max-w-3xl site-full-width:max-w-screen-2xl justify-between gap-12',
|
||||
'grid-cols-[auto_auto]',
|
||||
'lg:grid-cols-[18rem_minmax(auto,_48rem)_auto]',
|
||||
'xl:grid-cols-[18rem_minmax(auto,_48rem)_14rem]',
|
||||
'site-full-width:lg:grid-cols-[18rem_minmax(auto,_80rem)_auto]',
|
||||
'site-full-width:xl:grid-cols-[18rem_minmax(auto,_80rem)_14rem]',
|
||||
'page-no-toc:lg:grid-cols-[minmax(auto,_48rem)_auto]',
|
||||
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]'
|
||||
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]',
|
||||
'[body:has(.site-full-width,.page-no-toc)_&]:lg:grid-cols-[minmax(auto,_90rem)_auto]',
|
||||
'[body:has(.site-full-width,.page-no-toc)_&]:xl:grid-cols-[14rem_minmax(auto,_90rem)_14rem]'
|
||||
)}
|
||||
>
|
||||
{
|
||||
@@ -102,7 +106,7 @@ export function Footer(props: { context: GitBookSiteContext }) {
|
||||
'col-span-2 page-has-toc:lg:col-span-1 page-has-toc:lg:col-start-2 page-no-toc:xl:col-span-1 page-no-toc:xl:col-start-2'
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto flex max-w-3xl flex-col gap-10 sm:flex-row sm:gap-6">
|
||||
<div className="mx-auto flex max-w-3xl site-full-width:max-w-screen-2xl flex-col gap-10 sm:flex-row sm:gap-6">
|
||||
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
|
||||
(column, columnIndex) => (
|
||||
<div
|
||||
|
||||
@@ -104,11 +104,9 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
|
||||
'lg:basis-40',
|
||||
'md:max-w-[40%]',
|
||||
'lg:max-w-lg',
|
||||
'lg:ml-[max(calc((100%-18rem-48rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - margin (3rem)
|
||||
'lg:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem)
|
||||
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
|
||||
'page-no-toc:lg:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
|
||||
'page-full-width:lg:ml-[max(calc((100%-18rem-103rem-3rem)/2),1.5rem)]',
|
||||
'page-full-width:2xl:ml-[max(calc((100%-18rem-96rem-14rem+3rem)/2),1.5rem)]',
|
||||
'md:mr-auto',
|
||||
'order-last',
|
||||
'md:order-[unset]',
|
||||
@@ -195,7 +193,6 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
|
||||
<div
|
||||
className={tcls(
|
||||
CONTAINER_STYLE,
|
||||
'page-default-width:max-w-[unset]',
|
||||
'grow',
|
||||
'flex',
|
||||
'items-end',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { SiteSpace } from '@gitbook/api';
|
||||
|
||||
import { getSiteSpaceURL } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { joinPath } from '@/lib/paths';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { DropdownChevron, DropdownMenu } from './DropdownMenu';
|
||||
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
|
||||
@@ -14,7 +13,6 @@ export function SpacesDropdown(props: {
|
||||
className?: string;
|
||||
}) {
|
||||
const { context, siteSpace, siteSpaces, className } = props;
|
||||
const { linker } = context;
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
@@ -73,9 +71,7 @@ export function SpacesDropdown(props: {
|
||||
variantSpace={{
|
||||
id: otherSiteSpace.id,
|
||||
title: otherSiteSpace.title,
|
||||
url: otherSiteSpace.urls.published
|
||||
? linker.toLinkForContent(otherSiteSpace.urls.published)
|
||||
: getFallbackSiteSpaceURL(otherSiteSpace, context),
|
||||
url: getSiteSpaceURL(context, otherSiteSpace),
|
||||
}}
|
||||
active={otherSiteSpace.id === siteSpace.id}
|
||||
/>
|
||||
@@ -83,14 +79,3 @@ export function SpacesDropdown(props: {
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the site is not published yet, `urls.published` is not available.
|
||||
* To ensure navigation works in preview, we compute a relative URL from the siteSpace path.
|
||||
*/
|
||||
function getFallbackSiteSpaceURL(siteSpace: SiteSpace, context: GitBookSiteContext) {
|
||||
const { linker, sections } = context;
|
||||
return linker.toPathInSite(
|
||||
sections?.current ? joinPath(sections.current.path, siteSpace.path) : siteSpace.path
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import type { GitBookSiteContext, GitBookSpaceContext } from '@v2/lib/context';
|
||||
import { getPageDocument } from '@v2/lib/data';
|
||||
import type { GitBookLinker } from '@v2/lib/links';
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
@@ -29,6 +28,7 @@ import { PageControlButtons } from './PageControlButtons';
|
||||
import { PrintButton } from './PrintButton';
|
||||
import './pdf.css';
|
||||
import { sanitizeGitBookAppURL } from '@/lib/app';
|
||||
import { getPageDocument } from '@v2/lib/data';
|
||||
|
||||
const DEFAULT_LIMIT = 100;
|
||||
|
||||
@@ -224,8 +224,7 @@ async function PDFPageDocument(props: {
|
||||
context: GitBookSpaceContext;
|
||||
}) {
|
||||
const { page, context } = props;
|
||||
const { space } = context;
|
||||
const document = await getPageDocument(context.dataFetcher, space, page);
|
||||
const document = await getPageDocument(context, page);
|
||||
|
||||
return (
|
||||
<PrintPage id={getPagePDFContainerId(page)}>
|
||||
|
||||
@@ -48,11 +48,9 @@ export function PageAside(props: {
|
||||
'group/aside',
|
||||
'hidden',
|
||||
'xl:flex',
|
||||
// 'page-no-toc:lg:flex',
|
||||
'flex-col',
|
||||
'basis-56',
|
||||
// 'page-no-toc:basis-40',
|
||||
// 'page-no-toc:xl:basis-56',
|
||||
'xl:ml-12',
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
'break-anywhere', // To prevent long words in headings from breaking the layout
|
||||
|
||||
@@ -27,7 +27,9 @@ export function PageBody(props: {
|
||||
const { page, context, ancestors, document, withPageFeedback } = props;
|
||||
const { customization } = context;
|
||||
|
||||
const asFullWidth = document ? hasFullWidthBlock(document) : false;
|
||||
const contentFullWidth = document ? hasFullWidthBlock(document) : false;
|
||||
const pageFullWidth = page.id === 'wtthNFMqmEQmnt5LKR0q';
|
||||
const asFullWidth = pageFullWidth || contentFullWidth;
|
||||
const language = getSpaceLanguage(customization);
|
||||
const updatedAt = page.updatedAt ?? page.createdAt;
|
||||
|
||||
@@ -36,15 +38,11 @@ export function PageBody(props: {
|
||||
<main
|
||||
className={tcls(
|
||||
'relative min-w-0 flex-1',
|
||||
'py-8 lg:px-12',
|
||||
'mx-auto max-w-screen-2xl py-8',
|
||||
// Allow words to break if they are too long.
|
||||
'break-anywhere',
|
||||
// When in api page mode without the aside, we align with the border of the main content
|
||||
'page-api-block:xl:max-2xl:pr-0',
|
||||
// Max size to ensure one column in api is aligned with rest of content (2 x 3xl) + (gap-3 + 2) * px-12
|
||||
'page-api-block:mx-auto page-api-block:max-w-screen-2xl',
|
||||
// page.layout.tableOfContents ? null : 'xl:ml-56',
|
||||
asFullWidth ? 'page-full-width' : 'page-default-width',
|
||||
pageFullWidth ? 'page-full-width 2xl:px-8' : 'page-default-width',
|
||||
asFullWidth ? 'site-full-width' : 'site-default-width',
|
||||
page.layout.tableOfContents ? 'page-has-toc' : 'page-no-toc'
|
||||
)}
|
||||
>
|
||||
@@ -81,7 +79,7 @@ export function PageBody(props: {
|
||||
<PageFooterNavigation context={context} page={page} />
|
||||
) : null}
|
||||
|
||||
<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
|
||||
<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl page-full-width:max-w-screen-2xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
|
||||
{updatedAt ? (
|
||||
<p className="mr-auto text-sm">
|
||||
{t(language, 'page_last_modified', <DateRelative value={updatedAt} />)}
|
||||
|
||||
@@ -34,14 +34,20 @@ export async function PageCover(props: {
|
||||
? [
|
||||
'sm:-mx-6',
|
||||
'md:-mx-8',
|
||||
'-lg:mr-8',
|
||||
'lg:ml-0',
|
||||
'lg:-mr-8',
|
||||
'lg:-ml-12',
|
||||
!page.layout.tableOfContents &&
|
||||
context.customization.header.preset !== 'none'
|
||||
? 'xl:-ml-64'
|
||||
? 'xl:-ml-[19rem]'
|
||||
: null,
|
||||
]
|
||||
: ['sm:mx-auto', 'max-w-3xl', 'sm:rounded-md', 'mb-8']
|
||||
: [
|
||||
'sm:mx-auto',
|
||||
'max-w-3xl ',
|
||||
'page-full-width:max-w-screen-2xl',
|
||||
'sm:rounded-md',
|
||||
'mb-8',
|
||||
]
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
|
||||
@@ -32,8 +32,8 @@ export async function PageFooterNavigation(props: {
|
||||
'mt-6',
|
||||
'gap-2',
|
||||
'max-w-3xl',
|
||||
'page-full-width:max-w-screen-2xl',
|
||||
'mx-auto',
|
||||
'page-api-block:ml-0',
|
||||
'text-tint'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,14 @@ export async function PageHeader(props: {
|
||||
|
||||
return (
|
||||
<header
|
||||
className={tcls('max-w-3xl', 'mx-auto', 'mb-6', 'space-y-3', 'page-api-block:ml-0')}
|
||||
className={tcls(
|
||||
'max-w-3xl',
|
||||
'page-full-width:max-w-screen-2xl',
|
||||
'mx-auto',
|
||||
'mb-6',
|
||||
'space-y-3',
|
||||
'page-api-block:ml-0'
|
||||
)}
|
||||
>
|
||||
{ancestors.length > 0 && (
|
||||
<nav>
|
||||
|
||||
@@ -3,12 +3,12 @@ import * as React from 'react';
|
||||
|
||||
/**
|
||||
* This component preserves the layout of the page while loading a new one.
|
||||
* This approach is needed as page layout (full width block) is done using CSS (`body:has(.page-full-width)`),
|
||||
* This approach is needed as page layout (full width block) is done using CSS (`body:has(.full-width)`),
|
||||
* which becomes false while transitioning between the 2 page states:
|
||||
*
|
||||
* 1. Page 1 with full width block: `body:has(.page-full-width)` is true
|
||||
* 2. Loading skeleton while transitioning to page 2: `body:has(.page-full-width)` is false
|
||||
* 3. Page 2 with full width block: `body:has(.page-full-width)` is true
|
||||
* 1. Page 1 with full width block: `body:has(.site-full-width)` is true
|
||||
* 2. Loading skeleton while transitioning to page 2: `body:has(.site-full-width)` is false
|
||||
* 3. Page 2 with full width block: `body:has(.site-full-width)` is true
|
||||
*
|
||||
* This component ensures that the layout is preserved while transitioning between the 2 page states (in step 2).
|
||||
*/
|
||||
@@ -24,9 +24,9 @@ export function PreservePageLayout(props: { asFullWidth: boolean }) {
|
||||
}
|
||||
|
||||
if (asFullWidth) {
|
||||
header.classList.add('page-full-width');
|
||||
header.classList.add('site-full-width');
|
||||
} else {
|
||||
header.classList.remove('page-full-width');
|
||||
header.classList.remove('site-full-width');
|
||||
}
|
||||
}, [asFullWidth]);
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ export function PageFeedbackForm(props: {
|
||||
<div className="rounded-full border border-tint-subtle bg-tint-base contrast-more:border-tint-12">
|
||||
<div className="flex">
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Bad}
|
||||
label={tString(languages, 'was_this_helpful_negative')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Bad)}
|
||||
active={rating === PageFeedbackRating.Bad}
|
||||
rating={PageFeedbackRating.Good}
|
||||
label={tString(languages, 'was_this_helpful_positive')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Good)}
|
||||
active={rating === PageFeedbackRating.Good}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
@@ -78,10 +78,10 @@ export function PageFeedbackForm(props: {
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Good}
|
||||
label={tString(languages, 'was_this_helpful_positive')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Good)}
|
||||
active={rating === PageFeedbackRating.Good}
|
||||
rating={PageFeedbackRating.Bad}
|
||||
label={tString(languages, 'was_this_helpful_negative')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Bad)}
|
||||
active={rating === PageFeedbackRating.Bad}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -143,10 +143,6 @@
|
||||
margin-right: 0;
|
||||
width: calc(100% - var(--scrollbar-width));
|
||||
}
|
||||
body:has(.page-full-width) .scroll-nojump {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.elevate-link {
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function SitePage(props: SitePageProps) {
|
||||
const withSections = Boolean(sections && sections.list.length > 0);
|
||||
const headerOffset = { sectionsHeader: withSections, topHeader: withTopHeader };
|
||||
|
||||
const document = await getPageDocument(context.dataFetcher, context.space, page);
|
||||
const document = await getPageDocument(context, page);
|
||||
|
||||
return (
|
||||
<PageContextProvider pageId={page.id} spaceId={context.space.id} title={page.title}>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function SitePageSkeleton() {
|
||||
'lg:items-start'
|
||||
)}
|
||||
>
|
||||
<div className={tcls('flex-1', 'max-w-3xl', 'mx-auto', 'page-full-width:mx-0')}>
|
||||
<div className={tcls('flex-1', 'max-w-3xl', 'mx-auto', 'site-full-width:mx-0')}>
|
||||
<SkeletonHeading style={tcls('mb-8')} />
|
||||
<SkeletonParagraph style={tcls('mb-4')} />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getSectionURL, getSiteSpaceURL } from '@/lib/sites';
|
||||
import type { SiteSection, SiteSectionGroup } from '@gitbook/api';
|
||||
import type { GitBookSiteContext, SiteSections } from '@v2/lib/context';
|
||||
|
||||
@@ -46,15 +47,35 @@ export function encodeClientSiteSections(context: GitBookSiteContext, sections:
|
||||
}
|
||||
|
||||
function encodeSection(context: GitBookSiteContext, section: SiteSection) {
|
||||
const { linker } = context;
|
||||
return {
|
||||
id: section.id,
|
||||
title: section.title,
|
||||
description: section.description,
|
||||
icon: section.icon,
|
||||
object: section.object,
|
||||
url: section.urls.published
|
||||
? linker.toLinkForContent(section.urls.published)
|
||||
: linker.toPathInSite(section.path),
|
||||
url: findBestTargetURL(context, section),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best default site space to navigate to for a givent section:
|
||||
* 1. If we are on the default, continue on the default.
|
||||
* 2. If a site space has the same path as the current one, return it.
|
||||
* 3. Otherwise, return the default one.
|
||||
*/
|
||||
function findBestTargetURL(context: GitBookSiteContext, section: SiteSection) {
|
||||
const { siteSpace: currentSiteSpace } = context;
|
||||
|
||||
if (section.siteSpaces.length === 1 || currentSiteSpace.default) {
|
||||
return getSectionURL(context, section);
|
||||
}
|
||||
|
||||
const bestMatch = section.siteSpaces.find(
|
||||
(siteSpace) => siteSpace.path === currentSiteSpace.path
|
||||
);
|
||||
if (bestMatch) {
|
||||
return getSiteSpaceURL(context, bestMatch);
|
||||
}
|
||||
|
||||
return getSectionURL(context, section);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ export function SpaceLayout(props: {
|
||||
'flex-col',
|
||||
'lg:flex-row',
|
||||
CONTAINER_STYLE,
|
||||
'site-full-width:max-w-full',
|
||||
|
||||
// Ensure the footer is display below the viewport even if the content is not enough
|
||||
withFooter && 'min-h-[calc(100vh-64px)]',
|
||||
|
||||
@@ -13,7 +13,7 @@ export function TOCPageIcon({ page }: { page: RevisionPage }) {
|
||||
page={page}
|
||||
style={tcls(
|
||||
'text-base',
|
||||
'text-tint-strong/6',
|
||||
'[.toclink_&]:text-tint-strong/6',
|
||||
'group-aria-current-page/toclink:text-primary-subtle',
|
||||
'contrast-more:group-aria-current-page/toclink:text-primary',
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export function TableOfContents(props: {
|
||||
'relative',
|
||||
'z-[1]',
|
||||
'lg:sticky',
|
||||
'lg:mr-12',
|
||||
|
||||
// Server-side static positioning
|
||||
'lg:top-0',
|
||||
|
||||
@@ -67,7 +67,7 @@ function LinkItem(
|
||||
insights={insights}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
className={tcls(
|
||||
'group/toclink relative transition-colors',
|
||||
'group/toclink toclink relative transition-colors',
|
||||
'flex flex-row justify-between',
|
||||
'circular-corners:rounded-2xl rounded-md straight-corners:rounded-none p-1.5 pl-3',
|
||||
'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong',
|
||||
|
||||
@@ -14,7 +14,7 @@ export const CONTAINER_STYLE: ClassValue = [
|
||||
'md:px-8',
|
||||
'max-w-screen-2xl',
|
||||
'mx-auto',
|
||||
'page-full-width:max-w-full',
|
||||
// 'site-full-width:max-w-full',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,10 +106,7 @@ export const LinkOverlay = React.forwardRef(function LinkOverlay(
|
||||
<Link
|
||||
ref={ref}
|
||||
{...domProps}
|
||||
className={tcls(
|
||||
'link-overlay static before:absolute before:top-0 before:left-0 before:z-10 before:h-full before:w-full',
|
||||
className
|
||||
)}
|
||||
className={tcls('link-overlay absolute inset-0 z-10', className)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
|
||||
@@ -493,6 +493,39 @@ export const getRevisionPageByPath = cache({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a document from a page by its ID
|
||||
*/
|
||||
export const getRevisionPageDocument = cache({
|
||||
name: 'api.getRevisionPageDocument.v1',
|
||||
tag: (spaceId, revisionId) =>
|
||||
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
|
||||
tagImmutable: true,
|
||||
getKeySuffix: getAPIContextId,
|
||||
get: async (
|
||||
spaceId: string,
|
||||
revisionId: string,
|
||||
pageId: string,
|
||||
options: CacheFunctionOptions
|
||||
) => {
|
||||
const apiCtx = await api();
|
||||
const response = await apiCtx.client.spaces.getPageDocumentInRevisionById(
|
||||
spaceId,
|
||||
revisionId,
|
||||
pageId,
|
||||
{
|
||||
evaluated: true,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
signal: options.signal,
|
||||
}
|
||||
);
|
||||
|
||||
return cacheResponse(response, cacheTtl_7days);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve a file by its ID.
|
||||
* It should not be used directly, use `getRevisionFile` instead.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { JSONDocument } from '@gitbook/api';
|
||||
import type { DocumentBlock, JSONDocument } from '@gitbook/api';
|
||||
import type { GitBookAnyContext } from '@v2/lib/context';
|
||||
|
||||
import { getNodeText } from './document';
|
||||
@@ -19,58 +19,102 @@ export interface DocumentSection {
|
||||
export async function getDocumentSections(
|
||||
context: GitBookAnyContext,
|
||||
document: JSONDocument
|
||||
): Promise<DocumentSection[]> {
|
||||
return getSectionsFromNodes(document.nodes, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a list of sections from a list of nodes.
|
||||
*/
|
||||
async function getSectionsFromNodes(
|
||||
nodes: DocumentBlock[],
|
||||
context: GitBookAnyContext
|
||||
): Promise<DocumentSection[]> {
|
||||
const sections: DocumentSection[] = [];
|
||||
let depth = 0;
|
||||
|
||||
for (const block of document.nodes) {
|
||||
if ((block.type === 'heading-1' || block.type === 'heading-2') && block.meta?.id) {
|
||||
if (block.type === 'heading-1') {
|
||||
for (const block of nodes) {
|
||||
switch (block.type) {
|
||||
case 'heading-1': {
|
||||
const id = block.meta?.id;
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
depth = 1;
|
||||
}
|
||||
const title = getNodeText(block);
|
||||
const id = block.meta.id;
|
||||
|
||||
sections.push({
|
||||
id,
|
||||
title,
|
||||
depth: block.type === 'heading-1' ? 1 : depth > 0 ? 2 : 1,
|
||||
});
|
||||
}
|
||||
|
||||
if ((block.type === 'swagger' || block.type === 'openapi-operation') && block.meta?.id) {
|
||||
const { data: operation } = await resolveOpenAPIOperationBlock({
|
||||
block,
|
||||
context,
|
||||
});
|
||||
if (operation) {
|
||||
const title = getNodeText(block);
|
||||
sections.push({
|
||||
id: block.meta.id,
|
||||
tag: operation.method.toUpperCase(),
|
||||
title: operation.operation.summary || operation.path,
|
||||
depth: 1,
|
||||
deprecated: operation.operation.deprecated,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
block.type === 'openapi-schemas' &&
|
||||
!block.data.grouped &&
|
||||
block.meta?.id &&
|
||||
block.data.schemas.length === 1
|
||||
) {
|
||||
const { data } = await resolveOpenAPISchemasBlock({
|
||||
block,
|
||||
context,
|
||||
});
|
||||
const schema = data?.schemas[0];
|
||||
if (schema) {
|
||||
sections.push({
|
||||
id: block.meta.id,
|
||||
title: `The ${schema.name} object`,
|
||||
id,
|
||||
title,
|
||||
depth: 1,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
case 'heading-2': {
|
||||
const id = block.meta?.id;
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
const title = getNodeText(block);
|
||||
sections.push({
|
||||
id,
|
||||
title,
|
||||
depth: depth > 0 ? 2 : 1,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
case 'stepper': {
|
||||
const stepNodes = await Promise.all(
|
||||
block.nodes.map(async (step) => getSectionsFromNodes(step.nodes, context))
|
||||
);
|
||||
for (const stepSections of stepNodes) {
|
||||
sections.push(...stepSections);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case 'swagger':
|
||||
case 'openapi-operation': {
|
||||
const id = block.meta?.id;
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
const { data: operation } = await resolveOpenAPIOperationBlock({
|
||||
block,
|
||||
context,
|
||||
});
|
||||
if (operation) {
|
||||
sections.push({
|
||||
id,
|
||||
tag: operation.method.toUpperCase(),
|
||||
title: operation.operation.summary || operation.path,
|
||||
depth: 1,
|
||||
deprecated: operation.operation.deprecated,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case 'openapi-schemas': {
|
||||
const id = block.meta?.id;
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
if (block.data.grouped || block.data.schemas.length !== 1) {
|
||||
// Skip grouped schemas, they are not sections
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data } = await resolveOpenAPISchemasBlock({
|
||||
block,
|
||||
context,
|
||||
});
|
||||
const schema = data?.schemas[0];
|
||||
if (schema) {
|
||||
sections.push({
|
||||
id,
|
||||
title: `The ${schema.name} object`,
|
||||
depth: 1,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export async function resolveContentRef(
|
||||
|
||||
// Compute the text to display for the link
|
||||
if (anchor) {
|
||||
text = `#${anchor}`;
|
||||
text = page.title;
|
||||
ancestors.push({
|
||||
label: page.title,
|
||||
icon: <PageIcon page={page} style={iconStyle} />,
|
||||
@@ -155,7 +155,7 @@ export async function resolveContentRef(
|
||||
});
|
||||
|
||||
if (resolveAnchorText) {
|
||||
const document = await getPageDocument(dataFetcher, space, page);
|
||||
const document = await getPageDocument(context, page);
|
||||
if (document) {
|
||||
const block = getBlockById(document, anchor);
|
||||
if (block) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { SiteSection, SiteSectionGroup, SiteSpace, SiteStructure } from '@gitbook/api';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { joinPath } from './paths';
|
||||
|
||||
/**
|
||||
* Get all sections from a site structure.
|
||||
@@ -64,6 +66,34 @@ export function findSiteSpaceById(siteStructure: SiteStructure, spaceId: string)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to navigate to for a section.
|
||||
* When the site is not published yet, `urls.published` is not available.
|
||||
* To ensure navigation works in preview, we compute a relative URL from the siteSection path.
|
||||
*/
|
||||
export function getSectionURL(context: GitBookSiteContext, section: SiteSection) {
|
||||
const { linker } = context;
|
||||
return section.urls.published
|
||||
? linker.toLinkForContent(section.urls.published)
|
||||
: linker.toPathInSite(section.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to navigate to for a site space.
|
||||
* When the site is not published yet, `urls.published` is not available.
|
||||
* To ensure navigation works in preview, we compute a relative URL from the siteSpace path.
|
||||
*/
|
||||
export function getSiteSpaceURL(context: GitBookSiteContext, siteSpace: SiteSpace) {
|
||||
const { linker, sections } = context;
|
||||
if (siteSpace.urls.published) {
|
||||
return linker.toLinkForContent(siteSpace.urls.published);
|
||||
}
|
||||
|
||||
return linker.toPathInSite(
|
||||
sections?.current ? joinPath(sections.current.path, siteSpace.path) : siteSpace.path
|
||||
);
|
||||
}
|
||||
|
||||
function findSiteSpaceByIdInSections(sections: SiteSection[], spaceId: string): SiteSpace | null {
|
||||
for (const section of sections) {
|
||||
const siteSpace =
|
||||
|
||||
@@ -4,3 +4,10 @@
|
||||
export function filterOutNullable<T>(value: T): value is NonNullable<T> {
|
||||
return !!value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to `assertNever` that returns `null` instead of throwing an error.
|
||||
*/
|
||||
export function nullIfNever(_value: never): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getRevision,
|
||||
getRevisionFile,
|
||||
getRevisionPageByPath,
|
||||
getRevisionPageDocument,
|
||||
getRevisionPages,
|
||||
getSiteRedirectBySource,
|
||||
getSpace,
|
||||
@@ -241,6 +242,18 @@ function getDataFetcherV1(apiTokenOverride?: string): GitBookDataFetcher {
|
||||
);
|
||||
},
|
||||
|
||||
getRevisionPageDocument(params) {
|
||||
return withAPI(() =>
|
||||
wrapDataFetcherError(async () => {
|
||||
return getRevisionPageDocument(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.pageId
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
getRevisionPageByPath(params) {
|
||||
return withAPI(() =>
|
||||
wrapDataFetcherError(async () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
|
||||
import { getCloudflareContext as getCloudflareContextV2 } from '@v2/lib/data/cloudflare';
|
||||
import { isV2 } from './v2';
|
||||
|
||||
let pendings: Array<Promise<unknown>> = [];
|
||||
|
||||
@@ -47,6 +49,14 @@ export async function waitUntil(promise: Promise<unknown>) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isV2()) {
|
||||
const context = getCloudflareContextV2();
|
||||
if (context) {
|
||||
context.ctx.waitUntil(promise);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const cloudflareContext = await getGlobalContext();
|
||||
if ('waitUntil' in cloudflareContext) {
|
||||
cloudflareContext.waitUntil(promise);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CustomizationDefaultFont, CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import { colorContrast } from '@gitbook/colors';
|
||||
import { type FontWeight, getDefaultFont } from '@gitbook/fonts';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { ImageResponse } from 'next/og';
|
||||
|
||||
@@ -11,24 +12,6 @@ import { getCacheTag } from '@gitbook/cache-tags';
|
||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||
import { getResizedImageURL } from '@v2/lib/images';
|
||||
|
||||
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
|
||||
[CustomizationDefaultFont.Inter]: 'Inter',
|
||||
[CustomizationDefaultFont.FiraSans]: 'Fira Sans Extra Condensed',
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: 'IBM Plex Serif',
|
||||
[CustomizationDefaultFont.Lato]: 'Lato',
|
||||
[CustomizationDefaultFont.Merriweather]: 'Merriweather',
|
||||
[CustomizationDefaultFont.NotoSans]: 'Noto Sans',
|
||||
[CustomizationDefaultFont.OpenSans]: 'Open Sans',
|
||||
[CustomizationDefaultFont.Overpass]: 'Overpass',
|
||||
[CustomizationDefaultFont.Poppins]: 'Poppins',
|
||||
[CustomizationDefaultFont.Raleway]: 'Raleway',
|
||||
[CustomizationDefaultFont.Roboto]: 'Roboto',
|
||||
[CustomizationDefaultFont.RobotoSlab]: 'Roboto Slab',
|
||||
[CustomizationDefaultFont.SourceSansPro]: 'Source Sans 3',
|
||||
[CustomizationDefaultFont.Ubuntu]: 'Ubuntu',
|
||||
[CustomizationDefaultFont.ABCFavorit]: 'Inter',
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the OpenGraph image for a site content.
|
||||
*/
|
||||
@@ -65,19 +48,15 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
const { fontFamily, fonts } = await (async () => {
|
||||
// google fonts
|
||||
if (typeof customization.styling.font === 'string') {
|
||||
const fontFamily = googleFontsMap[customization.styling.font] ?? 'Inter';
|
||||
const fontFamily = customization.styling.font ?? CustomizationDefaultFont.Inter;
|
||||
|
||||
const regularText = pageDescription;
|
||||
const boldText = `${contentTitle}${pageTitle}`;
|
||||
|
||||
const fonts = (
|
||||
await Promise.all([
|
||||
getWithCache(`google-font:${fontFamily}:400`, () =>
|
||||
loadGoogleFont({ fontFamily, text: regularText, weight: 400 })
|
||||
),
|
||||
getWithCache(`google-font:${fontFamily}:700`, () =>
|
||||
loadGoogleFont({ fontFamily, text: boldText, weight: 700 })
|
||||
),
|
||||
loadGoogleFont({ font: fontFamily, text: regularText, weight: 400 }),
|
||||
loadGoogleFont({ font: fontFamily, text: boldText, weight: 700 }),
|
||||
])
|
||||
).filter(filterOutNullable);
|
||||
|
||||
@@ -260,37 +239,31 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
||||
);
|
||||
}
|
||||
|
||||
async function loadGoogleFont(input: { fontFamily: string; text: string; weight: 400 | 700 }) {
|
||||
const { fontFamily, text, weight } = input;
|
||||
async function loadGoogleFont(input: {
|
||||
font: CustomizationDefaultFont;
|
||||
text: string;
|
||||
weight: FontWeight;
|
||||
}) {
|
||||
const lookup = getDefaultFont({
|
||||
font: input.font,
|
||||
text: input.text,
|
||||
weight: input.weight,
|
||||
});
|
||||
|
||||
if (!text.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = new URL('https://fonts.googleapis.com/css2');
|
||||
url.searchParams.set('family', `${fontFamily}:wght@${weight}`);
|
||||
url.searchParams.set('text', text);
|
||||
|
||||
const result = await fetch(url.href);
|
||||
if (!result.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const css = await result.text();
|
||||
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
||||
const resourceUrl = resource ? resource[1] : null;
|
||||
|
||||
if (resourceUrl) {
|
||||
const response = await fetch(resourceUrl);
|
||||
if (response.ok) {
|
||||
const data = await response.arrayBuffer();
|
||||
return {
|
||||
name: fontFamily,
|
||||
data,
|
||||
style: 'normal' as const,
|
||||
weight,
|
||||
};
|
||||
}
|
||||
// If we found a font file, load it
|
||||
if (lookup) {
|
||||
return getWithCache(`google-font-files:${lookup.url}`, async () => {
|
||||
const response = await fetch(lookup.url);
|
||||
if (response.ok) {
|
||||
const data = await response.arrayBuffer();
|
||||
return {
|
||||
name: lookup.font,
|
||||
data,
|
||||
style: 'normal' as const,
|
||||
weight: input.weight,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If for some reason we can't load the font, we'll just use the default one
|
||||
|
||||
@@ -447,6 +447,13 @@ const config: Config = {
|
||||
},
|
||||
},
|
||||
opacity: opacity(),
|
||||
screens: {
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
'2xl': '1536px',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
plugin(({ addVariant }) => {
|
||||
@@ -514,8 +521,9 @@ const config: Config = {
|
||||
/**
|
||||
* Variant when the page contains a block that will be rendered in full-width mode.
|
||||
*/
|
||||
addVariant('site-full-width', 'body:has(.site-full-width) &');
|
||||
addVariant('site-default-width', 'body:has(.site-default-width) &');
|
||||
addVariant('page-full-width', 'body:has(.page-full-width) &');
|
||||
addVariant('page-default-width', 'body:has(.page-default-width) &');
|
||||
|
||||
/**
|
||||
* Variant when the page is configured to hide the table of content.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { mock } from 'bun:test';
|
||||
|
||||
/**
|
||||
* Mock the `server-only` module to avoid errors when running tests as it doesn't work well in Bun
|
||||
*/
|
||||
mock.module('server-only', () => {
|
||||
return {};
|
||||
});
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "bun ./bin/gen-list.js",
|
||||
"build": "tsc",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "tsc -w",
|
||||
"clean": "rm -rf ./dist && rm -rf ./src/data",
|
||||
@@ -41,7 +41,7 @@
|
||||
"bin": {
|
||||
"gitbook-icons": "./bin/gitbook-icons.js"
|
||||
},
|
||||
"files": ["dist", "src", "bin", "data", "README.md", "CHANGELOG.md"],
|
||||
"files": ["dist", "src", "bin", "README.md", "CHANGELOG.md"],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "src/**/*.test.ts"]
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.5.1",
|
||||
"@gitbook/api": "^0.119.0",
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/icons": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -27,12 +27,14 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
||||
{propertyName}
|
||||
</span>
|
||||
) : null}
|
||||
<span>
|
||||
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
||||
{additionalItems ? (
|
||||
<span className="openapi-schema-type">{additionalItems}</span>
|
||||
) : null}
|
||||
</span>
|
||||
{type || additionalItems ? (
|
||||
<span>
|
||||
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
||||
{additionalItems ? (
|
||||
<span className="openapi-schema-type">{additionalItems}</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
{schema?.readOnly ? (
|
||||
<span className="openapi-schema-readonly">
|
||||
{t(context.translation, 'read_only')}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { InteractiveSection } from './InteractiveSection';
|
||||
import { Markdown } from './Markdown';
|
||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
@@ -105,13 +107,7 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
||||
/>
|
||||
);
|
||||
case 'oauth2':
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="OAuth2"
|
||||
required={security.required}
|
||||
/>
|
||||
);
|
||||
return <OpenAPISchemaOAuth2Flows context={context} security={security} />;
|
||||
case 'openIdConnect':
|
||||
return (
|
||||
<OpenAPISchemaName
|
||||
@@ -125,3 +121,111 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
||||
return security.type;
|
||||
}
|
||||
}
|
||||
|
||||
function OpenAPISchemaOAuth2Flows(props: {
|
||||
context: OpenAPIClientContext;
|
||||
security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean };
|
||||
}) {
|
||||
const { context, security } = props;
|
||||
|
||||
const flows = Object.entries(security.flows ?? {});
|
||||
|
||||
return (
|
||||
<div className="openapi-securities-oauth-flows">
|
||||
{flows.map(([name, flow], index) => (
|
||||
<OpenAPISchemaOAuth2Item
|
||||
key={index}
|
||||
flow={flow}
|
||||
name={name}
|
||||
context={context}
|
||||
security={security}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPISchemaOAuth2Item(props: {
|
||||
flow: NonNullable<OpenAPIV3.OAuth2SecurityScheme['flows']>[keyof NonNullable<
|
||||
OpenAPIV3.OAuth2SecurityScheme['flows']
|
||||
>];
|
||||
name: string;
|
||||
context: OpenAPIClientContext;
|
||||
security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean };
|
||||
}) {
|
||||
const { flow, context, security, name } = props;
|
||||
|
||||
if (!flow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scopes = Object.entries(flow?.scopes ?? {});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<OpenAPISchemaName
|
||||
context={context}
|
||||
propertyName="OAuth2"
|
||||
type={name}
|
||||
required={security.required}
|
||||
/>
|
||||
<div className="openapi-securities-oauth-content openapi-markdown">
|
||||
{security.description ? <Markdown source={security.description} /> : null}
|
||||
{'authorizationUrl' in flow && flow.authorizationUrl ? (
|
||||
<span>
|
||||
Authorization URL:{' '}
|
||||
<OpenAPICopyButton
|
||||
value={flow.authorizationUrl}
|
||||
context={context}
|
||||
className="openapi-securities-url"
|
||||
withTooltip
|
||||
>
|
||||
{flow.authorizationUrl}
|
||||
</OpenAPICopyButton>
|
||||
</span>
|
||||
) : null}
|
||||
{'tokenUrl' in flow && flow.tokenUrl ? (
|
||||
<span>
|
||||
Token URL:{' '}
|
||||
<OpenAPICopyButton
|
||||
value={flow.tokenUrl}
|
||||
context={context}
|
||||
className="openapi-securities-url"
|
||||
withTooltip
|
||||
>
|
||||
{flow.tokenUrl}
|
||||
</OpenAPICopyButton>
|
||||
</span>
|
||||
) : null}
|
||||
{'refreshUrl' in flow && flow.refreshUrl ? (
|
||||
<span>
|
||||
Refresh URL:{' '}
|
||||
<OpenAPICopyButton
|
||||
value={flow.refreshUrl}
|
||||
context={context}
|
||||
className="openapi-securities-url"
|
||||
withTooltip
|
||||
>
|
||||
{flow.refreshUrl}
|
||||
</OpenAPICopyButton>
|
||||
</span>
|
||||
) : null}
|
||||
{scopes.length ? (
|
||||
<div>
|
||||
{t(context.translation, 'available_scopes')}:{' '}
|
||||
<ul>
|
||||
{scopes.map(([key, value]) => (
|
||||
<li key={key}>
|
||||
<OpenAPICopyButton value={key} context={context} withTooltip>
|
||||
<code>{key}</code>
|
||||
</OpenAPICopyButton>
|
||||
: {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export const de = {
|
||||
show: 'Zeige ${1}',
|
||||
hide: 'Verstecke ${1}',
|
||||
available_items: 'Verfügbare Elemente',
|
||||
available_scopes: 'Verfügbare scopes',
|
||||
properties: 'Eigenschaften',
|
||||
or: 'oder',
|
||||
and: 'und',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const en = {
|
||||
show: 'Show ${1}',
|
||||
hide: 'Hide ${1}',
|
||||
available_items: 'Available items',
|
||||
available_scopes: 'Available scopes',
|
||||
possible_values: 'Possible values',
|
||||
properties: 'Properties',
|
||||
or: 'or',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const es = {
|
||||
show: 'Mostrar ${1}',
|
||||
hide: 'Ocultar ${1}',
|
||||
available_items: 'Elementos disponibles',
|
||||
available_scopes: 'Scopes disponibles',
|
||||
properties: 'Propiedades',
|
||||
or: 'o',
|
||||
and: 'y',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const fr = {
|
||||
show: 'Afficher ${1}',
|
||||
hide: 'Masquer ${1}',
|
||||
available_items: 'Éléments disponibles',
|
||||
available_scopes: 'Scopes disponibles',
|
||||
properties: 'Propriétés',
|
||||
or: 'ou',
|
||||
and: 'et',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const ja = {
|
||||
show: '${1}を表示',
|
||||
hide: '${1}を非表示',
|
||||
available_items: '利用可能なアイテム',
|
||||
available_scopes: '利用可能なスコープ',
|
||||
properties: 'プロパティ',
|
||||
or: 'または',
|
||||
and: 'かつ',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const nl = {
|
||||
show: 'Toon ${1}',
|
||||
hide: 'Verberg ${1}',
|
||||
available_items: 'Beschikbare items',
|
||||
available_scopes: 'Beschikbare scopes',
|
||||
properties: 'Eigenschappen',
|
||||
or: 'of',
|
||||
and: 'en',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const no = {
|
||||
show: 'Vis ${1}',
|
||||
hide: 'Skjul ${1}',
|
||||
available_items: 'Tilgjengelige elementer',
|
||||
available_scopes: 'Tilgjengelige scopes',
|
||||
properties: 'Egenskaper',
|
||||
or: 'eller',
|
||||
and: 'og',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const pt_br = {
|
||||
show: 'Mostrar ${1}',
|
||||
hide: 'Ocultar ${1}',
|
||||
available_items: 'Itens disponíveis',
|
||||
available_scopes: 'Scopes disponíveis',
|
||||
properties: 'Propriedades',
|
||||
or: 'ou',
|
||||
and: 'e',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const zh = {
|
||||
show: '显示${1}',
|
||||
hide: '隐藏${1}',
|
||||
available_items: '可用项',
|
||||
available_scopes: '可用范围',
|
||||
properties: '属性',
|
||||
or: '或',
|
||||
and: '和',
|
||||
|
||||
Reference in New Issue
Block a user