mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-14 14:45:17 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1bd02a6f5 | |||
| 24cd72e088 | |||
| 47f7558c38 | |||
| de747b7b33 | |||
| 8e8b73fbd9 | |||
| e966e33e8f | |||
| bd0ca5babd | |||
| f6bacea1ae | |||
| ba2a523722 | |||
| 57adb3e54a | |||
| 7377928c8a | |||
| 5f8a8fe9b1 | |||
| 29a0bf25c4 | |||
| 92bb016576 | |||
| c8810ea002 | |||
| aa8bbaddc4 | |||
| b8c136e9c0 | |||
| 46d54172b0 | |||
| 68dc2de190 | |||
| 81b372f372 | |||
| 966e520640 | |||
| 12702930ec | |||
| 6ce57bcda1 | |||
| d99b453bc4 | |||
| aaf5793cb7 | |||
| 9b1762d2ab | |||
| 8d38d000fd | |||
| 4cf9cdd56d | |||
| 39406bbf54 | |||
| 9c5c1e5b68 | |||
| 6929b0b98b | |||
| 7c26d491b1 | |||
| 97035a054a | |||
| db45c6d773 | |||
| e3f5f81e62 |
@@ -0,0 +1,8 @@
|
|||||||
|
# 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)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$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.0.33
|
bun-version: 1.1.18
|
||||||
- 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: ./.vercel/output/static
|
directory: ./packages/gitbook/.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.0.33
|
bun-version: 1.1.18
|
||||||
- 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.0.33
|
bun-version: 1.1.18
|
||||||
- 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 ./tests/pagespeed-testing.ts $DEPLOYMENT_URL
|
run: bun ./packages/gitbook/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.0.33
|
bun-version: 1.1.18
|
||||||
- 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.0.33
|
bun-version: 1.1.18
|
||||||
- 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.0.33
|
bun-version: 1.1.18
|
||||||
- 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.0.33
|
bun-version: 1.1.18
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
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 }}
|
||||||
+3
-29
@@ -5,16 +5,6 @@ node_modules
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
@@ -24,25 +14,9 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
# Turbo
|
||||||
.env*.local
|
.turbo
|
||||||
|
|
||||||
# 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/
|
|
||||||
|
|||||||
+6
-1
@@ -1 +1,6 @@
|
|||||||
.next
|
.next
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# Generated
|
||||||
|
packages/emoji-codepoints/index.ts
|
||||||
|
packages/gitbook/public/~gitbook/static/
|
||||||
|
|||||||
+12
-77
@@ -2,91 +2,26 @@
|
|||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"packageManager": "bun@1.1.18",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "turbo run dev",
|
||||||
"build": "next build",
|
"build": "turbo run build",
|
||||||
"build:cloudflare": "next-on-pages",
|
"build:cloudflare": "turbo run build:cloudflare",
|
||||||
"start": "next start",
|
"lint": "turbo run lint",
|
||||||
"lint": "next lint",
|
"typecheck": "turbo run typecheck",
|
||||||
"format": "prettier ./ --ignore-unknown --write",
|
"format": "prettier ./ --ignore-unknown --write",
|
||||||
"format:check": "prettier ./ --ignore-unknown --list-different",
|
"format:check": "prettier ./ --ignore-unknown --list-different",
|
||||||
"typecheck": "tsc --noEmit",
|
"unit": "turbo run unit",
|
||||||
"unit": "bun test {src,packages}",
|
"e2e": "turbo run e2e",
|
||||||
"e2e": "playwright test",
|
"changeset": "changeset",
|
||||||
"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"
|
"release": "turbo run build && changeset publish"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
"@geist-ui/icons": "^1.0.2",
|
|
||||||
"@gitbook/api": "^0.51.0",
|
|
||||||
"@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.2.4",
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@argos-ci/playwright": "^2.0.0",
|
"@changesets/cli": "^2.27.7",
|
||||||
"@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",
|
"prettier": "^3.0.3",
|
||||||
"psi": "^4.1.0",
|
"turbo": "^2.0.9"
|
||||||
"tailwindcss": "^3.4.0",
|
|
||||||
"typescript": "^5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# @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,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"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": {},
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 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/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# gitbook
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -106,6 +106,93 @@ 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',
|
||||||
@@ -276,6 +363,11 @@ 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',
|
||||||
@@ -9,7 +9,7 @@ module.exports = withSentryConfig(
|
|||||||
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack(config) {
|
webpack(config, { dev, webpack }) {
|
||||||
config.resolve.fallback = {
|
config.resolve.fallback = {
|
||||||
...config.resolve.fallback,
|
...config.resolve.fallback,
|
||||||
|
|
||||||
@@ -19,6 +19,21 @@ 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;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -45,9 +60,9 @@ module.exports = withSentryConfig(
|
|||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: '*.gitbook.io',
|
hostname: '*.gitbook.io',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
silent: true,
|
silent: true,
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"name": "gitbook",
|
||||||
|
"version": "0.2.1",
|
||||||
|
"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.2.0",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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,8 +1,8 @@
|
|||||||
import * as Sentry from '@sentry/nextjs';
|
import { init } from '@sentry/nextjs';
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
const dsn = process.env.SENTRY_DSN;
|
||||||
if (dsn) {
|
if (dsn) {
|
||||||
Sentry.init({
|
init({
|
||||||
debug: false,
|
debug: false,
|
||||||
dsn,
|
dsn,
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as Sentry from '@sentry/nextjs';
|
import { init } from '@sentry/nextjs';
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
const dsn = process.env.SENTRY_DSN;
|
||||||
if (dsn) {
|
if (dsn) {
|
||||||
Sentry.init({
|
init({
|
||||||
debug: false,
|
debug: false,
|
||||||
dsn,
|
dsn,
|
||||||
|
|
||||||
+16
-2
@@ -1,6 +1,11 @@
|
|||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
|
|
||||||
import { verifyImageSignature, resizeImage, CloudflareImageOptions } from '@/lib/images';
|
import {
|
||||||
|
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';
|
||||||
@@ -13,6 +18,9 @@ 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 });
|
||||||
}
|
}
|
||||||
@@ -24,8 +32,14 @@ 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);
|
const verified = await verifyImageSignature(url, { signature, version: signatureVersion });
|
||||||
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,40 @@
|
|||||||
|
'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]);
|
||||||
|
}
|
||||||
+58
-8
@@ -6,7 +6,7 @@ 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 } from '@/lib/pages';
|
import { getPagePath, resolveFirstDocument } 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 { getContentTitle } from '@/lib/utils';
|
||||||
@@ -19,21 +19,29 @@ export const runtime = 'edge';
|
|||||||
/**
|
/**
|
||||||
* Fetch and render a page.
|
* Fetch and render a page.
|
||||||
*/
|
*/
|
||||||
export default async function Page(props: { params: PagePathParams }) {
|
export default async function Page(props: {
|
||||||
const { params } = props;
|
params: PagePathParams;
|
||||||
|
searchParams: { fallback?: string };
|
||||||
|
}) {
|
||||||
|
const { params, searchParams } = props;
|
||||||
|
|
||||||
const rawPathname = getPathnameParam(params);
|
|
||||||
const {
|
const {
|
||||||
content: contentPointer,
|
content: contentPointer,
|
||||||
contentTarget,
|
contentTarget,
|
||||||
space,
|
space,
|
||||||
|
parent,
|
||||||
customization,
|
customization,
|
||||||
pages,
|
pages,
|
||||||
page,
|
page,
|
||||||
document,
|
document,
|
||||||
} = await fetchPageData(params);
|
} = await getPageDataWithFallback({
|
||||||
const linksContext: PageHrefContext = {};
|
pagePathParams: params,
|
||||||
|
searchParams,
|
||||||
|
redirectOnFallback: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const linksContext: PageHrefContext = {};
|
||||||
|
const rawPathname = getPathnameParam(params);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
const pathname = normalizePathname(rawPathname);
|
const pathname = normalizePathname(rawPathname);
|
||||||
if (pathname !== rawPathname) {
|
if (pathname !== rawPathname) {
|
||||||
@@ -84,6 +92,7 @@ export default async function Page(props: { params: PagePathParams }) {
|
|||||||
{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}
|
||||||
@@ -112,8 +121,18 @@ export async function generateViewport({ params }: { params: PagePathParams }):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: { params: PagePathParams }): Promise<Metadata> {
|
export async function generateMetadata({
|
||||||
const { space, pages, page, customization, parent } = await fetchPageData(params);
|
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();
|
||||||
}
|
}
|
||||||
@@ -134,3 +153,34 @@ export async function generateMetadata({ params }: { params: PagePathParams }):
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
+6
-3
@@ -15,7 +15,7 @@ export const runtime = 'edge';
|
|||||||
*/
|
*/
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const { pages: rootPages } = await getSpaceContentData(getContentPointer());
|
const { pages: rootPages } = await getSpaceContentData(getContentPointer());
|
||||||
const pages = flattenPages(rootPages);
|
const pages = flattenPages(rootPages, (page) => !page.hidden);
|
||||||
const urls = pages.map(({ page, depth }) => {
|
const urls = pages.map(({ page, depth }) => {
|
||||||
// Decay priority with depth
|
// Decay priority with depth
|
||||||
const priority = Math.pow(2, -0.25 * depth);
|
const priority = Math.pow(2, -0.25 * depth);
|
||||||
@@ -66,13 +66,16 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
type FlatPageEntry = { page: RevisionPageDocument; depth: number };
|
type FlatPageEntry = { page: RevisionPageDocument; depth: number };
|
||||||
|
|
||||||
function flattenPages(rootPags: RevisionPage[]): FlatPageEntry[] {
|
function flattenPages(
|
||||||
|
rootPags: RevisionPage[],
|
||||||
|
filter: (page: RevisionPageDocument) => boolean,
|
||||||
|
): FlatPageEntry[] {
|
||||||
const flattenPage = (
|
const flattenPage = (
|
||||||
page: RevisionPageDocument | RevisionPageGroup,
|
page: RevisionPageDocument | RevisionPageGroup,
|
||||||
depth: number,
|
depth: number,
|
||||||
): FlatPageEntry[] => {
|
): FlatPageEntry[] => {
|
||||||
return [
|
return [
|
||||||
...(page.type === 'document' ? [{ page, depth }] : []),
|
...(page.type === 'document' && filter(page) ? [{ page, depth }] : []),
|
||||||
...page.pages.flatMap((child) =>
|
...page.pages.flatMap((child) =>
|
||||||
child.type === 'link' ? [] : flattenPage(child, depth + 1),
|
child.type === 'link' ? [] : flattenPage(child, depth + 1),
|
||||||
),
|
),
|
||||||
+23
-6
@@ -4,6 +4,7 @@ import {
|
|||||||
Revision,
|
Revision,
|
||||||
RevisionPageDocument,
|
RevisionPageDocument,
|
||||||
RevisionPageGroup,
|
RevisionPageGroup,
|
||||||
|
RevisionPageLink,
|
||||||
SiteCustomizationSettings,
|
SiteCustomizationSettings,
|
||||||
Space,
|
Space,
|
||||||
} from '@gitbook/api';
|
} from '@gitbook/api';
|
||||||
@@ -313,9 +314,17 @@ function selectPages(
|
|||||||
): FlatPageEntry[] => {
|
): FlatPageEntry[] => {
|
||||||
return [
|
return [
|
||||||
{ page, depth },
|
{ page, depth },
|
||||||
...page.pages.flatMap((child) =>
|
...page.pages.flatMap((child) => {
|
||||||
child.type === 'link' ? [] : flattenPage(child, depth + 1),
|
if (child.type === 'link') {
|
||||||
),
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.hidden) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenPage(child, depth + 1);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -340,8 +349,16 @@ function selectPages(
|
|||||||
return limitTo(flattenPage(found.page, 0));
|
return limitTo(flattenPage(found.page, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPages = rootPages.flatMap((page) =>
|
const allPages = rootPages.flatMap((page) => {
|
||||||
page.type === 'link' ? [] : flattenPage(page, 0),
|
if (page.type === 'link') {
|
||||||
);
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.hidden) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenPage(page, 0);
|
||||||
|
});
|
||||||
return limitTo(allPages);
|
return limitTo(allPages);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import * as Sentry from '@sentry/nextjs';
|
import { captureException } from '@sentry/nextjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Button } from '@/components/primitives/Button';
|
import { Button } from '@/components/primitives/Button';
|
||||||
@@ -15,7 +15,7 @@ export default function ErrorPage(props: {
|
|||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
Sentry.captureException(error);
|
captureException(error);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import * as Sentry from '@sentry/nextjs';
|
import { captureException } from '@sentry/nextjs';
|
||||||
import Error from 'next/error';
|
import Error from 'next/error';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function GlobalError({ error }: { error: Error }) {
|
export default function GlobalError({ error }: { error: Error }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.captureException(error);
|
captureException(error);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
'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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './Ad';
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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 | {}>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
+5
-4
@@ -1,4 +1,5 @@
|
|||||||
import * as gitbookAPI from '@gitbook/api';
|
import * as gitbookAPI from '@gitbook/api';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
@@ -12,8 +13,9 @@ import { IntegrationBlock } from './Integration';
|
|||||||
|
|
||||||
export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
|
export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
|
||||||
const { block, context, ...otherProps } = props;
|
const { block, context, ...otherProps } = props;
|
||||||
|
const nonce = headers().get('x-nonce') || undefined;
|
||||||
ReactDOM.preconnect('https://cdn.iframe.ly');
|
|
||||||
|
ReactDOM.preload('https://cdn.iframe.ly/embed.js', { as: 'script', nonce });
|
||||||
|
|
||||||
const { data: embed } = await (context.content
|
const { data: embed } = await (context.content
|
||||||
? api().spaces.getEmbedByUrlInSpace(context.content.spaceId, { url: block.data.url })
|
? api().spaces.getEmbedByUrlInSpace(context.content.spaceId, { url: block.data.url })
|
||||||
@@ -28,8 +30,7 @@ export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
|
|||||||
__html: embed.html,
|
__html: embed.html,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* We load the iframely script to resize the embed iframes dynamically */}
|
<Script src="https://cdn.iframe.ly/embed.js" nonce={nonce} />
|
||||||
<Script src="https://cdn.iframe.ly/embed.js" defer async />
|
|
||||||
</>
|
</>
|
||||||
) : embed.type === 'integration' ? (
|
) : embed.type === 'integration' ? (
|
||||||
<IntegrationBlock
|
<IntegrationBlock
|
||||||
+1
-2
@@ -15,13 +15,12 @@ import {
|
|||||||
Lock,
|
Lock,
|
||||||
} from '@geist-ui/icons';
|
} from '@geist-ui/icons';
|
||||||
import { ContentKitContext, DocumentBlockIntegration } from '@gitbook/api';
|
import { ContentKitContext, DocumentBlockIntegration } from '@gitbook/api';
|
||||||
|
import { ContentKit, ContentKitOutput, ContentKitServerContext } from '@gitbook/react-contentkit';
|
||||||
|
|
||||||
import { ignoreAPIError, renderIntegrationUi } from '@/lib/api';
|
import { ignoreAPIError, renderIntegrationUi } from '@/lib/api';
|
||||||
import { parseMarkdown } from '@/lib/markdown';
|
import { parseMarkdown } from '@/lib/markdown';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
import { ContentKit, ContentKitOutput, ContentKitServerContext } from '@gitbook/react-contentkit';
|
|
||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import './contentkit.css';
|
import './contentkit.css';
|
||||||
import { PlainCodeBlock } from '../CodeBlock';
|
import { PlainCodeBlock } from '../CodeBlock';
|
||||||
+1
-2
@@ -1,10 +1,9 @@
|
|||||||
import { DocumentBlockMath, DocumentInlineMath } from '@gitbook/api';
|
import { DocumentBlockMath, DocumentInlineMath } from '@gitbook/api';
|
||||||
|
import { MathFormula } from '@gitbook/react-math';
|
||||||
|
|
||||||
import { getStaticFileURL } from '@/lib/assets';
|
import { getStaticFileURL } from '@/lib/assets';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
import { MathFormula } from '@gitbook/react-math';
|
|
||||||
|
|
||||||
import { BlockProps } from './Block';
|
import { BlockProps } from './Block';
|
||||||
import { InlineProps } from './Inline';
|
import { InlineProps } from './Inline';
|
||||||
|
|
||||||
+1
-2
@@ -1,13 +1,12 @@
|
|||||||
import { ChevronDown, ChevronRight } from '@geist-ui/icons';
|
import { ChevronDown, ChevronRight } from '@geist-ui/icons';
|
||||||
import { DocumentBlockSwagger } from '@gitbook/api';
|
import { DocumentBlockSwagger } from '@gitbook/api';
|
||||||
|
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { LoadingPane } from '@/components/primitives';
|
import { LoadingPane } from '@/components/primitives';
|
||||||
import { fetchOpenAPIBlock } from '@/lib/openapi';
|
import { fetchOpenAPIBlock } from '@/lib/openapi';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
|
||||||
|
|
||||||
import { BlockProps } from '../Block';
|
import { BlockProps } from '../Block';
|
||||||
import { PlainCodeBlock } from '../CodeBlock';
|
import { PlainCodeBlock } from '../CodeBlock';
|
||||||
|
|
||||||
+21
-5
@@ -5,8 +5,16 @@
|
|||||||
rgb(var(--primary-base-300, 180 180 180)),
|
rgb(var(--primary-base-300, 180 180 180)),
|
||||||
rgb(var(--dark-base, 23 23 23)) 96%
|
rgb(var(--dark-base, 23 23 23)) 96%
|
||||||
);
|
);
|
||||||
--scalar-color-2: color-mix(in srgb, var(--scalar-color-1), transparent calc(100% - 100% * 0.72));
|
--scalar-color-2: color-mix(
|
||||||
--scalar-color-3: color-mix(in srgb, var(--scalar-color-1), transparent calc(100% - 100% * 0.4));
|
in srgb,
|
||||||
|
var(--scalar-color-1),
|
||||||
|
transparent calc(100% - 100% * 0.72)
|
||||||
|
);
|
||||||
|
--scalar-color-3: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--scalar-color-1),
|
||||||
|
transparent calc(100% - 100% * 0.4)
|
||||||
|
);
|
||||||
--scalar-color-accent: #007d9c;
|
--scalar-color-accent: #007d9c;
|
||||||
|
|
||||||
--scalar-background-1: rgb(var(--light-base, 255 255 255));
|
--scalar-background-1: rgb(var(--light-base, 255 255 255));
|
||||||
@@ -50,8 +58,16 @@
|
|||||||
rgb(var(--primary-base-700, 70 70 70)),
|
rgb(var(--primary-base-700, 70 70 70)),
|
||||||
rgb(var(--light-base, 255 255 255)) 100%
|
rgb(var(--light-base, 255 255 255)) 100%
|
||||||
);
|
);
|
||||||
--scalar-color-2: color-mix(in srgb, var(--scalar-color-1), transparent calc(100% - 100% * 0.64));
|
--scalar-color-2: color-mix(
|
||||||
--scalar-color-3: color-mix(in srgb, var(--scalar-color-1), transparent calc(100% - 100% * 0.4));
|
in srgb,
|
||||||
|
var(--scalar-color-1),
|
||||||
|
transparent calc(100% - 100% * 0.64)
|
||||||
|
);
|
||||||
|
--scalar-color-3: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--scalar-color-1),
|
||||||
|
transparent calc(100% - 100% * 0.4)
|
||||||
|
);
|
||||||
--scalar-color-accent: #50b7e0;
|
--scalar-color-accent: #50b7e0;
|
||||||
|
|
||||||
--scalar-background-1: rgb(var(--dark-base, 22 22 22));
|
--scalar-background-1: rgb(var(--dark-base, 22 22 22));
|
||||||
@@ -330,4 +346,4 @@
|
|||||||
.scalar .custom-scroll {
|
.scalar .custom-scroll {
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user