mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-13 22:29:22 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e30c249498 | |||
| 5288f76ded |
@@ -1,8 +0,0 @@
|
|||||||
# Changesets
|
|
||||||
|
|
||||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
||||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
||||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
||||||
|
|
||||||
We have a quick list of common questions to get you started engaging with this project in
|
|
||||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
|
|
||||||
"changelog": "@changesets/cli/changelog",
|
|
||||||
"commit": false,
|
|
||||||
"fixed": [],
|
|
||||||
"linked": [],
|
|
||||||
"access": "public",
|
|
||||||
"baseBranch": "main",
|
|
||||||
"updateInternalDependencies": "patch",
|
|
||||||
"ignore": []
|
|
||||||
}
|
|
||||||
+10
-10
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
projectName: ${{ vars.CLOUDFLARE_PROJECT_NAME }}
|
projectName: ${{ vars.CLOUDFLARE_PROJECT_NAME }}
|
||||||
directory: ./packages/gitbook/.vercel/output/static
|
directory: ./.vercel/output/static
|
||||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
workingDirectory: ./
|
workingDirectory: ./
|
||||||
- name: Outputs
|
- name: Outputs
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
- name: Setup Playwright
|
- name: Setup Playwright
|
||||||
@@ -102,13 +102,13 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
PUPPETEER_SKIP_DOWNLOAD: 1
|
||||||
- name: Run pagespeed tests
|
- name: Run pagespeed tests
|
||||||
run: bun ./packages/gitbook/tests/pagespeed-testing.ts $DEPLOYMENT_URL
|
run: bun ./tests/pagespeed-testing.ts $DEPLOYMENT_URL
|
||||||
env:
|
env:
|
||||||
DEPLOYMENT_URL: ${{needs.deploy.outputs.deployment_url}}
|
DEPLOYMENT_URL: ${{needs.deploy.outputs.deployment_url}}
|
||||||
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
|
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
|
||||||
@@ -121,12 +121,12 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
PUPPETEER_SKIP_DOWNLOAD: 1
|
||||||
- run: bun format:check
|
- run: bun format --check .
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Lint
|
name: Lint
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
@@ -151,7 +151,7 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
- name: Setup bun
|
- name: Setup bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.1.18
|
bun-version: 1.0.33
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Publish
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup bun
|
|
||||||
uses: oven-sh/setup-bun@v1
|
|
||||||
with:
|
|
||||||
bun-version: 1.1.18
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install --frozen-lockfile
|
|
||||||
env:
|
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
|
||||||
id: changesets
|
|
||||||
uses: changesets/action@v1
|
|
||||||
with:
|
|
||||||
publish: npm run release
|
|
||||||
env:
|
|
||||||
# Using a PAT instead of GITHUB_TOKEN because we need to run workflows when releases are created
|
|
||||||
# https://github.com/orgs/community/discussions/26875#discussioncomment-3253761
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
+29
-3
@@ -5,6 +5,16 @@ node_modules
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
@@ -14,9 +24,25 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# Turbo
|
# local env files
|
||||||
.turbo
|
.env*.local
|
||||||
|
|
||||||
# Vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# visual tests
|
||||||
|
screenshots/
|
||||||
|
|
||||||
|
# Sentry Config File
|
||||||
|
.sentryclirc
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|
||||||
|
# Generated public files
|
||||||
|
/public/~gitbook/static/
|
||||||
|
|||||||
+1
-6
@@ -1,6 +1 @@
|
|||||||
.next
|
.next
|
||||||
.vercel
|
|
||||||
|
|
||||||
# Generated
|
|
||||||
packages/emoji-codepoints/index.ts
|
|
||||||
packages/gitbook/public/~gitbook/static/
|
|
||||||
@@ -119,12 +119,6 @@ GitBook wouldn't be possible without these projects:
|
|||||||
- [Tailwind CSS](https://tailwindcss.com/)
|
- [Tailwind CSS](https://tailwindcss.com/)
|
||||||
- [Framer Motion](https://www.npmjs.com/package/framer-motion)
|
- [Framer Motion](https://www.npmjs.com/package/framer-motion)
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
<a href="https://github.com/gitbookIO/gitbook/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=gitbookIO/gitbook" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Legacy GitBook (Deprecated)
|
## Legacy GitBook (Deprecated)
|
||||||
|
|
||||||
Our previous version of GitBook and it's CLI tool are now deprecated. You can still view the old repository and it's commits on this [branch](https://github.com/GitbookIO/gitbook/tree/legacy).
|
Our previous version of GitBook and it's CLI tool are now deprecated. You can still view the old repository and it's commits on this [branch](https://github.com/GitbookIO/gitbook/tree/legacy).
|
||||||
|
|||||||
@@ -106,93 +106,6 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'GitBook Site (Navigation when switching variant)',
|
|
||||||
baseUrl: 'https://gitbook-open-e2e-sites.gitbook.io/',
|
|
||||||
tests: [
|
|
||||||
{
|
|
||||||
name: 'Keep navigation path/route when switching variant (Public)',
|
|
||||||
url: 'api-multi-versions/reference/api-reference/pets',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const spaceDrowpdown = await page.waitForSelector(
|
|
||||||
'[data-testid="space-dropdown-button"]',
|
|
||||||
);
|
|
||||||
await spaceDrowpdown.click();
|
|
||||||
|
|
||||||
// Click the second variant in the dropdown
|
|
||||||
await page
|
|
||||||
.getByRole('link', {
|
|
||||||
name: '2.0',
|
|
||||||
})
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// It should keep the current page path, i.e "reference/api-reference/pets" when navigating to the new variant
|
|
||||||
await page.waitForURL(
|
|
||||||
'https://gitbook-open-e2e-sites.gitbook.io/api-multi-versions/v/2.0/reference/api-reference/pets?fallback=true',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Keep navigation path/route when switching variant (Share link)',
|
|
||||||
url: 'api-multi-versions-share-links/bRfQbzwsK8rbN1GRxx7K/reference/api-reference/pets',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const spaceDrowpdown = await page.waitForSelector(
|
|
||||||
'[data-testid="space-dropdown-button"]',
|
|
||||||
);
|
|
||||||
await spaceDrowpdown.click();
|
|
||||||
|
|
||||||
// Click the second variant in the dropdown
|
|
||||||
await page
|
|
||||||
.getByRole('link', {
|
|
||||||
name: '2.0',
|
|
||||||
})
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// It should keep the current page path, i.e "reference/api-reference/pets" when navigating to the new variant
|
|
||||||
await page.waitForURL(
|
|
||||||
'https://gitbook-open-e2e-sites.gitbook.io/api-multi-versions-share-links/bRfQbzwsK8rbN1GRxx7K/v/2.0/reference/api-reference/pets?fallback=true',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Keep navigation path/route when switching variant (VA)',
|
|
||||||
screenshot: false,
|
|
||||||
url: (() => {
|
|
||||||
const privateKey = 'c26190fc-74b2-4b54-9fc7-df9941104953';
|
|
||||||
const token = jwt.sign(
|
|
||||||
{
|
|
||||||
name: 'gitbook-open-tests',
|
|
||||||
},
|
|
||||||
privateKey,
|
|
||||||
{
|
|
||||||
expiresIn: '24h',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return `api-multi-versions-va/reference/api-reference/pets?jwt_token=${token}`;
|
|
||||||
})(),
|
|
||||||
run: async (page) => {
|
|
||||||
const spaceDrowpdown = await page.waitForSelector(
|
|
||||||
'[data-testid="space-dropdown-button"]',
|
|
||||||
);
|
|
||||||
await spaceDrowpdown.click();
|
|
||||||
|
|
||||||
// Click the second variant in the dropdown
|
|
||||||
await page
|
|
||||||
.getByRole('link', {
|
|
||||||
name: '2.0',
|
|
||||||
})
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// It should keep the current page path, i.e "reference/api-reference/pets" when navigating to the new variant
|
|
||||||
await page.waitForURL(
|
|
||||||
'https://gitbook-open-e2e-sites.gitbook.io/api-multi-versions-va/v/2.0/reference/api-reference/pets?fallback=true',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'GitBook',
|
name: 'GitBook',
|
||||||
baseUrl: 'https://docs.gitbook.com',
|
baseUrl: 'https://docs.gitbook.com',
|
||||||
@@ -363,11 +276,6 @@ const testCases: TestsCase[] = [
|
|||||||
name: 'Page options',
|
name: 'Page options',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
|
||||||
name: 'Hidden',
|
|
||||||
url: 'page-options/page-hidden',
|
|
||||||
run: waitForCookiesDialog,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'With cover',
|
name: 'With cover',
|
||||||
url: 'page-options/page-with-cover',
|
url: 'page-options/page-with-cover',
|
||||||
@@ -2,6 +2,8 @@ const { withSentryConfig } = require('@sentry/nextjs');
|
|||||||
|
|
||||||
module.exports = withSentryConfig(
|
module.exports = withSentryConfig(
|
||||||
{
|
{
|
||||||
|
output: 'standalone',
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
BUILD_VERSION: (process.env.GITHUB_SHA ?? '').slice(0, 7),
|
BUILD_VERSION: (process.env.GITHUB_SHA ?? '').slice(0, 7),
|
||||||
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
||||||
@@ -9,7 +11,7 @@ module.exports = withSentryConfig(
|
|||||||
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack(config, { dev, webpack }) {
|
webpack(config) {
|
||||||
config.resolve.fallback = {
|
config.resolve.fallback = {
|
||||||
...config.resolve.fallback,
|
...config.resolve.fallback,
|
||||||
|
|
||||||
@@ -19,21 +21,6 @@ module.exports = withSentryConfig(
|
|||||||
http: false,
|
http: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tree shake debug code for Sentry
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/#tree-shaking-with-nextjs
|
|
||||||
if (!dev) {
|
|
||||||
config.plugins.push(
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
__SENTRY_DEBUG__: false,
|
|
||||||
// We always init Sentry with enableTracing: false for now, so this is useless
|
|
||||||
__SENTRY_TRACING__: false,
|
|
||||||
__RRWEB_EXCLUDE_IFRAME__: true,
|
|
||||||
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
|
|
||||||
__SENTRY_EXCLUDE_REPLAY_WORKER__: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -60,9 +47,9 @@ module.exports = withSentryConfig(
|
|||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: '*.gitbook.io',
|
hostname: '*.gitbook.io',
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
silent: true,
|
silent: true,
|
||||||
+82
-16
@@ -2,29 +2,95 @@
|
|||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "bun@1.1.18",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run dev",
|
"dev": "next dev",
|
||||||
"build": "turbo run build",
|
"build": "next build",
|
||||||
"build:cloudflare": "turbo run build:cloudflare",
|
"build:cloudflare": "next-on-pages",
|
||||||
"lint": "turbo run lint",
|
"start": "next start",
|
||||||
"typecheck": "turbo run typecheck",
|
"lint": "next lint",
|
||||||
"format": "prettier ./ --ignore-unknown --write",
|
"format": "prettier ./ --ignore-unknown --write",
|
||||||
"format:check": "prettier ./ --ignore-unknown --list-different",
|
"format:check": "prettier ./ --ignore-unknown --list-different",
|
||||||
"unit": "turbo run unit",
|
"typecheck": "tsc --noEmit",
|
||||||
"e2e": "turbo run e2e",
|
"unit": "bun test {src,packages}/**/*.test.ts",
|
||||||
"changeset": "changeset",
|
"e2e": "playwright test",
|
||||||
"release": "turbo run build && changeset publish"
|
"postinstall": "rm -rf ./public/~gitbook/static/mathjax@3.2.2 && mkdir -p ./public/~gitbook/static/ && cp -R node_modules/mathjax/es5 ./public/~gitbook/static/mathjax@3.2.2"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"dependencies": {
|
||||||
"@changesets/cli": "^2.27.7",
|
"@geist-ui/icons": "^1.0.2",
|
||||||
"prettier": "^3.0.3",
|
"@gitbook/api": "^0.48.0",
|
||||||
"turbo": "^2.0.9"
|
"@gitbook/react-math": "workspace:*",
|
||||||
|
"@gitbook/react-openapi": "workspace:*",
|
||||||
|
"@gitbook/react-contentkit": "workspace:*",
|
||||||
|
"@gitbook/emoji-codepoints": "workspace:*",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"@sentry/nextjs": "^7.94.1",
|
||||||
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"@upstash/redis": "^1.27.1",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"assert-never": "^1.2.1",
|
||||||
|
"bun-types": "^1.0.7",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
|
"content-security-policy-merger": "^1.0.0",
|
||||||
|
"framer-motion": "^10.16.14",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"jsontoxml": "^1.0.1",
|
||||||
|
"katex": "^0.16.9",
|
||||||
|
"mathjax": "^3.2.2",
|
||||||
|
"memoizee": "^0.4.15",
|
||||||
|
"next": "^14.1.3",
|
||||||
|
"next-themes": "^0.2.1",
|
||||||
|
"nuqs": "^1.15.4",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"openapi-types": "^12.1.3",
|
||||||
|
"p-map": "^7.0.0",
|
||||||
|
"parse-cache-control": "^1.0.1",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
|
"recoil": "^0.7.7",
|
||||||
|
"rehype-sanitize": "^6.0.0",
|
||||||
|
"rehype-stringify": "^10.0.0",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.0",
|
||||||
|
"rison": "^0.1.1",
|
||||||
|
"server-only": "^0.0.1",
|
||||||
|
"shiki": "^1.2.0",
|
||||||
|
"tailwind-merge": "^2.2.0",
|
||||||
|
"tailwind-shades": "^1.1.2",
|
||||||
|
"unified": "^11.0.4",
|
||||||
|
"url-join": "^5.0.0"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"devDependencies": {
|
||||||
"@vercel/next@4.0.15": "patches/@vercel%2Fnext@4.0.15.patch"
|
"@argos-ci/playwright": "^2.0.0",
|
||||||
|
"@cloudflare/next-on-pages": "^1.11.3",
|
||||||
|
"@cloudflare/workers-types": "^4.20231218.0",
|
||||||
|
"@playwright/test": "^1.42.1",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/jsontoxml": "^1.0.5",
|
||||||
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
|
"@types/katex": "^0.16.5",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/object-hash": "^3.0.6",
|
||||||
|
"@types/parse-cache-control": "^1.0.4",
|
||||||
|
"@types/psi": "^4.1.6",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@types/rison": "^0.0.9",
|
||||||
|
"autoprefixer": "^10",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "13.5.6",
|
||||||
|
"eslint-plugin-import": "^2.29.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"postcss": "^8",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"psi": "^4.1.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# @gitbook/emoji-codepoints
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 57adb3e: Second release to fix publishing with changeset
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 5f8a8fe: Initial release
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/emoji-codepoints",
|
"name": "@gitbook/emoji-codepoints",
|
||||||
"description": "Optimized mapping of codepoints to the fully qualified emoji codepoints",
|
"description": "Optimized mapping of codepoints to the fully qualified emoji codepoints",
|
||||||
"version": "0.2.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"exports": "./index.ts",
|
"exports": "./index.ts",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
||||||
|
|
||||||
# visual tests
|
|
||||||
screenshots/
|
|
||||||
|
|
||||||
# Sentry Config File
|
|
||||||
.sentryclirc
|
|
||||||
|
|
||||||
/test-results/
|
|
||||||
/playwright-report/
|
|
||||||
/blob-report/
|
|
||||||
/playwright/.cache/
|
|
||||||
|
|
||||||
# Generated public files
|
|
||||||
/public/~gitbook/static/
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# gitbook
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 24b785c: Update shiki for code block syntax highlighting, with support for more languages and fixes for diffs. It also patches the deployment on Cloudflare to support edge functions larger than 4MB.
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- acc3f2f: Fix error with the "Try it" of OpenAPI block because of the Scalar proxy failing on Cloudflare with the `cache` option
|
|
||||||
- Updated dependencies [709f1a1]
|
|
||||||
- Updated dependencies [ede2335]
|
|
||||||
- Updated dependencies [0426312]
|
|
||||||
- @gitbook/react-openapi@0.6.0
|
|
||||||
|
|
||||||
## 0.2.2
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [3445db4]
|
|
||||||
- @gitbook/react-contentkit@0.5.0
|
|
||||||
- @gitbook/react-openapi@0.5.0
|
|
||||||
- @gitbook/react-math@0.5.0
|
|
||||||
|
|
||||||
## 0.2.1
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [24cd72e]
|
|
||||||
- @gitbook/react-contentkit@0.4.0
|
|
||||||
- @gitbook/react-math@0.4.0
|
|
||||||
- @gitbook/react-openapi@0.4.0
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- de747b7: Refactor the repository to be a proper monorepo and publish JS files on NPM instead of TypeScript files.
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [de747b7]
|
|
||||||
- Updated dependencies [de747b7]
|
|
||||||
- @gitbook/react-contentkit@0.3.0
|
|
||||||
- @gitbook/react-openapi@0.3.0
|
|
||||||
- @gitbook/react-math@0.3.0
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "gitbook",
|
|
||||||
"version": "0.3.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"build": "next build",
|
|
||||||
"build:cloudflare": "next-on-pages",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"e2e": "playwright test",
|
|
||||||
"unit": "bun test {src,packages}",
|
|
||||||
"postinstall": "rm -rf ./public/~gitbook/static/mathjax@3.2.2 && mkdir -p ./public/~gitbook/static/ && cp -R ../../node_modules/mathjax/es5 ./public/~gitbook/static/mathjax@3.2.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@geist-ui/icons": "^1.0.2",
|
|
||||||
"@gitbook/api": "^0.56.0",
|
|
||||||
"@gitbook/react-math": "workspace:*",
|
|
||||||
"@gitbook/react-openapi": "workspace:*",
|
|
||||||
"@gitbook/react-contentkit": "workspace:*",
|
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
|
||||||
"@sentry/nextjs": "^7.94.1",
|
|
||||||
"@sindresorhus/fnv1a": "^3.1.0",
|
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
|
||||||
"@upstash/redis": "^1.27.1",
|
|
||||||
"ajv": "^8.12.0",
|
|
||||||
"assert-never": "^1.2.1",
|
|
||||||
"bun-types": "^1.1.20",
|
|
||||||
"classnames": "^2.5.1",
|
|
||||||
"content-security-policy-merger": "^1.0.0",
|
|
||||||
"framer-motion": "^10.16.14",
|
|
||||||
"js-cookie": "^3.0.5",
|
|
||||||
"jsontoxml": "^1.0.1",
|
|
||||||
"katex": "^0.16.9",
|
|
||||||
"mathjax": "^3.2.2",
|
|
||||||
"memoizee": "^0.4.15",
|
|
||||||
"next": "^14.1.3",
|
|
||||||
"next-themes": "^0.2.1",
|
|
||||||
"nuqs": "^1.17.4",
|
|
||||||
"object-hash": "^3.0.0",
|
|
||||||
"openapi-types": "^12.1.3",
|
|
||||||
"p-map": "^7.0.0",
|
|
||||||
"parse-cache-control": "^1.0.1",
|
|
||||||
"react": "^18",
|
|
||||||
"react-dom": "^18",
|
|
||||||
"react-hotkeys-hook": "^4.4.1",
|
|
||||||
"recoil": "^0.7.7",
|
|
||||||
"rehype-sanitize": "^6.0.0",
|
|
||||||
"rehype-stringify": "^10.0.0",
|
|
||||||
"remark-gfm": "^4.0.0",
|
|
||||||
"remark-parse": "^11.0.0",
|
|
||||||
"remark-rehype": "^11.1.0",
|
|
||||||
"rison": "^0.1.1",
|
|
||||||
"server-only": "^0.0.1",
|
|
||||||
"shiki": "^1.11.1",
|
|
||||||
"tailwind-merge": "^2.2.0",
|
|
||||||
"tailwind-shades": "^1.1.2",
|
|
||||||
"unified": "^11.0.4",
|
|
||||||
"url-join": "^5.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@argos-ci/playwright": "^2.0.0",
|
|
||||||
"@playwright/test": "^1.42.1",
|
|
||||||
"@cloudflare/next-on-pages": "^1.11.3",
|
|
||||||
"@cloudflare/workers-types": "^4.20231218.0",
|
|
||||||
"@types/js-cookie": "^3.0.6",
|
|
||||||
"@types/jsontoxml": "^1.0.5",
|
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
|
||||||
"@types/node": "^20",
|
|
||||||
"@types/object-hash": "^3.0.6",
|
|
||||||
"@types/parse-cache-control": "^1.0.4",
|
|
||||||
"@types/psi": "^4.1.6",
|
|
||||||
"@types/react": "^18.3.3",
|
|
||||||
"@types/react-dom": "^18.3.0",
|
|
||||||
"@types/rison": "^0.0.9",
|
|
||||||
"autoprefixer": "^10",
|
|
||||||
"eslint": "^8",
|
|
||||||
"eslint-config-next": "^14.2.5",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"jsonwebtoken": "^9.0.2",
|
|
||||||
"postcss": "^8",
|
|
||||||
"psi": "^4.1.0",
|
|
||||||
"tailwindcss": "^3.4.0",
|
|
||||||
"typescript": "^5.5.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import {
|
|
||||||
BrowserClient,
|
|
||||||
makeFetchTransport,
|
|
||||||
defaultStackParser,
|
|
||||||
getCurrentScope,
|
|
||||||
} from '@sentry/nextjs';
|
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
|
||||||
if (dsn) {
|
|
||||||
// To tree shake default integrations that we don't use
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/#tree-shaking-default-integrations
|
|
||||||
const client = new BrowserClient({
|
|
||||||
debug: false,
|
|
||||||
dsn,
|
|
||||||
integrations: [],
|
|
||||||
sampleRate: 0.1,
|
|
||||||
enableTracing: false,
|
|
||||||
beforeSendTransaction: () => {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
transport: makeFetchTransport,
|
|
||||||
stackParser: defaultStackParser,
|
|
||||||
});
|
|
||||||
|
|
||||||
getCurrentScope().setClient(client);
|
|
||||||
client.init();
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { useScrollToHash } from '@/components/hooks';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client component to initialize interactivity for a page.
|
|
||||||
*/
|
|
||||||
export function PageClientLayout(props: {}) {
|
|
||||||
// We use this hook in the page layout to ensure the elements for the blocks
|
|
||||||
// are rendered before we scroll to the hash.
|
|
||||||
useScrollToHash();
|
|
||||||
|
|
||||||
useStripFallbackQueryParam();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strip the fallback query parameter from current URL.
|
|
||||||
*
|
|
||||||
* When the user switches variants using the space dropdown, we pass a fallback=true parameter.
|
|
||||||
* This parameter indicates that we should redirect to the root page if the path from the
|
|
||||||
* previous variant doesn't exist in the new variant. If the path does exist, no redirect occurs,
|
|
||||||
* so we need to remove the fallback parameter.
|
|
||||||
*/
|
|
||||||
function useStripFallbackQueryParam() {
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (searchParams.has('fallback')) {
|
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
|
||||||
params.delete('fallback');
|
|
||||||
router.push(`${pathname}?${params.toString()}${window.location.hash ?? ''}`);
|
|
||||||
}
|
|
||||||
}, [router, pathname, searchParams]);
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import IconHeart from '@geist-ui/icons/heart';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
import { AdClassicRendering } from './AdClassicRendering';
|
|
||||||
import { AdCoverRendering } from './AdCoverRendering';
|
|
||||||
import { AdItem, AdsResponse } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch and render the Ad placement.
|
|
||||||
* https://docs.buysellads.com/ad-serving-api
|
|
||||||
*/
|
|
||||||
export function Ad({
|
|
||||||
zoneId,
|
|
||||||
spaceId,
|
|
||||||
placement,
|
|
||||||
ignore,
|
|
||||||
style,
|
|
||||||
mode = 'auto',
|
|
||||||
}: {
|
|
||||||
zoneId: string;
|
|
||||||
spaceId: string;
|
|
||||||
placement: string;
|
|
||||||
ignore: boolean;
|
|
||||||
style?: ClassValue;
|
|
||||||
mode?: 'classic' | 'auto' | 'cover';
|
|
||||||
}) {
|
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
const [visible, setVisible] = React.useState(false);
|
|
||||||
const [failed, setFailed] = React.useState(false);
|
|
||||||
const [ad, setAd] = React.useState<AdItem | undefined>(undefined);
|
|
||||||
|
|
||||||
// Observe the container visibility
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!containerRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
setVisible(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: null,
|
|
||||||
rootMargin: '0px',
|
|
||||||
threshold: 0.1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
observer.observe(containerRef.current);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// When the container is visible,
|
|
||||||
// track an impression on the ad and fetch it
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelled = false;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const url = new URL(`https://srv.buysellads.com/ads/${zoneId}.json`);
|
|
||||||
url.searchParams.set('segment', `placement:${placement}`);
|
|
||||||
url.searchParams.set('v', 'true');
|
|
||||||
if (ignore) {
|
|
||||||
url.searchParams.set('ignore', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(url);
|
|
||||||
const json: AdsResponse = await res.json();
|
|
||||||
|
|
||||||
if (cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = json.ads[0];
|
|
||||||
if (first && 'active' in first) {
|
|
||||||
setAd(first);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'Failed to fetch ad, it might have been blocked by a ad-blocker',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
setFailed(true);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, [visible, zoneId, ignore, placement]);
|
|
||||||
|
|
||||||
const viaUrl = new URL('https://www.gitbook.com');
|
|
||||||
viaUrl.searchParams.set('utm_source', 'content');
|
|
||||||
viaUrl.searchParams.set('utm_medium', 'ads');
|
|
||||||
viaUrl.searchParams.set('utm_campaign', spaceId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={containerRef} className={tcls(style)}>
|
|
||||||
{ad && ad.description && ad.statlink ? (
|
|
||||||
<>
|
|
||||||
{mode === 'classic' || !('callToAction' in ad) ? (
|
|
||||||
<AdClassicRendering ad={ad} />
|
|
||||||
) : (
|
|
||||||
<AdCoverRendering ad={ad} />
|
|
||||||
)}
|
|
||||||
{ad.pixel ? <AdPixels rawPixel={ad.pixel} /> : null}
|
|
||||||
<p
|
|
||||||
className={tcls(
|
|
||||||
'mt-2',
|
|
||||||
'mr-2',
|
|
||||||
'text-xs',
|
|
||||||
'text-right',
|
|
||||||
'text-dark/5',
|
|
||||||
'dark:text-light/5',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href={viaUrl.toString()}
|
|
||||||
className={tcls('hover:underline')}
|
|
||||||
>
|
|
||||||
Ads via GitBook
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : failed ? (
|
|
||||||
<AdBlockerPlaceholder />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render attribution or verification pixels.
|
|
||||||
* https://docs.buysellads.com/ad-serving-api#pixels
|
|
||||||
*/
|
|
||||||
function AdPixels({ rawPixel }: { rawPixel: string }) {
|
|
||||||
const pixels = rawPixel.split('||');
|
|
||||||
const time = String(Math.round(Date.now() / 1e4) | 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={tcls('hidden')}>
|
|
||||||
{pixels.map((pixel, index) => {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
key={index}
|
|
||||||
src={pixel.replace('[timestamp]', time)}
|
|
||||||
width="1"
|
|
||||||
height="1"
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
alt="Ads tracking pixel"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder when visitor has an ad-blocker.
|
|
||||||
*/
|
|
||||||
function AdBlockerPlaceholder() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={tcls(
|
|
||||||
'flex',
|
|
||||||
'flex-col',
|
|
||||||
'gap-3',
|
|
||||||
'bg-light-2',
|
|
||||||
'text-dark/7',
|
|
||||||
'dark:bg-dark-2',
|
|
||||||
'dark:text-light/7',
|
|
||||||
'rounded-lg',
|
|
||||||
'p-4',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className={tcls('flex', 'flex-row', 'gap-2', 'items-center')}>
|
|
||||||
<IconHeart className={tcls('w-4', 'h-4', 'text-primary-500')} />
|
|
||||||
<p className={tcls('text-xs', 'font-semibold')}>Ad disabled</p>
|
|
||||||
</div>
|
|
||||||
<p className={tcls('text-xs')}>
|
|
||||||
{`It looks like you're using an adblocker. Whitelist this site to help support this
|
|
||||||
project.`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
import { AdItem } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classic rendering for an ad.
|
|
||||||
*/
|
|
||||||
export function AdClassicRendering({ ad }: { ad: AdItem }) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className={tcls(
|
|
||||||
'flex',
|
|
||||||
'flex-col',
|
|
||||||
'gap-4',
|
|
||||||
'bg-light-2',
|
|
||||||
'text-dark/7',
|
|
||||||
'dark:bg-dark-2',
|
|
||||||
'dark:text-light/7',
|
|
||||||
'hover:text-dark/9',
|
|
||||||
'dark:hover:text-light/9',
|
|
||||||
'rounded-lg',
|
|
||||||
'p-4',
|
|
||||||
)}
|
|
||||||
href={ad.statlink}
|
|
||||||
rel="sponsored noopener"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{'smallImage' in ad ? (
|
|
||||||
<div>
|
|
||||||
<img alt="Ads logo" className={tcls('rounded-md')} src={ad.smallImage} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={tcls('px-6', 'py-4', 'rounded-md')}
|
|
||||||
style={{ backgroundColor: ad.backgroundColor }}
|
|
||||||
>
|
|
||||||
<img alt="Ads logo" src={ad.logo} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={tcls('flex', 'flex-col')}>
|
|
||||||
<div className={tcls('text-xs')}>{ad.description}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { hexToRgba } from '@/lib/colors';
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
import { AdCover } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cover rendering for an ad.
|
|
||||||
*/
|
|
||||||
export function AdCoverRendering({ ad }: { ad: AdCover }) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className={tcls(
|
|
||||||
'group/ad',
|
|
||||||
'relative',
|
|
||||||
'flex',
|
|
||||||
'flex-col',
|
|
||||||
'gap-4',
|
|
||||||
'bg-light-2',
|
|
||||||
'text-dark/7',
|
|
||||||
'dark:bg-dark-2',
|
|
||||||
'dark:text-light/7',
|
|
||||||
'hover:text-dark/9',
|
|
||||||
'dark:hover:text-light/9',
|
|
||||||
'rounded-lg',
|
|
||||||
'p-4',
|
|
||||||
'overflow-hidden',
|
|
||||||
'shadow-sm',
|
|
||||||
)}
|
|
||||||
style={{ backgroundColor: ad.backgroundColor, color: ad.textColor ?? '#ffffff' }}
|
|
||||||
href={ad.statlink}
|
|
||||||
rel="sponsored noopener"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={tcls(
|
|
||||||
'absolute',
|
|
||||||
'inset-0',
|
|
||||||
'bg-center',
|
|
||||||
'bg-cover',
|
|
||||||
'bg-no-repeat',
|
|
||||||
'z-0',
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${ad.largeImage})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={tcls('z-[2]')}>
|
|
||||||
<img
|
|
||||||
alt="Large image"
|
|
||||||
src={ad.largeImage}
|
|
||||||
className={tcls(
|
|
||||||
'rounded-md',
|
|
||||||
'shadow-md',
|
|
||||||
'max-h-32',
|
|
||||||
'group-hover/ad:max-h-16',
|
|
||||||
'transition-all',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={tcls('z-[2]')}>
|
|
||||||
<img alt={ad.company} src={ad.logo} className={tcls('max-w-36', 'max-h-12')} />
|
|
||||||
</div>
|
|
||||||
<div className={tcls('flex', 'flex-col', 'z-[2]')}>
|
|
||||||
<div className={tcls('text-sm', 'font-semibold', 'mb-2')}>{ad.companyTagline}</div>
|
|
||||||
<div
|
|
||||||
className={tcls(
|
|
||||||
'text-xs',
|
|
||||||
'h-0',
|
|
||||||
'opacity-0',
|
|
||||||
'group-hover/ad:h-16',
|
|
||||||
'group-hover/ad:opacity-10',
|
|
||||||
'transition-all',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{ad.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={tcls('z-[2]')}>
|
|
||||||
<span
|
|
||||||
className={tcls(
|
|
||||||
'text-sm',
|
|
||||||
'font-semibold',
|
|
||||||
'shadow-lg',
|
|
||||||
'rounded-md',
|
|
||||||
'bg-white',
|
|
||||||
'py-2',
|
|
||||||
'px-4',
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
backgroundColor: ad.ctaBackgroundColor,
|
|
||||||
color: ad.ctaTextColor ?? ad.backgroundColor,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ad.callToAction}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={tcls('absolute', 'inset-0', 'backdrop-blur', 'z-[1]')}
|
|
||||||
style={{
|
|
||||||
backgroundColor: hexToRgba(ad.backgroundColor, 0.8),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './Ad';
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
export interface AdGeneric {
|
|
||||||
active: string;
|
|
||||||
ad_via_link: string;
|
|
||||||
bannerid: string;
|
|
||||||
creativeid: string;
|
|
||||||
evenodd: string;
|
|
||||||
external_id: string;
|
|
||||||
height: string;
|
|
||||||
i: string;
|
|
||||||
identifier: string;
|
|
||||||
longimp: string;
|
|
||||||
longlink: string;
|
|
||||||
num_slots: string;
|
|
||||||
statimp: string;
|
|
||||||
statlink: string;
|
|
||||||
timestamp: string;
|
|
||||||
width: string;
|
|
||||||
zoneid: string;
|
|
||||||
zonekey: string;
|
|
||||||
rendering: 'carbon';
|
|
||||||
pixel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdClassic extends AdGeneric {
|
|
||||||
description: string;
|
|
||||||
smallImage: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdCover extends AdGeneric {
|
|
||||||
backgroundColor: string;
|
|
||||||
backgroundHoverColor?: string;
|
|
||||||
textColor?: string;
|
|
||||||
textColorHover?: string;
|
|
||||||
callToAction: string;
|
|
||||||
company: string;
|
|
||||||
companyTagline: string;
|
|
||||||
description: string;
|
|
||||||
largeImage: string;
|
|
||||||
image?: string;
|
|
||||||
logo: string;
|
|
||||||
ctaBackgroundColor?: string;
|
|
||||||
ctaBackgroundHoverColor?: string;
|
|
||||||
ctaTextColor?: string;
|
|
||||||
ctaTextColorHover?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AdItem = AdClassic | AdCover;
|
|
||||||
|
|
||||||
export interface AdsResponse {
|
|
||||||
ads: Array<AdItem | {}>;
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { DocumentBlockContentRef } from '@gitbook/api';
|
|
||||||
|
|
||||||
import { LogoIcon } from '@/components/icons';
|
|
||||||
import { Card, Emoji } from '@/components/primitives';
|
|
||||||
import { getSpaceCustomization, ignoreAPIError } from '@/lib/api';
|
|
||||||
import { ResolvedContentRef } from '@/lib/references';
|
|
||||||
|
|
||||||
import { BlockProps } from './Block';
|
|
||||||
|
|
||||||
export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>) {
|
|
||||||
const { block, context, style } = props;
|
|
||||||
|
|
||||||
const resolved = await context.resolveContentRef(block.data.ref, {
|
|
||||||
resolveAnchorText: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resolved) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isContentInOtherSpace =
|
|
||||||
context.contentRefContext?.space &&
|
|
||||||
'space' in block.data.ref &&
|
|
||||||
context.contentRefContext.space.id !== block.data.ref.space;
|
|
||||||
const kind = block?.data?.ref?.kind;
|
|
||||||
if ((resolved.active && kind === 'space') || isContentInOtherSpace) {
|
|
||||||
return <SpaceRefCard {...props} resolved={resolved} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
leadingIcon={resolved.emoji ? <Emoji code={resolved.emoji} style="text-xl" /> : null}
|
|
||||||
href={resolved.href}
|
|
||||||
title={resolved.text}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function SpaceRefCard(
|
|
||||||
props: { resolved: ResolvedContentRef } & BlockProps<DocumentBlockContentRef>,
|
|
||||||
) {
|
|
||||||
const { context, style, resolved } = props;
|
|
||||||
const spaceId = context.contentRefContext?.space.id;
|
|
||||||
|
|
||||||
if (!spaceId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceCustomization = await ignoreAPIError(getSpaceCustomization(spaceId));
|
|
||||||
const customFavicon = spaceCustomization?.favicon;
|
|
||||||
const customEmoji = customFavicon && 'emoji' in customFavicon ? customFavicon.emoji : undefined;
|
|
||||||
const customIcon = customFavicon && 'icon' in customFavicon ? customFavicon.icon : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
leadingIcon={
|
|
||||||
<LogoIcon
|
|
||||||
icon={customIcon}
|
|
||||||
emoji={customEmoji}
|
|
||||||
alt=""
|
|
||||||
sizes={[{ width: 24 }]}
|
|
||||||
style={['object-contain', 'size-6']}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
href={resolved.href}
|
|
||||||
title={resolved.text}
|
|
||||||
postTitle={resolved.subText}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { Space } from '@gitbook/api';
|
|
||||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
|
||||||
|
|
||||||
import { DropdownMenuItem } from './Dropdown';
|
|
||||||
|
|
||||||
function useVariantSpaceHref(variantSpaceUrl: string) {
|
|
||||||
const currentPathname = useSelectedLayoutSegment() ?? '';
|
|
||||||
const targetUrl = new URL(variantSpaceUrl);
|
|
||||||
targetUrl.pathname += `/${currentPathname}`;
|
|
||||||
targetUrl.pathname = targetUrl.pathname.replace(/\/{2,}/g, '/').replace(/\/$/, '');
|
|
||||||
targetUrl.searchParams.set('fallback', 'true');
|
|
||||||
|
|
||||||
return targetUrl.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SpacesDropdownMenuItem(props: {
|
|
||||||
variantSpace: { id: Space['id']; title: Space['title']; url: string };
|
|
||||||
active: boolean;
|
|
||||||
}) {
|
|
||||||
const { variantSpace, active } = props;
|
|
||||||
const variantHref = useVariantSpaceHref(variantSpace.url);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem key={variantSpace.id} href={variantHref} active={active}>
|
|
||||||
{variantSpace.title}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import type { RequestSiteTrackPageView, RequestSpaceTrackPageView } from '@gitbook/api';
|
|
||||||
import cookies from 'js-cookie';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { getVisitorId } from '@/lib/analytics';
|
|
||||||
import { SiteContentPointer } from '@/lib/api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the page view for the current page to integrations.
|
|
||||||
*/
|
|
||||||
export function TrackPageView(props: {
|
|
||||||
apiHost: string;
|
|
||||||
sitePointer?: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
|
|
||||||
spaceId: string;
|
|
||||||
pageId: string | undefined;
|
|
||||||
}) {
|
|
||||||
const { apiHost, sitePointer, spaceId, pageId } = props;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
trackPageView({ apiHost, sitePointer, spaceId, pageId });
|
|
||||||
}, [apiHost, spaceId, pageId, sitePointer]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendSpaceTrackPageViewRequest(args: {
|
|
||||||
apiHost: string;
|
|
||||||
spaceId: string;
|
|
||||||
body: RequestSpaceTrackPageView;
|
|
||||||
}) {
|
|
||||||
const { apiHost, spaceId, body } = args;
|
|
||||||
const url = new URL(apiHost);
|
|
||||||
url.pathname = `/v1/spaces/${spaceId}/insights/track_view`;
|
|
||||||
|
|
||||||
await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendSiteTrackPageViewRequest(args: {
|
|
||||||
apiHost: string;
|
|
||||||
sitePointer: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
|
|
||||||
body: RequestSiteTrackPageView;
|
|
||||||
}) {
|
|
||||||
const { apiHost, sitePointer, body } = args;
|
|
||||||
const url = new URL(apiHost);
|
|
||||||
url.pathname = `/v1/orgs/${sitePointer.organizationId}/sites/${sitePointer.siteId}/insights/track_view`;
|
|
||||||
|
|
||||||
await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let latestPageId: string | undefined | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the page view for the current page to GitBook.
|
|
||||||
* We don't use the API client to avoid shipping 80kb of JS to the client.
|
|
||||||
* And instead use a simple fetch.
|
|
||||||
*/
|
|
||||||
async function trackPageView(args: {
|
|
||||||
apiHost: string;
|
|
||||||
sitePointer?: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
|
|
||||||
spaceId: string;
|
|
||||||
pageId: string | undefined;
|
|
||||||
}) {
|
|
||||||
const { apiHost, sitePointer, pageId, spaceId } = args;
|
|
||||||
if (pageId === latestPageId) {
|
|
||||||
// The hook can be called multiple times, we only want to track once.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
latestPageId = pageId;
|
|
||||||
|
|
||||||
const visitorId = await getVisitorId();
|
|
||||||
const sharedTrackedProps = {
|
|
||||||
url: window.location.href,
|
|
||||||
pageId,
|
|
||||||
visitor: {
|
|
||||||
anonymousId: visitorId,
|
|
||||||
userAgent: window.navigator.userAgent,
|
|
||||||
language: window.navigator.language,
|
|
||||||
cookies: cookies.get(),
|
|
||||||
},
|
|
||||||
referrer: document.referrer,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
sitePointer
|
|
||||||
? await sendSiteTrackPageViewRequest({
|
|
||||||
apiHost,
|
|
||||||
sitePointer,
|
|
||||||
body: {
|
|
||||||
...sharedTrackedProps,
|
|
||||||
spaceId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: await sendSpaceTrackPageViewRequest({ apiHost, spaceId, body: sharedTrackedProps });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to track page view', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { CustomizationThemedURL } from '@gitbook/api';
|
|
||||||
|
|
||||||
import { Image } from '@/components/utils';
|
|
||||||
import { absoluteHref } from '@/lib/links';
|
|
||||||
|
|
||||||
import { Emoji } from '../primitives';
|
|
||||||
|
|
||||||
export function LogoIcon(
|
|
||||||
props: { icon?: CustomizationThemedURL; emoji?: string } & Omit<
|
|
||||||
React.ComponentProps<typeof Image>,
|
|
||||||
'sources'
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
const { icon, emoji, alt, ...imageProps } = props;
|
|
||||||
|
|
||||||
if (emoji && !icon) {
|
|
||||||
return <Emoji code={emoji} style="text-xl" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Image
|
|
||||||
alt={alt ?? ''}
|
|
||||||
sources={
|
|
||||||
icon
|
|
||||||
? {
|
|
||||||
light: {
|
|
||||||
src: icon.light,
|
|
||||||
aspectRatio: '1',
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
src: icon.dark,
|
|
||||||
aspectRatio: '1',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
light: {
|
|
||||||
src: absoluteHref('~gitbook/icon?size=medium&theme=light', true),
|
|
||||||
size: { width: 256, height: 256 },
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
src: absoluteHref('~gitbook/icon?size=medium&theme=dark', true),
|
|
||||||
size: { width: 256, height: 256 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{...imageProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './types';
|
|
||||||
export * from './LogoIcon';
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
export const de = {
|
|
||||||
locale: 'de',
|
|
||||||
powered_by_gitbook: 'Bereitgestellt von GitBook',
|
|
||||||
switch_to_dark_theme: 'Zum dunklen Modus wechseln',
|
|
||||||
switch_to_light_theme: 'Zum hellen Modus wechseln',
|
|
||||||
switch_to_system_theme: 'Zum Systemmodus wechseln',
|
|
||||||
search: 'Suche',
|
|
||||||
search_or_ask: 'Fragen oder Suchen',
|
|
||||||
search_input_placeholder: 'Inhalt durchsuchen',
|
|
||||||
search_ask_input_placeholder: 'Inhalt durchsuchen oder eine Frage stellen',
|
|
||||||
search_no_results: 'Keine Ergebnisse für "${1}".',
|
|
||||||
search_scope_space: 'Nur in ${1}',
|
|
||||||
search_scope_all: 'Alle Inhalte',
|
|
||||||
search_ask: 'Fragen "${1}"',
|
|
||||||
search_ask_sources: 'Quellen',
|
|
||||||
search_ask_sources_no_answer: 'Verwandte Seiten',
|
|
||||||
search_ask_no_answer:
|
|
||||||
'Es konnte keine Antwort auf Ihre Frage gefunden werden. Versuchen Sie, sie umzuformulieren oder genauer zu sein.',
|
|
||||||
search_ask_error: 'Etwas ist schief gelaufen. Bitte versuchen Sie es später noch einmal.',
|
|
||||||
on_this_page: 'Auf dieser Seite',
|
|
||||||
next_page: 'Nächste',
|
|
||||||
previous_page: 'Vorherige',
|
|
||||||
page_last_modified: 'Zuletzt aktualisiert ${1}',
|
|
||||||
was_this_helpful: 'War das hilfreich?',
|
|
||||||
was_this_helpful_positive: 'Ja, das war es!',
|
|
||||||
was_this_helpful_neutral: 'Nicht sicher',
|
|
||||||
was_this_helpful_negative: 'Nein',
|
|
||||||
was_this_helpful_thank_you: 'Danke!',
|
|
||||||
annotation_button_label: 'Kommentar öffnen',
|
|
||||||
code_copied: 'Kopiert!',
|
|
||||||
code_copy: 'Kopieren',
|
|
||||||
table_of_contents_button_label: 'Inhaltsverzeichnis öffnen',
|
|
||||||
cookies_title: 'Cookies',
|
|
||||||
cookies_prompt:
|
|
||||||
'Diese Website verwendet Cookies, um ihre Dienste bereitzustellen und den Datenverkehr zu analysieren. Durch die Nutzung dieser Website akzeptieren Sie die ${1}.',
|
|
||||||
cookies_prompt_privacy: 'Datenschutzrichtlinie',
|
|
||||||
cookies_accept: 'Akzeptieren',
|
|
||||||
cookies_reject: 'Ablehnen',
|
|
||||||
cookies_close: 'Schließen',
|
|
||||||
edit_on_git: 'Bearbeiten auf ${1}',
|
|
||||||
notfound_title: 'Seite nicht gefunden',
|
|
||||||
notfound: 'Die gesuchte Seite existiert nicht.',
|
|
||||||
unexpected_error_title: 'Ein Fehler ist aufgetreten',
|
|
||||||
unexpected_error:
|
|
||||||
'Entschuldigung, ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.',
|
|
||||||
unexpected_error_retry: 'Erneut versuchen',
|
|
||||||
pdf_download: 'Als PDF exportieren',
|
|
||||||
pdf_goback: 'Zurück zum Inhalt',
|
|
||||||
pdf_print: 'Drucken oder als PDF speichern',
|
|
||||||
pdf_page_of: '${1} von ${2}',
|
|
||||||
pdf_mode_only_page: 'Nur diese Seite',
|
|
||||||
pdf_mode_all: 'Alle Seiten',
|
|
||||||
pdf_limit_reached:
|
|
||||||
'Das PDF konnte für ${1} Seiten nicht generiert werden, Generierung wurde bei ${2} gestoppt.',
|
|
||||||
pdf_limit_reached_continue: 'Mit ${1} weiteren Seiten erweitern.',
|
|
||||||
};
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
export const pt_br = {
|
|
||||||
locale: 'pt-br',
|
|
||||||
powered_by_gitbook: 'Powered by GitBook',
|
|
||||||
switch_to_dark_theme: 'Mudar para modo escuro',
|
|
||||||
switch_to_light_theme: 'Mudar para modo claro',
|
|
||||||
switch_to_system_theme: 'Mudar para configuração do sistema',
|
|
||||||
search: 'Busca',
|
|
||||||
search_or_ask: 'Perguntar ou buscar',
|
|
||||||
search_input_placeholder: 'Buscar conteúdo',
|
|
||||||
search_ask_input_placeholder: 'Buscar conteúdo ou fazer uma pergunta',
|
|
||||||
search_no_results: 'Sem resultados para "${1}".',
|
|
||||||
search_scope_space: 'Somente em ${1}',
|
|
||||||
search_scope_all: 'Todo o conteúdo',
|
|
||||||
search_ask: 'Perguntar "${1}"',
|
|
||||||
search_ask_sources: 'Fontes',
|
|
||||||
search_ask_sources_no_answer: 'Páginas relacionadas',
|
|
||||||
search_ask_no_answer:
|
|
||||||
'Não foi possível encontrar uma resposta para sua pergunta. Você pode tentar reformulá-la ou ser mais específico.',
|
|
||||||
search_ask_error: 'Algo deu errado. Por favor tente novamente mais tarde.',
|
|
||||||
on_this_page: 'Nesta página',
|
|
||||||
next_page: 'Próximo',
|
|
||||||
previous_page: 'Anterior',
|
|
||||||
page_last_modified: 'Atualizado ${1}',
|
|
||||||
was_this_helpful: 'Isto foi útil?',
|
|
||||||
was_this_helpful_positive: 'Sim, foi!',
|
|
||||||
was_this_helpful_neutral: 'Não sei',
|
|
||||||
was_this_helpful_negative: 'Não',
|
|
||||||
was_this_helpful_thank_you: 'Obrigado!',
|
|
||||||
annotation_button_label: 'Abrir anotação',
|
|
||||||
code_copied: 'Copiado!',
|
|
||||||
code_copy: 'Copiar',
|
|
||||||
table_of_contents_button_label: 'Abrir o índice',
|
|
||||||
cookies_title: 'Cookies',
|
|
||||||
cookies_prompt:
|
|
||||||
'Este site usa cookies para entregar suas funcionalidades e analizar o tráfego. Ao navegar neste site, você aceita a ${1}.',
|
|
||||||
cookies_prompt_privacy: 'política de privacidade',
|
|
||||||
cookies_accept: 'Aceitar',
|
|
||||||
cookies_reject: 'Rejeitar',
|
|
||||||
cookies_close: 'Fechar',
|
|
||||||
edit_on_git: 'Editar no ${1}',
|
|
||||||
notfound_title: 'Página não encontrada',
|
|
||||||
notfound: 'A página que você está procurando não existe.',
|
|
||||||
unexpected_error_title: 'Aconteceu um erro',
|
|
||||||
unexpected_error:
|
|
||||||
'Desculpe, aconteceu um erro inesperado. Por favor tente novamente mais tarde.',
|
|
||||||
unexpected_error_retry: ' Tentar novamente',
|
|
||||||
pdf_download: 'Exportar como PDF',
|
|
||||||
pdf_goback: 'Voltar ao conteúdo',
|
|
||||||
pdf_print: 'Imprimir ou salvar como PDF',
|
|
||||||
pdf_page_of: '${1} de ${2}',
|
|
||||||
pdf_mode_only_page: 'Somente esta página',
|
|
||||||
pdf_mode_all: 'Todas as páginas',
|
|
||||||
pdf_limit_reached:
|
|
||||||
'Não foi possível gerar o PDF para ${1} páginas, generation stopped at ${2}.',
|
|
||||||
pdf_limit_reached_continue: 'Extender com mais ${1} páginas.',
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
Collection,
|
|
||||||
CustomizationSettings,
|
|
||||||
Site,
|
|
||||||
SiteCustomizationSettings,
|
|
||||||
Space,
|
|
||||||
} from '@gitbook/api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title to display for a content.
|
|
||||||
*/
|
|
||||||
export function getContentTitle(
|
|
||||||
space: Space,
|
|
||||||
customization: CustomizationSettings | SiteCustomizationSettings,
|
|
||||||
parent: Site | Collection | null,
|
|
||||||
) {
|
|
||||||
// When we are rendering a site, always give priority to the customization title first
|
|
||||||
// and then fallback to the site title
|
|
||||||
if (parent?.object === 'site') {
|
|
||||||
return customization.title ?? parent.title ?? space.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise the legacy behavior is not changed to avoid regressions
|
|
||||||
return parent ? parent.title : customization.title ?? space.title;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dist/
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# @gitbook/react-contentkit
|
|
||||||
|
|
||||||
## 0.5.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 3445db4: Fix files published in the NPM packages by defining "files" in "package.json"
|
|
||||||
|
|
||||||
## 0.4.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 24cd72e: Fix changeset CI workflow
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- de747b7: Publish to NPM with JS files and TypeScript declaration files
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 57adb3e: Second release to fix publishing with changeset
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 5f8a8fe: Initial release
|
|
||||||
@@ -1,33 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/react-contentkit",
|
"name": "@gitbook/react-contentkit",
|
||||||
"version": "0.5.0",
|
"exports": "./src/index.ts",
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"development": "./src/index.ts",
|
|
||||||
"default": "./dist/index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"@gitbook/api": "^0.51.0",
|
"@gitbook/api": "^0.48.0",
|
||||||
"assert-never": "^1.2.1"
|
"assert-never": "^1.2.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
},
|
}
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.5.3"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"dev": "tsc -w"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src",
|
|
||||||
"README.md",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { type ContentKitRenderOutput } from '@gitbook/api';
|
import { type ContentKitRenderOutput } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Element } from './Element';
|
import { Element } from './Element';
|
||||||
import { ContentKitServerContext } from './types';
|
import { ContentKitServerContext } from './types';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
ContentKitInlineElement,
|
ContentKitInlineElement,
|
||||||
ContentKitRootElement,
|
ContentKitRootElement,
|
||||||
} from '@gitbook/api';
|
} from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ElementBlock } from './ElementBlock';
|
import { ElementBlock } from './ElementBlock';
|
||||||
import { ElementWebframe } from './ElementWebframe';
|
import { ElementWebframe } from './ElementWebframe';
|
||||||
@@ -14,6 +13,7 @@ import { ElementCodeBlock } from './ElementCodeBlock';
|
|||||||
import { ElementImage } from './ElementImage';
|
import { ElementImage } from './ElementImage';
|
||||||
import { ElementStack } from './ElementStack';
|
import { ElementStack } from './ElementStack';
|
||||||
import { ElementText } from './ElementText';
|
import { ElementText } from './ElementText';
|
||||||
|
import React from 'react';
|
||||||
import { ElementButton } from './ElementButton';
|
import { ElementButton } from './ElementButton';
|
||||||
import { ElementModal } from './ElementModal';
|
import { ElementModal } from './ElementModal';
|
||||||
import { ElementBox } from './ElementBox';
|
import { ElementBox } from './ElementBox';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitBlock } from '@gitbook/api';
|
import { ContentKitBlock } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitBox } from '@gitbook/api';
|
import { ContentKitBox } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { ContentKitButton } from '@gitbook/api';
|
import { ContentKitButton } from '@gitbook/api';
|
||||||
|
|
||||||
import { ContentKitClientElementProps } from './types';
|
import { ContentKitClientElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useContentKitClientContext } from './context';
|
import { useContentKitClientContext } from './context';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export function ElementButton(
|
export function ElementButton(
|
||||||
props: ContentKitClientElementProps<ContentKitButton> & {
|
props: ContentKitClientElementProps<ContentKitButton> & {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitCodeBlock } from '@gitbook/api';
|
import { ContentKitCodeBlock } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
import { resolveDynamicBinding } from './dynamic';
|
import { resolveDynamicBinding } from './dynamic';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitCodeBlock } from '@gitbook/api';
|
import { ContentKitCodeBlock } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitClientElementProps } from './types';
|
import { ContentKitClientElementProps } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitDivider } from '@gitbook/api';
|
import { ContentKitDivider } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitIcon } from '@gitbook/api';
|
import { ContentKitIcon } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
import { ContentKitServerContext } from './types';
|
import { ContentKitServerContext } from './types';
|
||||||
|
|
||||||
export function ElementIcon(props: { icon: ContentKitIcon; context: ContentKitServerContext }) {
|
export function ElementIcon(props: { icon: ContentKitIcon; context: ContentKitServerContext }) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitImage } from '@gitbook/api';
|
import { ContentKitImage } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitMarkdown } from '@gitbook/api';
|
import { ContentKitMarkdown } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
import { ElementMarkdownClient } from './ElementMarkdownClient';
|
import { ElementMarkdownClient } from './ElementMarkdownClient';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { ContentKitMarkdown } from '@gitbook/api';
|
import { ContentKitMarkdown } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitClientElementProps } from './types';
|
import { ContentKitClientElementProps } from './types';
|
||||||
import { resolveDynamicBinding } from './dynamic';
|
import { resolveDynamicBinding } from './dynamic';
|
||||||
|
import React from 'react';
|
||||||
import { useContentKitClientContext } from './context';
|
import { useContentKitClientContext } from './context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ContentKitModal } from '@gitbook/api';
|
import { ContentKitModal } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitClientElementProps } from './types';
|
import { ContentKitClientElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useContentKitClientContext } from './context';
|
import { useContentKitClientContext } from './context';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export function ElementModal(
|
export function ElementModal(
|
||||||
props: ContentKitClientElementProps<ContentKitModal> & {
|
props: ContentKitClientElementProps<ContentKitModal> & {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitHStack, ContentKitVStack } from '@gitbook/api';
|
import { ContentKitHStack, ContentKitVStack } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContentKitText } from '@gitbook/api';
|
import { ContentKitText } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitServerElementProps } from './types';
|
import { ContentKitServerElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ContentKitTextInput } from '@gitbook/api';
|
import { ContentKitTextInput } from '@gitbook/api';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ContentKitClientElementProps } from './types';
|
import { ContentKitClientElementProps } from './types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useContentKitClientContext } from './context';
|
import { useContentKitClientContext } from './context';
|
||||||
|
import React from 'react';
|
||||||
import { getStateStringValue } from './dynamic';
|
import { getStateStringValue } from './dynamic';
|
||||||
|
|
||||||
export function ElementTextInput(props: ContentKitClientElementProps<ContentKitTextInput>) {
|
export function ElementTextInput(props: ContentKitClientElementProps<ContentKitTextInput>) {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": false,
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"incremental": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dist/
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# @gitbook/react-math
|
|
||||||
|
|
||||||
## 0.5.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 3445db4: Fix files published in the NPM packages by defining "files" in "package.json"
|
|
||||||
|
|
||||||
## 0.4.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 24cd72e: Fix changeset CI workflow
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- de747b7: Refactor the repository to be a proper monorepo and publish JS files on NPM instead of TypeScript files.
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 57adb3e: Second release to fix publishing with changeset
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 5f8a8fe: Initial release
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# `@gitbook/react-math`
|
|
||||||
|
|
||||||
React component to render a Math formula. It uses KaTeX when possible and fallbacks to MathJaX if needed.
|
|
||||||
@@ -1,33 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/react-math",
|
"name": "@gitbook/react-math",
|
||||||
"exports": {
|
"exports": "./src/index.ts",
|
||||||
".": {
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"development": "./src/index.ts",
|
|
||||||
"default": "./dist/index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": "0.5.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"object-hash": "^3.0.0"
|
"object-hash": "^3.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
},
|
}
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.5.3",
|
|
||||||
"@types/katex": "^0.16.5"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"dev": "tsc -w"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src",
|
|
||||||
"css",
|
|
||||||
"README.md",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { KaTeX } from './KaTeX';
|
import { KaTeX } from './KaTeX';
|
||||||
import { MathJaXLazy } from './MathJaXLazy';
|
import { MathJaXLazy } from './MathJaXLazy';
|
||||||
|
|
||||||
import '../css/default.css';
|
import './default.css';
|
||||||
|
|
||||||
export interface MathFormulaProps {
|
export interface MathFormulaProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
declare var MathJax: {
|
declare var MathJax: {
|
||||||
startup: {
|
startup: {
|
||||||
@@ -24,7 +24,6 @@ export interface MathJaXFormulaProps {
|
|||||||
export default function MathJaXFormula(props: MathJaXFormulaProps) {
|
export default function MathJaXFormula(props: MathJaXFormulaProps) {
|
||||||
const { formula, inline, className, mathJaxUrl } = props;
|
const { formula, inline, className, mathJaxUrl } = props;
|
||||||
|
|
||||||
// @ts-ignore - React.use doesn't seem define in typing
|
|
||||||
React.use(loadMathJaxScript(mathJaxUrl));
|
React.use(loadMathJaxScript(mathJaxUrl));
|
||||||
const [html, setHTML] = React.useState('');
|
const [html, setHTML] = React.useState('');
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": false,
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"incremental": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dist
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# @gitbook/react-openapi
|
|
||||||
|
|
||||||
## 0.6.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 709f1a1: Update Scalar to the latest version, with faster performances and an improved experience
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- ede2335: Fix x-codeSamples: false not working at the single operation level
|
|
||||||
- 0426312: Fix tabs being empty for code samples when they are updated dynamically
|
|
||||||
|
|
||||||
## 0.5.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 3445db4: Fix files published in the NPM packages by defining "files" in "package.json"
|
|
||||||
|
|
||||||
## 0.4.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 24cd72e: Fix changeset CI workflow
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- de747b7: Refactor the repository to be a proper monorepo and publish JS files on NPM instead of TypeScript files.
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- bd0ca5b: Fix missing react imports
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 57adb3e: Second release to fix publishing with changeset
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 5f8a8fe: Initial release
|
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
# `@gitbook/react-openapi`
|
# `@gitbook/react-openapi`
|
||||||
|
|
||||||
Style-less React components to render OpenAPI operation blocks.
|
React components to render OpenAPI operations.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Generate code samples for the request
|
||||||
|
- Support custom cde samples with `x-codeSamples` (Redocly syntax)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Support for trying out the request
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/react-openapi",
|
"name": "@gitbook/react-openapi",
|
||||||
"exports": {
|
"exports": "./src/index.ts",
|
||||||
".": {
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"development": "./src/index.ts",
|
|
||||||
"default": "./dist/index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": "0.6.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scalar/api-client-react": "1.0.5",
|
"@scalar/api-client-react": "^0.3.7",
|
||||||
|
"@scalar/oas-utils": "0.1.6",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"flatted": "^3.2.9",
|
"flatted": "^3.2.9",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
@@ -17,23 +11,9 @@
|
|||||||
"swagger2openapi": "^7.0.8"
|
"swagger2openapi": "^7.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/swagger2openapi": "^7.0.4",
|
"@types/swagger2openapi": "^7.0.4"
|
||||||
"bun-types": "^1.1.20",
|
|
||||||
"typescript": "^5.5.3"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
},
|
}
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"unit": "bun test",
|
|
||||||
"dev": "tsc -w"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src",
|
|
||||||
"README.md",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface InteractiveSectionTab {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
body: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To optimize rendering, most of the components are server-components,
|
* To optimize rendering, most of the components are server-components,
|
||||||
* and the interactiveness is mainly handled by a few key components like this one.
|
* and the interactiveness is mainly handled by a few key components like this one.
|
||||||
@@ -25,7 +19,11 @@ export function InteractiveSection(props: {
|
|||||||
toggleOpenIcon?: React.ReactNode;
|
toggleOpenIcon?: React.ReactNode;
|
||||||
toggleCloseIcon?: React.ReactNode;
|
toggleCloseIcon?: React.ReactNode;
|
||||||
/** Tabs of content to display */
|
/** Tabs of content to display */
|
||||||
tabs?: Array<InteractiveSectionTab>;
|
tabs?: Array<{
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
body: React.ReactNode;
|
||||||
|
}>;
|
||||||
/** Default tab to have opened */
|
/** Default tab to have opened */
|
||||||
defaultTab?: string;
|
defaultTab?: string;
|
||||||
/** Content of the header */
|
/** Content of the header */
|
||||||
@@ -50,9 +48,9 @@ export function InteractiveSection(props: {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [opened, setOpened] = React.useState(defaultOpened);
|
const [opened, setOpened] = React.useState(defaultOpened);
|
||||||
const [selectedTabKey, setSelectedTab] = React.useState(defaultTab);
|
const [selectedTab, setSelectedTab] = React.useState(defaultTab);
|
||||||
const selectedTab: InteractiveSectionTab | undefined =
|
|
||||||
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
const tabBody = tabs.find((tab) => tab.key === selectedTab)?.body;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -96,7 +94,7 @@ export function InteractiveSection(props: {
|
|||||||
'openapi-select',
|
'openapi-select',
|
||||||
`${className}-tabs-select`,
|
`${className}-tabs-select`,
|
||||||
)}
|
)}
|
||||||
value={selectedTab.key}
|
value={selectedTab}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setSelectedTab(event.target.value);
|
setSelectedTab(event.target.value);
|
||||||
setOpened(true);
|
setOpened(true);
|
||||||
@@ -109,7 +107,7 @@ export function InteractiveSection(props: {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
) : null}
|
) : null}
|
||||||
{(children || selectedTab?.body) && toggeable ? (
|
{(children || tabBody) && toggeable ? (
|
||||||
<button
|
<button
|
||||||
className={classNames('openapi-section-toggle', `${className}-toggle`)}
|
className={classNames('openapi-section-toggle', `${className}-toggle`)}
|
||||||
onClick={() => setOpened(!opened)}
|
onClick={() => setOpened(!opened)}
|
||||||
@@ -119,10 +117,10 @@ export function InteractiveSection(props: {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(!toggeable || opened) && (children || selectedTab?.body) ? (
|
{(!toggeable || opened) && (children || tabBody) ? (
|
||||||
<div className={classNames('openapi-section-body', `${className}-body`)}>
|
<div className={classNames('openapi-section-body', `${className}-body`)}>
|
||||||
{children}
|
{children}
|
||||||
{selectedTab?.body}
|
{tabBody}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{overlay}
|
{overlay}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export function Markdown(props: { source: string; className?: string }) {
|
export function Markdown(props: { source: string; className?: string }) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
|
|
||||||
import { CodeSampleInput, codeSampleGenerators } from './code-samples';
|
import { CodeSampleInput, codeSampleGenerators } from './code-samples';
|
||||||
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
|
||||||
import { generateMediaTypeExample } from './generateSchemaExample';
|
import { generateMediaTypeExample } from './generateSchemaExample';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { getServersURL } from './OpenAPIServerURL';
|
import { getServersURL } from './OpenAPIServerURL';
|
||||||
@@ -52,7 +52,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
}> = null;
|
}> = null;
|
||||||
(['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const).forEach((key) => {
|
(['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const).forEach((key) => {
|
||||||
const customSamples = data.operation[key];
|
const customSamples = data.operation[key];
|
||||||
if (customSamples && Array.isArray(customSamples)) {
|
if (customSamples) {
|
||||||
customCodeSamples = customSamples.map((sample) => ({
|
customCodeSamples = customSamples.map((sample) => ({
|
||||||
key: `redocly-${sample.lang}`,
|
key: `redocly-${sample.lang}`,
|
||||||
label: sample.label,
|
label: sample.label,
|
||||||
@@ -61,15 +61,16 @@ export function OpenAPICodeSample(props: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Code samples can be disabled at the top-level or at the operation level
|
const samples = customCodeSamples ?? (data['x-codeSamples'] !== false ? autoCodeSamples : []);
|
||||||
// If code samples are defined at the operation level, it will override the top-level setting
|
|
||||||
const codeSamplesDisabled =
|
|
||||||
data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
|
|
||||||
const samples = customCodeSamples ?? (!codeSamplesDisabled ? autoCodeSamples : []);
|
|
||||||
if (samples.length === 0) {
|
if (samples.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchOperationData() {
|
||||||
|
'use server';
|
||||||
|
return toJSON(data);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
header="Request"
|
header="Request"
|
||||||
@@ -77,7 +78,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
tabs={samples}
|
tabs={samples}
|
||||||
overlay={
|
overlay={
|
||||||
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
|
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
|
||||||
<ScalarApiButton />
|
<ScalarApiButton fetchOperationData={fetchOperationData} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
|
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
|
||||||
@@ -7,8 +6,8 @@ import { OpenAPICodeSample } from './OpenAPICodeSample';
|
|||||||
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
||||||
import { OpenAPIServerURL } from './OpenAPIServerURL';
|
import { OpenAPIServerURL } from './OpenAPIServerURL';
|
||||||
import { OpenAPISpec } from './OpenAPISpec';
|
import { OpenAPISpec } from './OpenAPISpec';
|
||||||
|
import { ScalarApiClient } from './ScalarApiButton';
|
||||||
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
|
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
|
||||||
import { ApiClientModalProvider } from '@scalar/api-client-react';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an interactive OpenAPI operation.
|
* Display an interactive OpenAPI operation.
|
||||||
@@ -23,15 +22,11 @@ export function OpenAPIOperation(props: {
|
|||||||
|
|
||||||
const clientContext: OpenAPIClientContext = {
|
const clientContext: OpenAPIClientContext = {
|
||||||
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
||||||
specUrl: context.specUrl,
|
|
||||||
icons: context.icons,
|
icons: context.icons,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ApiClientModalProvider
|
<ScalarApiClient>
|
||||||
configuration={{ spec: { url: context.specUrl } }}
|
|
||||||
initialRequest={{ path: data.path, method: data.method }}
|
|
||||||
>
|
|
||||||
<div className={classNames('openapi-operation', className)}>
|
<div className={classNames('openapi-operation', className)}>
|
||||||
<div className="openapi-intro">
|
<div className="openapi-intro">
|
||||||
<h2 className="openapi-summary">{operation.summary}</h2>
|
<h2 className="openapi-summary">{operation.summary}</h2>
|
||||||
@@ -65,6 +60,6 @@ export function OpenAPIOperation(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ApiClientModalProvider>
|
</ScalarApiClient>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { OpenAPIRootSchema } from './OpenAPISchema';
|
import { OpenAPIRootSchema } from './OpenAPISchema';
|
||||||
import { noReference } from './utils';
|
import { noReference } from './utils';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { OpenAPIRootSchema, OpenAPISchemaProperties } from './OpenAPISchema';
|
import { OpenAPIRootSchema, OpenAPISchemaProperties } from './OpenAPISchema';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
||||||
import { generateSchemaExample } from './generateSchemaExample';
|
import { generateSchemaExample } from './generateSchemaExample';
|
||||||
@@ -63,10 +62,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection header="Response" className="openapi-response-example">
|
<InteractiveSection header="Response" className="openapi-response-example">
|
||||||
<context.CodeBlock
|
<context.CodeBlock code={JSON.stringify(example, null, 2)} syntax="json" />
|
||||||
code={typeof example === 'string' ? example : JSON.stringify(example, null, 2)}
|
|
||||||
syntax="json"
|
|
||||||
/>
|
|
||||||
</InteractiveSection>
|
</InteractiveSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { noReference } from './utils';
|
import { noReference } from './utils';
|
||||||
|
|||||||
@@ -47,13 +47,6 @@ export function OpenAPISchemaProperty(
|
|||||||
? null
|
? null
|
||||||
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
||||||
|
|
||||||
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
|
|
||||||
return (
|
|
||||||
typeof schema.example === 'string' ||
|
|
||||||
typeof schema.example === 'number' ||
|
|
||||||
typeof schema.example === 'boolean'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
id={id}
|
id={id}
|
||||||
@@ -99,11 +92,6 @@ export function OpenAPISchemaProperty(
|
|||||||
className="openapi-schema-description"
|
className="openapi-schema-description"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{shouldDisplayExample(schema) ? (
|
|
||||||
<span className="openapi-schema-example">
|
|
||||||
Example: <code>{JSON.stringify(schema.example)}</code>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -182,8 +170,6 @@ export function OpenAPIRootSchema(props: {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a tab for an alternative schema.
|
* Render a tab for an alternative schema.
|
||||||
* It renders directly the properties if relevant;
|
|
||||||
* for primitives, it renders the schema itself.
|
|
||||||
*/
|
*/
|
||||||
function OpenAPISchemaAlternative(props: {
|
function OpenAPISchemaAlternative(props: {
|
||||||
schema: OpenAPIV3.SchemaObject;
|
schema: OpenAPIV3.SchemaObject;
|
||||||
@@ -192,13 +178,12 @@ function OpenAPISchemaAlternative(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { schema, circularRefs, context } = props;
|
const { schema, circularRefs, context } = props;
|
||||||
const id = useId();
|
const id = useId();
|
||||||
const subProperties = getSchemaProperties(schema);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPISchemaProperties
|
<OpenAPISchemaProperties
|
||||||
id={id}
|
id={id}
|
||||||
properties={subProperties ?? [{ schema }]}
|
properties={getSchemaProperties(schema) ?? []}
|
||||||
circularRefs={subProperties ? new Map(circularRefs).set(schema, id) : circularRefs}
|
circularRefs={new Map(circularRefs).set(schema, id)}
|
||||||
context={context}
|
context={context}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { OpenAPIClientContext } from './types';
|
import { OpenAPIClientContext } from './types';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
|
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interactive component to show the value of a server variable and let the user change it.
|
* Interactive component to show the value of a server variable and let the user change it.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
|
|
||||||
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
|
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
|
||||||
@@ -49,7 +48,6 @@ export function OpenAPISpec(props: { rawData: any; context: OpenAPIClientContext
|
|||||||
// Description of the parameter is defined at the parameter level
|
// Description of the parameter is defined at the parameter level
|
||||||
// we use display it if the schema doesn't override it
|
// we use display it if the schema doesn't override it
|
||||||
description: parameter.description,
|
description: parameter.description,
|
||||||
example: parameter.example,
|
|
||||||
...(noReference(parameter.schema) ?? {}),
|
...(noReference(parameter.schema) ?? {}),
|
||||||
},
|
},
|
||||||
required: parameter.required,
|
required: parameter.required,
|
||||||
|
|||||||
@@ -1,17 +1,44 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
import {
|
||||||
import { useApiClientModal } from '@scalar/api-client-react';
|
Cookie,
|
||||||
|
getHarRequest,
|
||||||
|
getParametersFromOperation,
|
||||||
|
type TransformedOperation,
|
||||||
|
getRequestFromOperation,
|
||||||
|
Query,
|
||||||
|
Header,
|
||||||
|
RequestBody,
|
||||||
|
} from '@scalar/oas-utils';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
|
||||||
|
|
||||||
|
const ApiClientReact = React.lazy(async () => {
|
||||||
|
const mod = await import('@scalar/api-client-react');
|
||||||
|
return { default: mod.ApiClientReact };
|
||||||
|
});
|
||||||
|
|
||||||
|
const ScalarContext = React.createContext<
|
||||||
|
(fetchOperationData: () => Promise<OpenAPIOperationData>) => void
|
||||||
|
>(() => {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button which launches the Scalar API Client
|
* Button which launches the Scalar API Client
|
||||||
*/
|
*/
|
||||||
export function ScalarApiButton() {
|
export function ScalarApiButton(props: {
|
||||||
const client = useApiClientModal();
|
fetchOperationData: () => Promise<OpenAPIOperationData>;
|
||||||
|
}) {
|
||||||
|
const { fetchOperationData } = props;
|
||||||
|
const open = React.useContext(ScalarContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scalar scalar-activate">
|
<div className="scalar scalar-activate">
|
||||||
<button className="scalar-activate-button" onClick={() => client?.open()}>
|
<button
|
||||||
|
className="scalar-activate-button"
|
||||||
|
onClick={() => {
|
||||||
|
open(fetchOperationData);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -24,3 +51,109 @@ export function ScalarApiButton() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the rendering with a context to open the scalar modal.
|
||||||
|
*/
|
||||||
|
export function ScalarApiClient(props: { children: React.ReactNode }) {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const [active, setActive] = React.useState<null | {
|
||||||
|
operationData: OpenAPIOperationData | null;
|
||||||
|
}>(null);
|
||||||
|
|
||||||
|
const proxy = '/~scalar/proxy';
|
||||||
|
|
||||||
|
const open = React.useCallback(
|
||||||
|
async (fetchOperationData: () => Promise<OpenAPIOperationData>) => {
|
||||||
|
setActive({ operationData: null });
|
||||||
|
const operationData = fromJSON(await fetchOperationData());
|
||||||
|
setActive({ operationData });
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClose = React.useCallback(() => {
|
||||||
|
setActive(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const request = React.useMemo(() => {
|
||||||
|
const operationData = active?.operationData;
|
||||||
|
|
||||||
|
if (!operationData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operationId =
|
||||||
|
operationData.operation.operationId ?? operationData.method + operationData.path;
|
||||||
|
|
||||||
|
const operation = {
|
||||||
|
...operationData,
|
||||||
|
httpVerb: operationData.method,
|
||||||
|
pathParameters: operationData.operation.parameters,
|
||||||
|
} as TransformedOperation;
|
||||||
|
|
||||||
|
const variables = getParametersFromOperation(operation, 'path', false);
|
||||||
|
|
||||||
|
const request = getHarRequest(
|
||||||
|
{
|
||||||
|
url: operationData.path,
|
||||||
|
},
|
||||||
|
getRequestFromOperation(
|
||||||
|
{
|
||||||
|
...operation,
|
||||||
|
information: {
|
||||||
|
requestBody: operationData.operation.requestBody as RequestBody,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ requiredOnly: false },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: operationId,
|
||||||
|
type: operationData.method,
|
||||||
|
path: operationData.path,
|
||||||
|
variables,
|
||||||
|
cookies: request.cookies.map((cookie: Cookie) => {
|
||||||
|
return { ...cookie, enabled: true };
|
||||||
|
}),
|
||||||
|
query: request.queryString.map((queryString: Query) => {
|
||||||
|
const query: typeof queryString & { required?: boolean } = queryString;
|
||||||
|
return { ...queryString, enabled: query.required ?? true };
|
||||||
|
}),
|
||||||
|
headers: request.headers.map((header: Header) => {
|
||||||
|
return { ...header, enabled: true };
|
||||||
|
}),
|
||||||
|
url: operationData.servers[0]?.url,
|
||||||
|
body: request.postData?.text,
|
||||||
|
};
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScalarContext.Provider value={open}>
|
||||||
|
{children}
|
||||||
|
{active ? (
|
||||||
|
<div className="scalar">
|
||||||
|
<div className="scalar-container">
|
||||||
|
<div className="scalar-app">
|
||||||
|
<React.Suspense fallback={<ScalarLoading />}>
|
||||||
|
<ApiClientReact
|
||||||
|
close={onClose}
|
||||||
|
proxy={proxy}
|
||||||
|
isOpen={true}
|
||||||
|
request={request}
|
||||||
|
/>
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
<div onClick={() => onClose()} className="scalar-app-exit"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</ScalarContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScalarLoading() {
|
||||||
|
return <div className="scalar-app-loading">Loading...</div>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export interface OpenAPICustomSpecProperties {
|
|||||||
*/
|
*/
|
||||||
export interface OpenAPICustomOperationProperties {
|
export interface OpenAPICustomOperationProperties {
|
||||||
'x-code-samples'?: OpenAPICustomCodeSample[];
|
'x-code-samples'?: OpenAPICustomCodeSample[];
|
||||||
'x-codeSamples'?: OpenAPICustomCodeSample[] | false;
|
'x-codeSamples'?: OpenAPICustomCodeSample[];
|
||||||
'x-custom-examples'?: OpenAPICustomCodeSample[];
|
'x-custom-examples'?: OpenAPICustomCodeSample[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function generateSchemaExample(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof schema.example !== 'undefined') {
|
if (schema.example) {
|
||||||
return schema.example;
|
return schema.example;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ export interface OpenAPIClientContext {
|
|||||||
chevronRight: React.ReactNode;
|
chevronRight: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Spec url foor the Scalar Api Client */
|
|
||||||
specUrl: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force all sections to be opened by default.
|
* Force all sections to be opened by default.
|
||||||
* @default false
|
* @default false
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": false,
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"incremental": true,
|
|
||||||
"types": [
|
|
||||||
"bun-types" // add Bun global
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
|
|||||||
diff --git a/dist/index.js b/dist/index.js
|
|
||||||
index 7f9eaa2526e5fad42420f590bb733e5670766e63..302274144d28dde2b9ba8f9018281d2867f79299 100644
|
|
||||||
--- a/dist/index.js
|
|
||||||
+++ b/dist/index.js
|
|
||||||
@@ -9484,7 +9484,7 @@ var import_path = require("path");
|
|
||||||
// src/constants.ts
|
|
||||||
var KIB = 1024;
|
|
||||||
var MIB = 1024 * KIB;
|
|
||||||
-var EDGE_FUNCTION_SIZE_LIMIT = 4 * MIB;
|
|
||||||
+var EDGE_FUNCTION_SIZE_LIMIT = 10 * MIB;
|
|
||||||
var MAX_UNCOMPRESSED_LAMBDA_SIZE = 250 * MIB;
|
|
||||||
var LAMBDA_RESERVED_UNCOMPRESSED_SIZE = 2.5 * MIB;
|
|
||||||
var LAMBDA_RESERVED_COMPRESSED_SIZE = 250 * KIB;
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
||||||
|
const dsn = process.env.SENTRY_DSN;
|
||||||
|
if (dsn) {
|
||||||
|
Sentry.init({
|
||||||
|
debug: false,
|
||||||
|
dsn,
|
||||||
|
integrations: [],
|
||||||
|
sampleRate: 0.1,
|
||||||
|
enableTracing: false,
|
||||||
|
beforeSendTransaction: () => {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { init } from '@sentry/nextjs';
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
const dsn = process.env.SENTRY_DSN;
|
||||||
if (dsn) {
|
if (dsn) {
|
||||||
init({
|
Sentry.init({
|
||||||
debug: false,
|
debug: false,
|
||||||
dsn,
|
dsn,
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { init } from '@sentry/nextjs';
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
const dsn = process.env.SENTRY_DSN;
|
||||||
if (dsn) {
|
if (dsn) {
|
||||||
init({
|
Sentry.init({
|
||||||
debug: false,
|
debug: false,
|
||||||
dsn,
|
dsn,
|
||||||
|
|
||||||
+2
-16
@@ -1,11 +1,6 @@
|
|||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
|
|
||||||
import {
|
import { verifyImageSignature, resizeImage, CloudflareImageOptions } from '@/lib/images';
|
||||||
verifyImageSignature,
|
|
||||||
resizeImage,
|
|
||||||
CloudflareImageOptions,
|
|
||||||
checkIsSizableImageURL,
|
|
||||||
} from '@/lib/images';
|
|
||||||
import { parseImageAPIURL } from '@/lib/urls';
|
import { parseImageAPIURL } from '@/lib/urls';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
@@ -18,9 +13,6 @@ export const runtime = 'edge';
|
|||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
let urlParam = request.nextUrl.searchParams.get('url');
|
let urlParam = request.nextUrl.searchParams.get('url');
|
||||||
const signature = request.nextUrl.searchParams.get('sign');
|
const signature = request.nextUrl.searchParams.get('sign');
|
||||||
// The current signature algorithm sets version as 1, but we need to support the older version as well
|
|
||||||
// for previously generated content. In this case, we default to version 0.
|
|
||||||
const signatureVersion = (request.nextUrl.searchParams.get('sv') as '1') || '0';
|
|
||||||
if (!urlParam || !signature) {
|
if (!urlParam || !signature) {
|
||||||
return new Response('Missing url/sign parameters', { status: 400 });
|
return new Response('Missing url/sign parameters', { status: 400 });
|
||||||
}
|
}
|
||||||
@@ -32,14 +24,8 @@ export async function GET(request: NextRequest) {
|
|||||||
return new Response('Invalid url parameter', { status: 400 });
|
return new Response('Invalid url parameter', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check again if the image can be sized, even though we checked when rendering the Image component
|
|
||||||
// Otherwise, it's possible to pass just any link to this endpoint and trigger HTML injection on the domain
|
|
||||||
if (!checkIsSizableImageURL(url)) {
|
|
||||||
return new Response('Invalid url parameter', { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
const verified = await verifyImageSignature(url, { signature, version: signatureVersion });
|
const verified = await verifyImageSignature(url, signature);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return new Response(`Invalid signature "${signature ?? ''}" for "${url}"`, { status: 400 });
|
return new Response(`Invalid signature "${signature ?? ''}" for "${url}"`, { status: 400 });
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useScrollToHash } from '@/components/hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client component to initialize interactivity for a page.
|
||||||
|
*/
|
||||||
|
export function PageClientLayout(props: {}) {
|
||||||
|
// We use this hook in the page layout to ensure the elements for the blocks
|
||||||
|
// are rendered before we scroll to the hash.
|
||||||
|
useScrollToHash();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
+10
-69
@@ -6,10 +6,9 @@ import React from 'react';
|
|||||||
import { PageAside } from '@/components/PageAside';
|
import { PageAside } from '@/components/PageAside';
|
||||||
import { PageBody, PageCover } from '@/components/PageBody';
|
import { PageBody, PageCover } from '@/components/PageBody';
|
||||||
import { PageHrefContext, absoluteHref, pageHref } from '@/lib/links';
|
import { PageHrefContext, absoluteHref, pageHref } from '@/lib/links';
|
||||||
import { getPagePath, resolveFirstDocument } from '@/lib/pages';
|
import { getPagePath } from '@/lib/pages';
|
||||||
import { ContentRefContext } from '@/lib/references';
|
import { ContentRefContext } from '@/lib/references';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
import { getContentTitle } from '@/lib/utils';
|
|
||||||
|
|
||||||
import { PageClientLayout } from './PageClientLayout';
|
import { PageClientLayout } from './PageClientLayout';
|
||||||
import { PagePathParams, fetchPageData, getPathnameParam, normalizePathname } from '../../fetch';
|
import { PagePathParams, fetchPageData, getPathnameParam, normalizePathname } from '../../fetch';
|
||||||
@@ -19,29 +18,14 @@ export const runtime = 'edge';
|
|||||||
/**
|
/**
|
||||||
* Fetch and render a page.
|
* Fetch and render a page.
|
||||||
*/
|
*/
|
||||||
export default async function Page(props: {
|
export default async function Page(props: { params: PagePathParams }) {
|
||||||
params: PagePathParams;
|
const { params } = props;
|
||||||
searchParams: { fallback?: string };
|
|
||||||
}) {
|
|
||||||
const { params, searchParams } = props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
content: contentPointer,
|
|
||||||
contentTarget,
|
|
||||||
space,
|
|
||||||
parent,
|
|
||||||
customization,
|
|
||||||
pages,
|
|
||||||
page,
|
|
||||||
document,
|
|
||||||
} = await getPageDataWithFallback({
|
|
||||||
pagePathParams: params,
|
|
||||||
searchParams,
|
|
||||||
redirectOnFallback: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const linksContext: PageHrefContext = {};
|
|
||||||
const rawPathname = getPathnameParam(params);
|
const rawPathname = getPathnameParam(params);
|
||||||
|
const { contentTarget, space, customization, pages, page, document } =
|
||||||
|
await fetchPageData(params);
|
||||||
|
const linksContext: PageHrefContext = {};
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
const pathname = normalizePathname(rawPathname);
|
const pathname = normalizePathname(rawPathname);
|
||||||
if (pathname !== rawPathname) {
|
if (pathname !== rawPathname) {
|
||||||
@@ -78,7 +62,6 @@ export default async function Page(props: {
|
|||||||
<div className={tcls('flex', 'flex-row')}>
|
<div className={tcls('flex', 'flex-row')}>
|
||||||
<PageBody
|
<PageBody
|
||||||
space={space}
|
space={space}
|
||||||
contentPointer={contentPointer}
|
|
||||||
contentTarget={contentTarget}
|
contentTarget={contentTarget}
|
||||||
customization={customization}
|
customization={customization}
|
||||||
context={contentRefContext}
|
context={contentRefContext}
|
||||||
@@ -92,7 +75,6 @@ export default async function Page(props: {
|
|||||||
{page.layout.outline ? (
|
{page.layout.outline ? (
|
||||||
<PageAside
|
<PageAside
|
||||||
space={space}
|
space={space}
|
||||||
site={parent?.object === 'site' ? parent : undefined}
|
|
||||||
customization={customization}
|
customization={customization}
|
||||||
page={page}
|
page={page}
|
||||||
document={document}
|
document={document}
|
||||||
@@ -121,24 +103,14 @@ export async function generateViewport({ params }: { params: PagePathParams }):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({ params }: { params: PagePathParams }): Promise<Metadata> {
|
||||||
params,
|
const { space, pages, page, customization, parent } = await fetchPageData(params);
|
||||||
searchParams,
|
|
||||||
}: {
|
|
||||||
params: PagePathParams;
|
|
||||||
searchParams: { fallback?: string };
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { space, pages, page, customization, parent } = await getPageDataWithFallback({
|
|
||||||
pagePathParams: params,
|
|
||||||
searchParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: [page.title, getContentTitle(space, customization, parent)]
|
title: [page.title, customization.title ?? space.title, parent?.title]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' | '),
|
.join(' | '),
|
||||||
description: page.description ?? '',
|
description: page.description ?? '',
|
||||||
@@ -153,34 +125,3 @@ export async function generateMetadata({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the page data matching the requested pathname and fallback to root page when page is not found.
|
|
||||||
*/
|
|
||||||
async function getPageDataWithFallback(args: {
|
|
||||||
pagePathParams: PagePathParams;
|
|
||||||
searchParams: { fallback?: string };
|
|
||||||
redirectOnFallback?: boolean;
|
|
||||||
}) {
|
|
||||||
const { pagePathParams, searchParams, redirectOnFallback = false } = args;
|
|
||||||
|
|
||||||
const { pages, page: targetPage, ...otherPageData } = await fetchPageData(pagePathParams);
|
|
||||||
|
|
||||||
let page = targetPage;
|
|
||||||
const canFallback = !!searchParams.fallback;
|
|
||||||
if (!page && canFallback) {
|
|
||||||
const rootPage = resolveFirstDocument(pages, []);
|
|
||||||
|
|
||||||
if (redirectOnFallback && rootPage?.page) {
|
|
||||||
redirect(pageHref(pages, rootPage?.page));
|
|
||||||
}
|
|
||||||
|
|
||||||
page = rootPage?.page;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...otherPageData,
|
|
||||||
pages,
|
|
||||||
page,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+1
-9
@@ -8,13 +8,10 @@ import { AdminToolbar } from '@/components/AdminToolbar';
|
|||||||
import { CookiesToast } from '@/components/Cookies';
|
import { CookiesToast } from '@/components/Cookies';
|
||||||
import { LoadIntegrations } from '@/components/Integrations';
|
import { LoadIntegrations } from '@/components/Integrations';
|
||||||
import { SpaceLayout } from '@/components/SpaceLayout';
|
import { SpaceLayout } from '@/components/SpaceLayout';
|
||||||
import { api } from '@/lib/api';
|
|
||||||
import { assetsDomain } from '@/lib/assets';
|
|
||||||
import { buildVersion } from '@/lib/build';
|
import { buildVersion } from '@/lib/build';
|
||||||
import { getContentSecurityPolicyNonce } from '@/lib/csp';
|
import { getContentSecurityPolicyNonce } from '@/lib/csp';
|
||||||
import { absoluteHref, baseUrl } from '@/lib/links';
|
import { absoluteHref, baseUrl } from '@/lib/links';
|
||||||
import { shouldIndexSpace } from '@/lib/seo';
|
import { shouldIndexSpace } from '@/lib/seo';
|
||||||
import { getContentTitle } from '@/lib/utils';
|
|
||||||
|
|
||||||
import { ClientContexts } from './ClientContexts';
|
import { ClientContexts } from './ClientContexts';
|
||||||
import { RocketLoaderDetector } from './RocketLoaderDetector';
|
import { RocketLoaderDetector } from './RocketLoaderDetector';
|
||||||
@@ -41,11 +38,6 @@ export default async function ContentLayout(props: { children: React.ReactNode }
|
|||||||
scripts,
|
scripts,
|
||||||
} = await fetchSpaceData();
|
} = await fetchSpaceData();
|
||||||
|
|
||||||
ReactDOM.preconnect(api().endpoint);
|
|
||||||
if (assetsDomain) {
|
|
||||||
ReactDOM.preconnect(assetsDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts.forEach(({ script }) => {
|
scripts.forEach(({ script }) => {
|
||||||
ReactDOM.preload(script, {
|
ReactDOM.preload(script, {
|
||||||
as: 'script',
|
as: 'script',
|
||||||
@@ -112,7 +104,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
|
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: getContentTitle(space, customization, parent),
|
title: `${parent ? parent.title : customization.title ?? space.title}`,
|
||||||
generator: `GitBook (${buildVersion()})`,
|
generator: `GitBook (${buildVersion()})`,
|
||||||
metadataBase: new URL(baseUrl()),
|
metadataBase: new URL(baseUrl()),
|
||||||
icons: {
|
icons: {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user