mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-11-05 11:36:47 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fac399b3f | |||
| 1d8875416b | |||
| 24a889cf50 |
@@ -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,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Avoid multiple <h1> in the page by using a <div> for the title in the header
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Adds content-visibility css property to OpenAPI Operation for better render performance
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/cache-do': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Experiment with optimizing billable duration in Cloudflare by using multiple RPC sessions instead of one
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix scroll position when navigating pages on mobile
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add Python support in Code Samples
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
RND-3532: drop down menu for hidden links at small screen size
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Change dark mode shadow for multi-space search toolbar
|
|
||||||
@@ -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": []
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Use ellipsis and fix icon color for more links in the header on small screen
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Improve consistency of change request preview by removing cache-control on response
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/icons': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Update version for static assets to fix invalid caching
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Revalidate change request cached content when pressing refresh button
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/icons': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix first run of the package in development mode
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Improve performances of loading pages with embeds by caching them
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
bump: scalar from 1.0.5 to 1.0.7
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Replace all icons, previously imported from Geist, by new package `@gitbook/icons`
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/cache-do': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
First version of a new cache backend powered by Cloudflare Durable Objects
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add support for Norwegian language
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Optimize layout shift while transitioning between pages with full width blocks (ex: OpenAPI blocks)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add support for page icons
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix GitBook specific static assets not being served correctly when deployed on Cloudflare
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Only display spaces dropdown in compact header when site is multi-variants
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/cache-do': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Enable Workers observability with a sampling of 0.1
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/icons': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Initial release for `@gitbook/icons`, a package of content icons to be used for pages, documents, etc.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Automatically scroll to active item in TOC
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Persist state of tabs and dynamically sync them based on title
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Ensure "Sponsored via GitBook" can be translated in all languages
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Show the caption for file blocks
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Use url hash to open Expandable and scroll to anchor
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix margin for paragraphs in quote blocks
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Use new cache backend, powered by Durable Objects, alongside the existing ones (KV, etc).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Include offset in calculations of whether scrollable element is in view
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add required query parameters to the code sample
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Display pattern if available in parmas in OpenAPI block
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix inconsistent click area in table because of scroll indicator
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Stop using KV cache backend for now, but also improves it for higher performances
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': minor
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Synchronize response and response example tabs
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Show definition title when visible in cards
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': minor
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Rollback of scalar modal while fixing perf issue
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Optimize images rendered in community ads
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Update design of Checkbox to be more consistent and readable
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-contentkit': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix rendering of webframe with SSR causing wrong communication between frame and renderer
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Disable KV cache for docs.gitbook.com as a test, also disable it for change-request to improve consistency
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Add optional env `GITBOOK_INTEGRATIONS_HOST` to configure the host serving the integrations
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix crash when `example` is undefined for a response
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Improve the toolbar for change-requests and revisions to show more actions
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add support for icons style customization for sites
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Adds tabs to Response Example section e.g. for status code examples
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Render mandatory headers in code sample
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-openapi': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Update Scalar client to latest version
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Prevent search indexation for pages where it's configured as disabled
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix table of contents being visible on mobile when disabled at the page level
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix accessibility of the table of contents by using `aria-current` instead of `aria-selected`
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Update rendering of community ads to match new API response, and make it possible to preview ads.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/react-math': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Export binary `gitbook-math` to copy assets to a directory
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix "ip" being passed to BSA for community ads
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Changed the alternative URL resolution criteria in order to support site URLs without /v/ prefix
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@gitbook/icons': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix bin gitbook-icons when installed without the Pro token
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Don't show hidden pages in the empty state of a page
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'gitbook': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix linking to a tab itself
|
|
||||||
@@ -25,6 +25,3 @@
|
|||||||
|
|
||||||
### Sentry ###
|
### Sentry ###
|
||||||
# SENTRY_DSN=xxx
|
# SENTRY_DSN=xxx
|
||||||
|
|
||||||
### Silent logs
|
|
||||||
# SILENT=true
|
|
||||||
+5
-19
@@ -1,7 +1,5 @@
|
|||||||
# Welcome to GitBook's contributing guide!
|
# Welcome to GitBook's contributing guide!
|
||||||
|
|
||||||
> _For help, support, feature requests, and product questions - head to our [GitHub Community](https://github.com/orgs/GitbookIO/discussions) 🤖_
|
|
||||||
|
|
||||||
Thank you for investing your time in contributing to GitBook. Any contribution you make will be reviewed by our team.In this guide, you'll learn the different ways you can contribute.
|
Thank you for investing your time in contributing to GitBook. Any contribution you make will be reviewed by our team.In this guide, you'll learn the different ways you can contribute.
|
||||||
|
|
||||||
## Types of Contributions
|
## Types of Contributions
|
||||||
@@ -47,25 +45,13 @@ Any contribution you make can be made to the code located in this repository. In
|
|||||||
|
|
||||||
- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
|
- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
|
||||||
|
|
||||||
|
##### GitHub Codespaces:
|
||||||
|
|
||||||
|
- [Fork, edit, and preview](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace) using [GitHub Codespaces](https://github.com/features/codespaces) without having to install and run the project locally.
|
||||||
|
|
||||||
#### 2. Create a working branch and start with your changes
|
#### 2. Create a working branch and start with your changes
|
||||||
|
|
||||||
After forking this repository, you'll want to [create a branch](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-a-branch-for-an-issue) to work off of.
|
After forking this repository, you'll want to [create a branch](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-a-branch-for-an-issue) to work off of. After creating the branch, you can start making changes!
|
||||||
|
|
||||||
#### 3. Install dependencies and run the project locally
|
|
||||||
|
|
||||||
GitBook uses [Bun](https://bun.sh/) to run the project. Make sure you're using the specified version of `node` before running any of the development commands to ensure a smooth development experience.
|
|
||||||
|
|
||||||
You can easily do this by running the command `nvm use`.
|
|
||||||
|
|
||||||
To start your local version of GitBook, run the command `bun dev`.
|
|
||||||
|
|
||||||
#### 4. Preview your changes
|
|
||||||
|
|
||||||
When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/`.
|
|
||||||
|
|
||||||
For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/docs.gitbook.com` after running the development server.
|
|
||||||
|
|
||||||
You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://docs.gitbook.com/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version.
|
|
||||||
|
|
||||||
### Commit your update
|
### Commit your update
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ runs:
|
|||||||
- name: 🏗 Prepare Playwright env
|
- name: 🏗 Prepare Playwright env
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output '.dependencies["gitbook"].dependencies["@playwright/test"].version')
|
PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output '.dependencies["@playwright/test"].version')
|
||||||
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Cache browser binaries, cache key is based on Playwright version and OS
|
# Cache browser binaries, cache key is based on Playwright version and OS
|
||||||
@@ -24,7 +24,6 @@ runs:
|
|||||||
- name: 🏗 Install Playwright browser binaries & OS dependencies
|
- name: 🏗 Install Playwright browser binaries & OS dependencies
|
||||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: packages/gitbook
|
|
||||||
run: |
|
run: |
|
||||||
bun x playwright install --with-deps chromium
|
bun x playwright install --with-deps chromium
|
||||||
|
|
||||||
@@ -32,6 +31,5 @@ runs:
|
|||||||
- name: 🏗 Install Playwright OS dependencies
|
- name: 🏗 Install Playwright OS dependencies
|
||||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: packages/gitbook
|
|
||||||
run: |
|
run: |
|
||||||
bun x playwright install-deps
|
bun x playwright install-deps
|
||||||
|
|||||||
+11
-36
@@ -4,8 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
env:
|
|
||||||
NPMRC_FONT_AWESOME_TOKEN: ${{ secrets.NPMRC_FONT_AWESOME_TOKEN }}
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to Cloudflare Pages
|
name: Deploy to Cloudflare Pages
|
||||||
@@ -21,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:
|
||||||
@@ -59,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
|
||||||
@@ -83,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
|
||||||
@@ -93,12 +92,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
BASE_URL: ${{needs.deploy.outputs.deployment_url}}
|
BASE_URL: ${{needs.deploy.outputs.deployment_url}}
|
||||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
with:
|
|
||||||
name: playwright-test-results
|
|
||||||
path: packages/gitbook/test-results/
|
|
||||||
retention-days: 3
|
|
||||||
pagespeed-testing:
|
pagespeed-testing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: PageSpeed Testing
|
name: PageSpeed Testing
|
||||||
@@ -109,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 }}
|
||||||
@@ -128,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
|
||||||
@@ -143,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:
|
||||||
@@ -158,30 +151,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 unit
|
- run: bun unit
|
||||||
build-oss:
|
|
||||||
# CI to check that the repository builds correctly on a machine without the credentials
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Build (Open Source)
|
|
||||||
env:
|
|
||||||
NPMRC_FONT_AWESOME_TOKEN: ''
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- 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
|
|
||||||
- run: bun run build
|
|
||||||
typecheck:
|
typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Typecheck
|
name: Typecheck
|
||||||
@@ -191,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,60 +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 }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
release-preview:
|
|
||||||
# For now it releases the cache-do to both preview and production
|
|
||||||
# Once we changed to deploy the app only on release, we should change `release:preview` in `cache-do`
|
|
||||||
name: Release Preview
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- 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: Release preview packages
|
|
||||||
run: bun run release:preview
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
+29
-5
@@ -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,11 +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
|
||||||
|
|
||||||
# Env files
|
# typescript
|
||||||
.env.local
|
*.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,10 +1 @@
|
|||||||
.next
|
.next
|
||||||
.vercel
|
|
||||||
|
|
||||||
# Generated
|
|
||||||
packages/emoji-codepoints/index.ts
|
|
||||||
packages/gitbook/public/~gitbook/static/
|
|
||||||
packages/icons/src/data/*.json
|
|
||||||
|
|
||||||
# Build files
|
|
||||||
dist/
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://gitbook.com"><img src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1"></a>
|
<a href="https://gitbook.com"><img src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNjUiIGhlaWdodD0iNjUiIHZpZXdCb3g9IjAgMCA2NSA2NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI3LjM5NjQgMzMuNjc2NEMzMC41MjU1IDM1LjQ4MjQgMzIuMDkgMzYuMzg1NCAzMy44MDgzIDM2LjM4NjlDMzUuNTI2NSAzNi4zODg0IDM3LjA5MjYgMzUuNDg4MiA0MC4yMjQ5IDMzLjY4NzdMNjAuMTkxNCAyMi4yMTA0QzYxLjA5MjcgMjEuNjkyMiA2MS42NDg0IDIwLjczMTggNjEuNjQ4NCAxOS42OTIxQzYxLjY0ODQgMTguNjUyNCA2MS4wOTI3IDE3LjY5MiA2MC4xOTE0IDE3LjE3MzlMNDAuMjE3NyA1LjY5MjQ1QzM3LjA4ODggMy44OTM4NiAzNS41MjQzIDIuOTk0NTcgMzMuODA3OCAyLjk5NTI0QzMyLjA5MTIgMi45OTU5MSAzMC41Mjc1IDMuODk2NDIgMjcuNCA1LjY5NzQ0TDEwLjIyOTMgMTUuNTg1NUMxMC4xMDIgMTUuNjU4OCAxMC4wMzg0IDE1LjY5NTQgOS45NzkwOCAxNS43MzAxQzQuMTEzNzEgMTkuMTYzNyAwLjQ4OTg5MiAyNS40MzIzIDAuNDQxNDM4IDMyLjIyODZDMC40NDA5NDggMzIuMjk3MyAwLjQ0MDk0OCAzMi4zNzA4IDAuNDQwOTQ4IDMyLjUxNzZDMC40NDA5NDggMzIuNjY0MyAwLjQ0MDk0OCAzMi43Mzc2IDAuNDQxNDM3IDMyLjgwNjNDMC40ODk3ODUgMzkuNTk0OSA0LjEwNTUyIDQ1Ljg1NzcgOS45NjA0NCA0OS4yOTRDMTAuMDE5NiA0OS4zMjg3IDEwLjA4MzIgNDkuMzY1NCAxMC4yMTAyIDQ5LjQzODdMMjAuOTY1OSA1NS42NDg3QzI3LjIzMzIgNTkuMjY3MyAzMC4zNjY4IDYxLjA3NjYgMzMuODA4MSA2MS4wNzc3QzM3LjI0OTMgNjEuMDc4OSA0MC4zODQyIDU5LjI3MTcgNDYuNjUzOSA1NS42NTc0TDU4LjAwOCA0OS4xMTIxQzYxLjE0NzQgNDcuMzAyMyA2Mi43MTcxIDQ2LjM5NzQgNjMuNTc5IDQ0LjkwNTZDNjQuNDQwOSA0My40MTM5IDY0LjQ0MDkgNDEuNjAyIDY0LjQ0MDkgMzcuOTc4NFYzMC45NzgxQzY0LjQ0MDkgMjkuOTcyOCA2My44OTY1IDI5LjA0NjQgNjMuMDE4MiAyOC41NTczQzYyLjE2ODQgMjguMDgzOSA2MS4xMzI1IDI4LjA5MSA2MC4yODkxIDI4LjU3NThMMzcuMDA3NCA0MS45NTg4QzM1LjQ0NTQgNDIuODU2NyAzNC42NjQzIDQzLjMwNTYgMzMuODA3MyA0My4zMDU5QzMyLjk1MDIgNDMuMzA2MiAzMi4xNjg5IDQyLjg1NzcgMzAuNjA2MyA0MS45NjA3TDE0Ljg0ODcgMzIuOTE1NUMxNC4wNTk0IDMyLjQ2MjQgMTMuNjY0NyAzMi4yMzU5IDEzLjM0NzcgMzIuMTk1QzEyLjYyNSAzMi4xMDE3IDExLjkzMDEgMzIuNTA2NiAxMS42NTQ4IDMzLjE4MTNDMTEuNTM0MSAzMy40NzcyIDExLjUzNjUgMzMuOTMyMiAxMS41NDE0IDM0Ljg0MjRDMTEuNTQ1IDM1LjUxMjQgMTEuNTQ2OCAzNS44NDc0IDExLjYwOTQgMzYuMTU1NkMxMS43NDk3IDM2Ljg0NTYgMTIuMTEyNyAzNy40NzA1IDEyLjY0MjggMzcuOTM0MUMxMi44Nzk1IDM4LjE0MTEgMTMuMTY5NiAzOC4zMDg1IDEzLjc1IDM4LjY0MzVMMzAuNTk3NCA0OC4zNjcyQzMyLjE2NDEgNDkuMjcxNCAzMi45NDc0IDQ5LjcyMzUgMzMuODA3NSA0OS43MjM3QzM0LjY2NzcgNDkuNzIzOSAzNS40NTEzIDQ5LjI3MjMgMzcuMDE4NCA0OC4zNjg5TDU3LjY2ODQgMzYuNDY1NEM1OC4yMDM3IDM2LjE1NjkgNTguNDcxNCAzNi4wMDI2IDU4LjY3MjEgMzYuMTE4NUM1OC44NzI3IDM2LjIzNDUgNTguODcyNyAzNi41NDM0IDU4Ljg3MjcgMzcuMTYxM1Y0MC4zMzY1QzU4Ljg3MjcgNDEuMjQyNCA1OC44NzI3IDQxLjY5NTQgNTguNjU3MiA0Mi4wNjgzQzU4LjQ0MTggNDIuNDQxMyA1OC4wNDkzIDQyLjY2NzUgNTcuMjY0NCA0My4xMTk5TDQwLjIzMjIgNTIuOTM4QzM3LjA5NjYgNTQuNzQ1NCAzNS41Mjg4IDU1LjY0OTIgMzMuODA3OSA1NS42NDg0QzMyLjA4NjkgNTUuNjQ3NiAzMC41MTk5IDU0Ljc0MjQgMjcuMzg2IDUyLjkzMjFMMTEuNDUwOSA0My43MjdDMTEuNDAwMyA0My42OTc4IDExLjM3NSA0My42ODMyIDExLjM1MTQgNDMuNjY5NEM4LjAxMDIzIDQxLjcxNyA1Ljk0ODU5IDM4LjE0NTEgNS45MjkyNSAzNC4yNzU0QzUuOTI5MTIgMzQuMjQ4IDUuOTI5MTIgMzQuMjE4OCA1LjkyOTEyIDM0LjE2MDRWMzEuMjQ1OEM1LjkyOTEyIDI5LjEwOTUgNy4wNjY4OSAyNy4xMzQ5IDguOTE1MTMgMjYuMDYzNkMxMC41NDgzIDI1LjExNjkgMTIuNTYyOCAyNS4xMTUxIDE0LjE5NzcgMjYuMDU4N0wyNy4zOTY0IDMzLjY3NjRaIiBmaWxsPSIjRjJGN0Y3Ii8+Cjwvc3ZnPgo=&labelColor=5c5c5c&color=3F89A1&label=%20" alt="Documented on GitBook"></a>
|
||||||
<a href="#"><img src="https://img.shields.io/badge/Open_Source-❤️-FDA599?"/></a>
|
<a href="#"><img src="https://img.shields.io/badge/Open_Source-❤️-FDA599?"/></a>
|
||||||
<a href="/LICENSE"><img src="https://img.shields.io/badge/License-GNU_GPLv3-F4E28D"/></a>
|
<a href="/LICENSE"><img src="https://img.shields.io/badge/License-GNU_GPLv3-F4E28D"/></a>
|
||||||
<a href="/.github/CONTRIBUTING.md"><img src="https://img.shields.io/github/contributors/gitbookIO/gitbook"/></a>
|
<a href="/.github/CONTRIBUTING.md"><img src="https://img.shields.io/github/contributors/gitbookIO/gitbook"/></a>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<p align="center">Welcome to GitBook, the platform for managing technical knowledge for teams.</p>
|
<p align="center">Welcome to GitBook, the platform for managing technical knowledge for teams.</p>
|
||||||
|
|
||||||
<p align="center">This repository contains the open source code used to render GitBook's published content.</p>
|
<p align="center">This repository contains the open-source code used to render GitBook's published content.</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="GitBook Open Published Site" src="./assets/published-site.png">
|
<img alt="GitBook Open Published Site" src="./assets/published-site.png">
|
||||||
@@ -46,28 +46,19 @@ To run a local version of this project, please follow these simple steps.
|
|||||||
git clone https://github.com/gitbookIO/gitbook.git
|
git clone https://github.com/gitbookIO/gitbook.git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Ensure you are using the project's version of `node`. Running `nvm use` will change your local version to the correct one.
|
2. Install the project's dependencies through Bun.
|
||||||
|
|
||||||
3. Install the project's dependencies through Bun.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start your local development server.
|
3. Start your local development server.
|
||||||
|
|
||||||
```
|
```
|
||||||
bun dev
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`.
|
Then open the space in your web browser, using http://localhost:3000/<host>/<path> (example: http://localhost:3000/docs.gitbook.com).
|
||||||
|
|
||||||
examples:
|
|
||||||
|
|
||||||
- http://localhost:3000/docs.gitbook.com
|
|
||||||
- http://localhost:3000/open-source.gitbook.io/midjourney
|
|
||||||
|
|
||||||
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
|
|
||||||
|
|
||||||
### Other development commands
|
### Other development commands
|
||||||
|
|
||||||
@@ -80,7 +71,7 @@ All pull-requests will be tested against both visual and performances testing to
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
GitBook's rendering engine is fully open source and built on top of [Next.js](https://nextjs.org/). Head to our [contributing guide](https://github.com/GitbookIO/gitbook/blob/main/.github/CONTRIBUTING.md) to learn more about the workflow on adding your first Pull Request.
|
GitBook's rendering engine is fully open-source and built on top of [Next.js](https://nextjs.org/). Head to our [contributing guide](https://github.com/GitbookIO/gitbook/.github/CONTRIBUTING.md) to learn more about the workflow on adding your first Pull Request.
|
||||||
|
|
||||||
### Types of contributions
|
### Types of contributions
|
||||||
|
|
||||||
@@ -88,7 +79,7 @@ We encourage you to contribute to GitBook to help us build the best tool for doc
|
|||||||
|
|
||||||
#### Translations
|
#### Translations
|
||||||
|
|
||||||
The GitBook UI is rendered using a set of translation files found in [`packages/gitbook/src/intl/translations`](/packages/gitbook/src/intl/translations/). We welcome all additional translations for the UI.
|
The GitBook UI is rendered using a set of translation files found in [`src/intl/translations`](/src/intl/translations/). We welcome all additional translations for the UI.
|
||||||
|
|
||||||
#### Bugs
|
#### Bugs
|
||||||
|
|
||||||
@@ -99,7 +90,7 @@ Encounter a bug or find an issue you'd like to fix? Helping us fix issues relate
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> While it is possible to self-host this project, we do not recommend this unless you are certain this option fits your need.
|
> While it is possible to self-host this project, we do not recommend this unless you are certain this option fits your need.
|
||||||
>
|
>
|
||||||
> _Looking to add a specific feature in GitBook? Head to our [contributing guide](https://github.com/GitbookIO/gitbook/blob/main/.github/CONTRIBUTING.md) to get started._
|
> _Looking to add a specific feature in GitBook? Head to our [contributing guide](/.github/CONTRIBUTING.md) to get started._
|
||||||
>
|
>
|
||||||
> Self-hosting this project puts the responsibility of maintaining and merging future updates on **you**. We cannot guarantee support, maintenance, or updates to forked and self-hosted instances of this project.
|
> Self-hosting this project puts the responsibility of maintaining and merging future updates on **you**. We cannot guarantee support, maintenance, or updates to forked and self-hosted instances of this project.
|
||||||
>
|
>
|
||||||
@@ -115,30 +106,10 @@ On the con side, you become responsible for the reliability of your published si
|
|||||||
|
|
||||||
Distributed under the [GNU GPLv3 License](https://github.com/GitBookIO/gitbook/blob/main/LICENSE).
|
Distributed under the [GNU GPLv3 License](https://github.com/GitBookIO/gitbook/blob/main/LICENSE).
|
||||||
|
|
||||||
If you plan to distribute the code, you must make the source code public to comply with the GNU GPLv3. To clone in a private repository, acquire a [commercial license](https://www.gitbook.com/pricing).
|
If you plan to distribute the code, you must the source code public to comply with GNU GPLv3. To clone in a private repository, acquire a [commercial license](https://www.gitbook.com/pricing).
|
||||||
|
|
||||||
See `LICENSE` for more information.
|
See `LICENSE` for more information.
|
||||||
|
|
||||||
## Badges
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://gitbook.com"><img src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1"></a>
|
|
||||||
<a href="https://gitbook.com"><img src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=F4E28D"></a>
|
|
||||||
<a href="https://gitbook.com"><img src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=FDA599"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://gitbook.com/)
|
|
||||||
```
|
|
||||||
|
|
||||||
```html
|
|
||||||
<a href="https://gitbook.com">
|
|
||||||
<img
|
|
||||||
src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
GitBook wouldn't be possible without these projects:
|
GitBook wouldn't be possible without these projects:
|
||||||
@@ -148,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).
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[install.scopes]
|
|
||||||
"awesome.me" = { token = "$NPMRC_FONT_AWESOME_TOKEN", url = "https://npm.fontawesome.com/" }
|
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
import { argosScreenshot } from '@argos-ci/playwright';
|
import { argosScreenshot } from '@argos-ci/playwright';
|
||||||
import {
|
import {
|
||||||
CustomizationHeaderPreset,
|
CustomizationHeaderPreset,
|
||||||
CustomizationIconsStyle,
|
|
||||||
CustomizationLocale,
|
CustomizationLocale,
|
||||||
SiteCustomizationSettings,
|
CustomizationSettings,
|
||||||
} from '@gitbook/api';
|
} from '@gitbook/api';
|
||||||
import { test, expect, Page } from '@playwright/test';
|
import { test, expect, Page } from '@playwright/test';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { DeepPartial } from 'ts-essentials';
|
|
||||||
|
|
||||||
import { getContentTestURL } from '../tests/utils';
|
import { getContentTestURL } from '../tests/utils';
|
||||||
|
|
||||||
interface Test {
|
interface Test {
|
||||||
name: string;
|
name: string;
|
||||||
url: string; // URL to visit for testing
|
url: string;
|
||||||
run?: (page: Page) => Promise<unknown>; // The test to run
|
run?: (page: Page) => Promise<unknown>;
|
||||||
fullPage?: boolean; // Whether the test should be fullscreened during testing
|
fullPage?: boolean;
|
||||||
screenshot?: false; // Should a screenshot be stored
|
screenshot?: false;
|
||||||
only?: boolean; // Only run this test
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TestsCase {
|
interface TestsCase {
|
||||||
@@ -105,119 +102,7 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'RFC variant',
|
name: 'RFC variant',
|
||||||
url: 'rfcs',
|
url: 'v/rfcs',
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Customized variant titles are displayed',
|
|
||||||
url: '',
|
|
||||||
run: async (page) => {
|
|
||||||
const spaceDrowpdown = page.locator('[data-testid="space-dropdown-button"]');
|
|
||||||
await spaceDrowpdown.click();
|
|
||||||
|
|
||||||
const variantSelectionDropdown = page.locator(
|
|
||||||
'css=[data-testid="space-dropdown-button"] + div',
|
|
||||||
);
|
|
||||||
// the customized space title
|
|
||||||
await expect(
|
|
||||||
variantSelectionDropdown.getByRole('link', {
|
|
||||||
name: 'Multi-Variants',
|
|
||||||
}),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
// the NON-customized space title
|
|
||||||
await expect(
|
|
||||||
variantSelectionDropdown.getByRole('link', {
|
|
||||||
name: 'RFCs',
|
|
||||||
}),
|
|
||||||
).toBeVisible();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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/2.0/reference/api-reference/pets?fallback=true',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Keep navigation path/route when switching variant (Share link)',
|
|
||||||
url: 'api-multi-versions-share-links/8tNo6MeXg7CkFMzSSz81/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/8tNo6MeXg7CkFMzSSz81/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/2.0/reference/api-reference/pets?fallback=true',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -258,7 +143,7 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Versioning',
|
name: 'Versioning',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
name: 'Revision',
|
name: 'Revision',
|
||||||
@@ -269,7 +154,7 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'PDF',
|
name: 'PDF',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
name: 'PDF',
|
name: 'PDF',
|
||||||
@@ -279,7 +164,7 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Content tests',
|
name: 'Content tests',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
name: 'Text',
|
name: 'Text',
|
||||||
@@ -372,21 +257,11 @@ const testCases: TestsCase[] = [
|
|||||||
name: 'Math',
|
name: 'Math',
|
||||||
url: 'blocks/math',
|
url: 'blocks/math',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Files',
|
|
||||||
url: 'blocks/files',
|
|
||||||
fullPage: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Embeds',
|
name: 'Embeds',
|
||||||
url: 'blocks/embeds',
|
url: 'blocks/embeds',
|
||||||
fullPage: true,
|
fullPage: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Page links',
|
|
||||||
url: 'blocks/page-links',
|
|
||||||
fullPage: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Annotations',
|
name: 'Annotations',
|
||||||
url: 'blocks/annotations',
|
url: 'blocks/annotations',
|
||||||
@@ -399,13 +274,8 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Page options',
|
name: 'Page options',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
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',
|
||||||
@@ -421,16 +291,11 @@ const testCases: TestsCase[] = [
|
|||||||
url: 'page-options/page-with-cover-and-no-toc',
|
url: 'page-options/page-with-cover-and-no-toc',
|
||||||
run: waitForCookiesDialog,
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'With icon',
|
|
||||||
url: 'page-options/page-with-icon',
|
|
||||||
run: waitForCookiesDialog,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customization',
|
name: 'Customization',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
name: 'Without header',
|
name: 'Without header',
|
||||||
@@ -442,60 +307,16 @@ const testCases: TestsCase[] = [
|
|||||||
}),
|
}),
|
||||||
run: waitForCookiesDialog,
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'With duotone icons',
|
|
||||||
url:
|
|
||||||
'page-options/page-with-icon' +
|
|
||||||
getCustomizationURL({
|
|
||||||
styling: {
|
|
||||||
icons: CustomizationIconsStyle.Duotone,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
run: waitForCookiesDialog,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'With header buttons',
|
|
||||||
url: getCustomizationURL({
|
|
||||||
header: {
|
|
||||||
preset: CustomizationHeaderPreset.Default,
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: 'Secondary button',
|
|
||||||
to: { kind: 'url', url: 'https://www.gitbook.com' },
|
|
||||||
// @ts-ignore Remove once we upgrade to the latest version of the API
|
|
||||||
style: 'button-secondary',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Primary button',
|
|
||||||
to: { kind: 'url', url: 'https://www.gitbook.com' },
|
|
||||||
// @ts-ignore Remove once we upgrade to the latest version of the API
|
|
||||||
style: 'button-primary',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
run: waitForCookiesDialog,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Ads',
|
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
|
||||||
tests: [
|
|
||||||
{
|
|
||||||
name: 'Without previewed ads',
|
|
||||||
url: 'text-page?ads_preview=1',
|
|
||||||
run: waitForCookiesDialog,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Share links',
|
name: 'Share links',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/gbo-tests-share-links/',
|
baseUrl: 'https://gitbook.gitbook.io/test-share-links/',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
name: 'Valid link',
|
name: 'Valid link',
|
||||||
url: 'TGs8PkF4GWVtbmPnWhYL/',
|
url: 'Fc6mMII9FKgnwm7qqynx/',
|
||||||
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Invalid link',
|
name: 'Invalid link',
|
||||||
@@ -582,7 +403,7 @@ const testCases: TestsCase[] = [
|
|||||||
expiresIn: '24h',
|
expiresIn: '24h',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return `spacea?jwt_token=${token}`;
|
return `v/spacea?jwt_token=${token}`;
|
||||||
})(),
|
})(),
|
||||||
run: waitForCookiesDialog,
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
@@ -599,7 +420,7 @@ const testCases: TestsCase[] = [
|
|||||||
expiresIn: '24h',
|
expiresIn: '24h',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return `spaceb?jwt_token=${token}`;
|
return `v/spaceb?jwt_token=${token}`;
|
||||||
})(),
|
})(),
|
||||||
run: waitForCookiesDialog,
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
@@ -616,7 +437,7 @@ const testCases: TestsCase[] = [
|
|||||||
expiresIn: '24h',
|
expiresIn: '24h',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return `spacec?jwt_token=${token}`;
|
return `v/spacec?jwt_token=${token}`;
|
||||||
})(),
|
})(),
|
||||||
run: waitForCookiesDialog,
|
run: waitForCookiesDialog,
|
||||||
},
|
},
|
||||||
@@ -698,12 +519,13 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Languages',
|
name: 'Languages',
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
baseUrl: 'https://gitbook.gitbook.io/test-1-1/',
|
||||||
tests: allLocales.map((locale) => ({
|
tests: allLocales.map((locale) => ({
|
||||||
name: locale,
|
name: locale,
|
||||||
url: getCustomizationURL({
|
url: getCustomizationURL({
|
||||||
internationalization: {
|
internationalization: {
|
||||||
locale,
|
locale,
|
||||||
|
inherit: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
run: async (page) => {
|
run: async (page) => {
|
||||||
@@ -712,64 +534,12 @@ const testCases: TestsCase[] = [
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'SEO',
|
|
||||||
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
|
|
||||||
tests: [
|
|
||||||
{
|
|
||||||
name: `Index by default`,
|
|
||||||
url: '?x-gitbook-search-indexation=true',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const metaRobots = page.locator('meta[name="robots"]');
|
|
||||||
await expect(metaRobots).toHaveAttribute('content', 'index, follow');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Don't index noIndex`,
|
|
||||||
url: 'page-options/page-no-index?x-gitbook-search-indexation=true',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const metaRobots = page.locator('meta[name="robots"]');
|
|
||||||
await expect(metaRobots).toHaveAttribute('content', 'noindex, nofollow');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Don't index descendant of noIndex`,
|
|
||||||
url: 'page-options/page-no-index/descendant-of-page-no-index?x-gitbook-search-indexation=true',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const metaRobots = page.locator('meta[name="robots"]');
|
|
||||||
await expect(metaRobots).toHaveAttribute('content', 'noindex, nofollow');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Don't index noRobotsIndex`,
|
|
||||||
url: 'page-options/page-no-robots-index?x-gitbook-search-indexation=true',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const metaRobots = page.locator('meta[name="robots"]');
|
|
||||||
await expect(metaRobots).toHaveAttribute('content', 'noindex, nofollow');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Don't index descendant of noRobotsIndex`,
|
|
||||||
url: 'page-options/page-no-robots-index/descendant-of-page-no-robots-index?x-gitbook-search-indexation=true',
|
|
||||||
screenshot: false,
|
|
||||||
run: async (page) => {
|
|
||||||
const metaRobots = page.locator('meta[name="robots"]');
|
|
||||||
await expect(metaRobots).toHaveAttribute('content', 'noindex, nofollow');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
test.describe(testCase.name, () => {
|
test.describe(testCase.name, () => {
|
||||||
for (const testEntry of testCase.tests) {
|
for (const testEntry of testCase.tests) {
|
||||||
const testFn = testEntry.only ? test.only : test;
|
test(testEntry.name, async ({ page, baseURL }) => {
|
||||||
testFn(testEntry.name, async ({ page, baseURL }) => {
|
|
||||||
const contentUrl = new URL(testEntry.url, testCase.baseUrl);
|
const contentUrl = new URL(testEntry.url, testCase.baseUrl);
|
||||||
const url = getContentTestURL(contentUrl.toString(), baseURL);
|
const url = getContentTestURL(contentUrl.toString(), baseURL);
|
||||||
await page.goto(url);
|
await page.goto(url);
|
||||||
@@ -796,7 +566,7 @@ for (const testCase of testCases) {
|
|||||||
/**
|
/**
|
||||||
* Create a URL with customization settings.
|
* Create a URL with customization settings.
|
||||||
*/
|
*/
|
||||||
function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettings>): string {
|
function getCustomizationURL(partial: Partial<CustomizationSettings>): string {
|
||||||
const encoded = rison.encode_object(partial);
|
const encoded = rison.encode_object(partial);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
@@ -7,11 +7,9 @@ module.exports = withSentryConfig(
|
|||||||
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
||||||
SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT ?? 'development',
|
SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT ?? 'development',
|
||||||
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
|
||||||
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
|
|
||||||
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack(config, { dev, webpack }) {
|
webpack(config) {
|
||||||
config.resolve.fallback = {
|
config.resolve.fallback = {
|
||||||
...config.resolve.fallback,
|
...config.resolve.fallback,
|
||||||
|
|
||||||
@@ -21,21 +19,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;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -62,9 +45,9 @@ module.exports = withSentryConfig(
|
|||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: '*.gitbook.io',
|
hostname: '*.gitbook.io',
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
silent: true,
|
silent: true,
|
||||||
+81
-23
@@ -1,34 +1,92 @@
|
|||||||
{
|
{
|
||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"devDependencies": {
|
|
||||||
"@changesets/cli": "^2.27.7",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"turbo": "^2.1.2"
|
|
||||||
},
|
|
||||||
"packageManager": "bun@1.1.18",
|
|
||||||
"patchedDependencies": {
|
|
||||||
"@vercel/next@4.3.6": "patches/@vercel%2Fnext@4.3.6.patch"
|
|
||||||
},
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"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",
|
||||||
"lint:fix": "turbo run lint -- --fix",
|
"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",
|
||||||
"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 release && 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"
|
||||||
"release:preview": "turbo run release:preview",
|
|
||||||
"download:env": "op read op://gitbook-x-dev/gitbook-open/.env.local >> .env.local",
|
|
||||||
"clean": "turbo run clean"
|
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@geist-ui/icons": "^1.0.2",
|
||||||
|
"@gitbook/api": "^0.46.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.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"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@argos-ci/playwright": "^2.0.0",
|
||||||
|
"@cloudflare/next-on-pages": "^1.9.0",
|
||||||
|
"@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,3 +0,0 @@
|
|||||||
.wrangler
|
|
||||||
worker-configuration.d.ts
|
|
||||||
dist/
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# `@gitbook/cache-do`
|
|
||||||
|
|
||||||
Cache backend, powered by Cloudflare Durable Objects. The cache is optimized for GitBook use-cases.
|
|
||||||
|
|
||||||
### Performances
|
|
||||||
|
|
||||||
The cache backend is optimized for performances by being distributed and accessible close to the worker locations that are reading it.
|
|
||||||
|
|
||||||
### Geo-distribution
|
|
||||||
|
|
||||||
To achieve a good balance between **performances** and **consistency**, cache objects are distributed over 7 locations, representing continents.
|
|
||||||
|
|
||||||
It makes it possible to purge all 7 locations in one go and achieve fast consistency.
|
|
||||||
|
|
||||||
### Concepts
|
|
||||||
|
|
||||||
**Cache tag**: unique tag in the cache environment. A cache tag groups multiple keys that should be purged together in one operation.
|
|
||||||
Cache tags should not contain a large set of unique keys. Exceeding thousands could lead to performances or reliability issues.
|
|
||||||
|
|
||||||
**Cache key**: unique key in the cache environment. Each key should be assigned to a `tag`.
|
|
||||||
|
|
||||||
**Location**: cache is distributed over 7 unique locations, one for each continent.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@gitbook/cache-do",
|
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"development": "./src/index.ts",
|
|
||||||
"default": "./dist/index.js"
|
|
||||||
},
|
|
||||||
"./api": {
|
|
||||||
"types": "./dist/api.d.ts",
|
|
||||||
"development": "./src/api.ts",
|
|
||||||
"default": "./dist/api.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": "0.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
||||||
"lru_map": "^0.4.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.5.3",
|
|
||||||
"wrangler": "^3.78.7"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"generate": "wrangler types --experimental-include-runtime",
|
|
||||||
"build": "tsc",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"dev": "tsc -w",
|
|
||||||
"release": "wrangler deploy",
|
|
||||||
"release:preview": "wrangler deploy && wrangler deploy --env preview"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src",
|
|
||||||
"bin",
|
|
||||||
"data",
|
|
||||||
"README.md",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
import { encode, decode } from '@msgpack/msgpack';
|
|
||||||
import { DurableObject } from 'cloudflare:workers';
|
|
||||||
import { LRUMap } from 'lru_map';
|
|
||||||
|
|
||||||
export interface CacheObjectDescriptor {
|
|
||||||
get: <Value = unknown>(key: string) => Promise<Value | undefined>;
|
|
||||||
set: <Value = unknown>(key: string, value: Value, expiresAt: number) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value stored in a chunked binary msgpack format.
|
|
||||||
* Stored under the key `prop.${key}.${index}`.
|
|
||||||
*/
|
|
||||||
interface CacheObjectProp<Value = unknown> {
|
|
||||||
value: Value;
|
|
||||||
expiresAt: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expiration clock stored under the key `exp.${expiresAt}.${key}`.
|
|
||||||
*/
|
|
||||||
interface CacheObjectExp {
|
|
||||||
/** Key of the property */
|
|
||||||
k: string;
|
|
||||||
/** Number of chunks */
|
|
||||||
c: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Durable Object class being deployed as a distributed cache.
|
|
||||||
*/
|
|
||||||
export class CacheObject extends DurableObject {
|
|
||||||
private lru = new LRUMap<string, { match: CacheObjectProp | undefined }>(500);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a descriptor to access the cache object.
|
|
||||||
* The goal is to minimize the amount of RPC sessions between the client and the cache object.
|
|
||||||
* One session is opened per request on the client side and used to perform multiple operations.
|
|
||||||
* https://developers.cloudflare.com/workers/runtime-apis/rpc/#return-functions-from-rpc-methods
|
|
||||||
*/
|
|
||||||
public open(): CacheObjectDescriptor {
|
|
||||||
return {
|
|
||||||
get: async <Value = unknown>(key: string) => {
|
|
||||||
return this.get<Value>(key);
|
|
||||||
},
|
|
||||||
set: async <Value = unknown>(key: string, value: Value, expiresAt: number) => {
|
|
||||||
await this.set(key, value, expiresAt);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of a property.
|
|
||||||
*/
|
|
||||||
public async get<Value = unknown>(key: string) {
|
|
||||||
return this.logOperation({ operation: 'get', key }, async (setLog) => {
|
|
||||||
// Try the memory state first.
|
|
||||||
const memoryEntry = this.lru.get(key);
|
|
||||||
if (memoryEntry) {
|
|
||||||
setLog({ memory: true });
|
|
||||||
setLog({ memoryMatch: !!memoryEntry.match });
|
|
||||||
if (!memoryEntry.match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isExpired = memoryEntry.match.expiresAt < Date.now();
|
|
||||||
setLog({ memoryExpired: isExpired });
|
|
||||||
|
|
||||||
if (!isExpired) {
|
|
||||||
return memoryEntry.match.value as Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.getFromStorage<Value>(key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of a property from the DO storage.
|
|
||||||
*/
|
|
||||||
public async getFromStorage<Value = unknown>(key: string) {
|
|
||||||
return this.logOperation({ operation: 'getFromStorage', key }, async (setLog) => {
|
|
||||||
const entries = await this.ctx.storage.list<Uint8Array>({
|
|
||||||
prefix: getStoragePropKey(key),
|
|
||||||
noCache: true,
|
|
||||||
});
|
|
||||||
if (entries.size) {
|
|
||||||
const entry = decodeChunks<CacheObjectProp<Value>>(entries);
|
|
||||||
setLog({ chunks: entries.size, chunksSize: entry?.size ?? 0 });
|
|
||||||
if (entry && entry.value.expiresAt > Date.now()) {
|
|
||||||
// Found
|
|
||||||
this.lru.set(key, { match: entry.value });
|
|
||||||
return entry.value.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not found
|
|
||||||
this.lru.set(key, { match: undefined });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a value in the cache object.
|
|
||||||
*/
|
|
||||||
public async set<Value = unknown>(key: string, value: Value, expiresAt: number) {
|
|
||||||
return this.logOperation({ operation: 'set', key }, async (setLog) => {
|
|
||||||
const prop: CacheObjectProp<Value> = {
|
|
||||||
value,
|
|
||||||
expiresAt,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.lru.set(key, { match: prop });
|
|
||||||
await this.ctx.storage.transaction(async (tx) => {
|
|
||||||
const entries = encodeChunks(key, prop);
|
|
||||||
const chunks = Object.keys(entries).length;
|
|
||||||
setLog({ chunks });
|
|
||||||
|
|
||||||
const clockValue: CacheObjectExp = {
|
|
||||||
k: key,
|
|
||||||
c: chunks,
|
|
||||||
};
|
|
||||||
|
|
||||||
await tx.put(getGCClockKey(key, expiresAt), clockValue);
|
|
||||||
await tx.put(entries);
|
|
||||||
|
|
||||||
const currentAlarm = await tx.getAlarm();
|
|
||||||
if (!currentAlarm) {
|
|
||||||
// Set an alarm to garbage collect all entries that have expired in 12h.
|
|
||||||
await tx.setAlarm(Date.now() + 12 * 60 * 60 * 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purge all keys in the cache object.
|
|
||||||
*/
|
|
||||||
public async purge() {
|
|
||||||
return this.logOperation({ operation: 'purge' }, async (setLog) => {
|
|
||||||
let result = new Set<string>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// List all the keys in the cache object.
|
|
||||||
const entries = await this.ctx.storage.list<CacheObjectExp>({
|
|
||||||
prefix: 'exp.',
|
|
||||||
noCache: true,
|
|
||||||
});
|
|
||||||
setLog({ entries: entries.size });
|
|
||||||
entries.forEach((exp) => {
|
|
||||||
result.add(exp.k);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// If an error occurs, reset the cache object.
|
|
||||||
// This is a safety mechanism to prevent the cache object from being stuck in a bad state.
|
|
||||||
console.error('Error during purge, resetting the cache object', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.reset();
|
|
||||||
return Array.from(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alarm to garbage collect all entries that have expired.
|
|
||||||
*/
|
|
||||||
async alarm() {
|
|
||||||
return this.logOperation({ operation: 'alarm' }, async (setLog) => {
|
|
||||||
try {
|
|
||||||
const entries = await this.ctx.storage.list<CacheObjectExp>({
|
|
||||||
prefix: 'exp.',
|
|
||||||
noCache: true,
|
|
||||||
});
|
|
||||||
setLog({ entries: entries.size });
|
|
||||||
const toDeleteSet = new Set<string>();
|
|
||||||
|
|
||||||
for (const [key, exp] of entries) {
|
|
||||||
const timestamp = parseInt(key.split('.')[1]);
|
|
||||||
if (timestamp < Date.now()) {
|
|
||||||
toDeleteSet.add(key);
|
|
||||||
for (let i = 0; i < exp.c; i++) {
|
|
||||||
toDeleteSet.add(getStoragePropChunkKey(exp.k, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the keys by batch of 128.
|
|
||||||
const toDelete = Array.from(toDeleteSet);
|
|
||||||
setLog({ toDelete: toDelete.length });
|
|
||||||
for (let i = 0; i < toDelete.length; i += 128) {
|
|
||||||
await this.ctx.storage.delete(toDelete.slice(i, i + 128));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are still keys to delete, set an alarm to continue the deletion in 12h.
|
|
||||||
if (toDelete.length) {
|
|
||||||
await this.ctx.storage.setAlarm(Date.now() + 12 * 60 * 60 * 1000);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If an error occurs, reset the cache object.
|
|
||||||
// This is a safety mechanism to prevent the cache object from being stuck in a bad state.
|
|
||||||
console.error('Error during alarm, reset the cache object', error);
|
|
||||||
await this.reset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the cache object.
|
|
||||||
*/
|
|
||||||
async reset() {
|
|
||||||
return this.logOperation({ operation: 'reset' }, async () => {
|
|
||||||
this.lru.clear();
|
|
||||||
await this.ctx.storage.deleteAll();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time and log an operation.
|
|
||||||
*/
|
|
||||||
async logOperation<T>(
|
|
||||||
log: Record<string, unknown>,
|
|
||||||
fn: (update: (log: Record<string, unknown>) => void) => Promise<T>,
|
|
||||||
): Promise<T> {
|
|
||||||
const objectId = this.ctx.id.name ?? this.ctx.id.toString();
|
|
||||||
let update: Record<string, unknown> = {};
|
|
||||||
const start = performance.now();
|
|
||||||
try {
|
|
||||||
return await fn((arg) => {
|
|
||||||
Object.assign(update, arg);
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
const duration = performance.now() - start;
|
|
||||||
console.log({ ...log, ...update, objectId, duration });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStoragePropKey(key: string): string {
|
|
||||||
return `prop.${key}.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStoragePropChunkKey(key: string, index: number): string {
|
|
||||||
return `${getStoragePropKey(key)}${index}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGCClockRootKey(timestamp: number): string {
|
|
||||||
return `exp.${timestamp}.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGCClockKey(key: string, expiresAt: number): string {
|
|
||||||
return `${getGCClockRootKey(expiresAt)}${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeChunks<T>(key: string, value: T): Record<string, Uint8Array> {
|
|
||||||
const buf = encode(value);
|
|
||||||
const entries: Record<string, Uint8Array> = {};
|
|
||||||
const chunks = chunkUint8Array(buf, 128 * 1024);
|
|
||||||
|
|
||||||
for (let index = 0; index < chunks.length; index++) {
|
|
||||||
entries[getStoragePropChunkKey(key, index)] = chunks[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeChunks<T>(entries: Map<string, Uint8Array>): { value: T; size: number } | undefined {
|
|
||||||
const chunks = Array.from(entries.entries())
|
|
||||||
.map(([key, value]) => {
|
|
||||||
const index = parseInt(key.split('.').pop()!);
|
|
||||||
return [index, value] as const;
|
|
||||||
})
|
|
||||||
.sort(([a], [b]) => a - b)
|
|
||||||
.map(([, value]) => value);
|
|
||||||
|
|
||||||
if (chunks.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buf = mergeUint8Array(chunks);
|
|
||||||
return { value: decode(buf) as T, size: buf.length };
|
|
||||||
}
|
|
||||||
|
|
||||||
function chunkUint8Array(input: Uint8Array, chunkSize: number): Uint8Array[] {
|
|
||||||
const chunks: Uint8Array[] = [];
|
|
||||||
for (let i = 0; i < input.length; i += chunkSize) {
|
|
||||||
chunks.push(input.slice(i, i + chunkSize));
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeUint8Array(chunks: Uint8Array[]): Uint8Array {
|
|
||||||
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
||||||
const result = new Uint8Array(totalLength);
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
result.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import type { CacheObject, CacheObjectDescriptor } from './CacheObject';
|
|
||||||
|
|
||||||
export type CacheLocationId = ContinentCode;
|
|
||||||
const allLocations: CacheLocationId[] = ['AF', 'AS', 'NA', 'SA', 'AN', 'EU', 'OC'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Location hint for the CacheObject durable object.
|
|
||||||
*/
|
|
||||||
const doLocationHints: {
|
|
||||||
[key in CacheLocationId]: DurableObjectLocationHint;
|
|
||||||
} = {
|
|
||||||
AF: 'afr',
|
|
||||||
AS: 'apac',
|
|
||||||
NA: 'wnam',
|
|
||||||
SA: 'sam',
|
|
||||||
AN: 'oc',
|
|
||||||
EU: 'weur',
|
|
||||||
OC: 'oc',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client to access a cache tag.
|
|
||||||
*/
|
|
||||||
export class CacheObjectStub {
|
|
||||||
private stub: DurableObjectStub<CacheObject>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
/** Binding to the CacheObject durable object */
|
|
||||||
private doNamespace: DurableObjectNamespace<CacheObject>,
|
|
||||||
/** ID of the location to target */
|
|
||||||
private locationId: CacheLocationId,
|
|
||||||
/** Name of the tag */
|
|
||||||
private tag: string,
|
|
||||||
) {
|
|
||||||
const groupId = getCacheObjectIdName(this.locationId, this.tag);
|
|
||||||
this.stub = this.doNamespace.get(this.doNamespace.idFromName(groupId), {
|
|
||||||
// Initialize the object with a locaiton hint,
|
|
||||||
// as we might want to purge all locations before the object is created.
|
|
||||||
// https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
||||||
locationHint: doLocationHints[this.locationId],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a descriptor to the cache object.
|
|
||||||
* It can be used to perform multiple operations in a single RPC session.
|
|
||||||
* Ex:
|
|
||||||
* ```ts
|
|
||||||
* using desc = cache.open();
|
|
||||||
* await desc.set('key', 'value', Date.now() + 1000);
|
|
||||||
* await desc.get('key');
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
async open() {
|
|
||||||
return await this.stub.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a value from the cache.
|
|
||||||
*/
|
|
||||||
async get<Value = unknown>(key: string) {
|
|
||||||
return (await this.stub.get(key)) as Value | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a value in the cache.
|
|
||||||
*/
|
|
||||||
async set<Value = unknown>(key: string, value: Value, expiresAt: number) {
|
|
||||||
return await this.stub.set(key, value, expiresAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purge all keys in the cache tag.
|
|
||||||
*/
|
|
||||||
async purge() {
|
|
||||||
const keys = new Set<string>();
|
|
||||||
await Promise.all(
|
|
||||||
allLocations.map(async (locationId) => {
|
|
||||||
const groupId = getCacheObjectIdName(locationId, this.tag);
|
|
||||||
const cacheGroup = this.doNamespace.get(this.doNamespace.idFromName(groupId), {
|
|
||||||
// Initialize the object with a locaiton hint,
|
|
||||||
// as we might want to purge all locations before the object is created.
|
|
||||||
// https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
||||||
locationHint: doLocationHints[this.locationId],
|
|
||||||
});
|
|
||||||
const locationkeys = await cacheGroup.purge();
|
|
||||||
locationkeys.forEach((key) => keys.add(key));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCacheObjectIdName(locationId: CacheLocationId, tag: string): string {
|
|
||||||
return `${locationId}:${tag}`;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './CacheObjectStub';
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
||||||
|
|
||||||
export * from './CacheObject';
|
|
||||||
|
|
||||||
export default class Worker extends WorkerEntrypoint {
|
|
||||||
fetch() {
|
|
||||||
return new Response('Hello, world!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
"incremental": true,
|
|
||||||
"types": ["./.wrangler/types/runtime.d.ts"]
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
main = "./src/index.ts"
|
|
||||||
name = "gitbook-open-cache"
|
|
||||||
compatibility_date = "2024-09-02"
|
|
||||||
|
|
||||||
durable_objects.bindings = [
|
|
||||||
{name = "CACHE", class_name = "CacheObject"}
|
|
||||||
]
|
|
||||||
|
|
||||||
migrations = [
|
|
||||||
{tag = "v1", new_classes = ["CacheObject"]}
|
|
||||||
]
|
|
||||||
|
|
||||||
[observability]
|
|
||||||
enabled = true
|
|
||||||
head_sampling_rate = 0.01
|
|
||||||
|
|
||||||
[env.preview]
|
|
||||||
name = "gitbook-open-cache-preview"
|
|
||||||
durable_objects.bindings = [
|
|
||||||
{name = "CACHE", class_name = "CacheObject"}
|
|
||||||
]
|
|
||||||
migrations = [
|
|
||||||
{tag = "v1", new_classes = ["CacheObject"]}
|
|
||||||
]
|
|
||||||
|
|
||||||
[env.preview.observability]
|
|
||||||
enabled = true
|
|
||||||
head_sampling_rate = 1
|
|
||||||
@@ -1 +1 @@
|
|||||||
dist/
|
index.ts
|
||||||
|
|||||||
@@ -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,5 +1,4 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'fs';
|
||||||
import path from 'node:path';
|
|
||||||
import emojisRaws from 'emoji-assets/emoji.json';
|
import emojisRaws from 'emoji-assets/emoji.json';
|
||||||
|
|
||||||
interface EmojiData {
|
interface EmojiData {
|
||||||
@@ -21,8 +20,7 @@ Object.entries(emojis).forEach(([key, value]) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.mkdirSync(path.resolve(__dirname, 'dist'), { recursive: true });
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(__dirname, 'dist/index.ts'),
|
'index.ts',
|
||||||
`export const emojiCodepoints: Record<string, string> = ${JSON.stringify(output, null, 4)};`,
|
`export const emojiCodepoints: Record<string, string> = ${JSON.stringify(output, null, 4)};`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
"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": "./dist/index.ts",
|
"exports": "./index.ts",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"emoji-assets": "^8.0.0"
|
"emoji-assets": "^8.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate": "bun ./build.ts",
|
"postinstall": "bun ./build.ts"
|
||||||
"clean": "rm -rf ./dist"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,4 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"exclude": ["/~gitbook/static/*"]
|
|
||||||
}
|
|
||||||
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
import type { CacheObject } from '@gitbook/cache-do';
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface CloudflareEnv {
|
|
||||||
CACHE?: DurableObjectNamespace<CacheObject>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "gitbook",
|
|
||||||
"version": "0.3.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "env-cmd --silent -f ../../.env.local 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}",
|
|
||||||
"generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math",
|
|
||||||
"copy:icons": "gitbook-icons ./public/~gitbook/static/icons",
|
|
||||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@gitbook/api": "0.64.1",
|
|
||||||
"@gitbook/cache-do": "workspace:*",
|
|
||||||
"@gitbook/emoji-codepoints": "workspace:*",
|
|
||||||
"@gitbook/icons": "workspace:*",
|
|
||||||
"@gitbook/react-contentkit": "workspace:*",
|
|
||||||
"@gitbook/react-math": "workspace:*",
|
|
||||||
"@gitbook/react-openapi": "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.2.5",
|
|
||||||
"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",
|
|
||||||
"postcss-color-contrast": "^1.1.0",
|
|
||||||
"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.13.3",
|
|
||||||
"@cloudflare/workers-types": "^4.20240725.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",
|
|
||||||
"env-cmd": "^10.1.0",
|
|
||||||
"ts-essentials": "^10.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# GitBook immutable static assets
|
|
||||||
/~gitbook/static/*
|
|
||||||
cache-control: public,max-age=31536000,immutable
|
|
||||||
@@ -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 { useScrollPage } 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 a hash or to the top of the page
|
|
||||||
useScrollPage();
|
|
||||||
|
|
||||||
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,137 +0,0 @@
|
|||||||
import { Space } from '@gitbook/api';
|
|
||||||
import { Icon } from '@gitbook/icons';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { SiteContentPointer, getChangeRequest, getRevision } from '@/lib/api';
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
|
|
||||||
import { Toolbar, ToolbarBody, ToolbarButton, ToolbarButtonGroups } from './Toolbar';
|
|
||||||
import { DateRelative } from '../primitives';
|
|
||||||
|
|
||||||
interface AdminToolbarProps {
|
|
||||||
content: SiteContentPointer;
|
|
||||||
space: Space;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toolbar with information for the content admin when previewing a revision or change-request.
|
|
||||||
*/
|
|
||||||
export function AdminToolbar(props: AdminToolbarProps) {
|
|
||||||
const { content } = props;
|
|
||||||
|
|
||||||
const toolbar = (() => {
|
|
||||||
if (content.changeRequestId) {
|
|
||||||
return (
|
|
||||||
<ChangeRequestToolbar
|
|
||||||
spaceId={content.spaceId}
|
|
||||||
changeRequestId={content.changeRequestId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content.revisionId) {
|
|
||||||
return <RevisionToolbar spaceId={content.spaceId} revisionId={content.revisionId} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!toolbar) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={tcls(
|
|
||||||
'fixed',
|
|
||||||
'bottom-5',
|
|
||||||
'left-1/2',
|
|
||||||
'z-50',
|
|
||||||
'transform',
|
|
||||||
'-translate-x-1/2',
|
|
||||||
'rounded-full',
|
|
||||||
'bg-dark-1/9',
|
|
||||||
'shadow-lg',
|
|
||||||
'min-h-10',
|
|
||||||
'min-w-40',
|
|
||||||
'p-2',
|
|
||||||
'max-w-md',
|
|
||||||
'border-dark-1',
|
|
||||||
'backdrop-blur-sm',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<React.Suspense fallback={null}>{toolbar}</React.Suspense>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ChangeRequestToolbar(props: { spaceId: string; changeRequestId: string }) {
|
|
||||||
const { spaceId, changeRequestId } = props;
|
|
||||||
|
|
||||||
const changeRequest = await getChangeRequest(spaceId, changeRequestId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Toolbar>
|
|
||||||
<ToolbarButton title="Open in application" href={changeRequest.urls.app}>
|
|
||||||
<Icon icon="code-branch" className="size-4" />
|
|
||||||
</ToolbarButton>
|
|
||||||
<ToolbarBody>
|
|
||||||
<p>
|
|
||||||
#{changeRequest.number}: {changeRequest.subject ?? 'No subject'}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-light/8 dark:text-light/8">
|
|
||||||
Change request updated <DateRelative value={changeRequest.updatedAt} />
|
|
||||||
</p>
|
|
||||||
</ToolbarBody>
|
|
||||||
<ToolbarButtonGroups>
|
|
||||||
<ToolbarButton title="Open in application" href={changeRequest.urls.app}>
|
|
||||||
<Icon icon="arrow-up-right-from-square" className="size-4" />
|
|
||||||
</ToolbarButton>
|
|
||||||
<RefreshChangeRequestButton
|
|
||||||
spaceId={spaceId}
|
|
||||||
changeRequestId={changeRequestId}
|
|
||||||
revisionId={changeRequest.revision}
|
|
||||||
updatedAt={new Date(changeRequest.updatedAt).getTime()}
|
|
||||||
/>
|
|
||||||
</ToolbarButtonGroups>
|
|
||||||
</Toolbar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function RevisionToolbar(props: { spaceId: string; revisionId: string }) {
|
|
||||||
const { spaceId, revisionId } = props;
|
|
||||||
|
|
||||||
const revision = await getRevision(spaceId, revisionId, {
|
|
||||||
metadata: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Toolbar>
|
|
||||||
<ToolbarButton title="Open in application" href={revision.urls.app}>
|
|
||||||
<Icon icon="code-commit" className="size-4" />
|
|
||||||
</ToolbarButton>
|
|
||||||
<ToolbarBody>
|
|
||||||
<p>
|
|
||||||
Revision created <DateRelative value={revision.createdAt} />
|
|
||||||
</p>
|
|
||||||
{revision.git ? (
|
|
||||||
<p className="text-xs text-light/8 dark:text-light/8">{revision.git.message}</p>
|
|
||||||
) : null}
|
|
||||||
</ToolbarBody>
|
|
||||||
<ToolbarButtonGroups>
|
|
||||||
<ToolbarButton title="Open in application" href={revision.urls.app}>
|
|
||||||
<Icon icon="arrow-up-right-from-square" className="size-4" />
|
|
||||||
</ToolbarButton>
|
|
||||||
{revision.git?.url ? (
|
|
||||||
<ToolbarButton title="Open git commit" href={revision.git.url}>
|
|
||||||
<Icon
|
|
||||||
icon={revision.git.url.includes('github.com') ? 'github' : 'gitlab'}
|
|
||||||
className="size-4"
|
|
||||||
/>
|
|
||||||
</ToolbarButton>
|
|
||||||
) : null}
|
|
||||||
</ToolbarButtonGroups>
|
|
||||||
</Toolbar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { Icon } from '@gitbook/icons';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { useCheckForContentUpdate } from '@/components/AutoRefreshContent';
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
import { ToolbarButton } from './Toolbar';
|
|
||||||
|
|
||||||
// We don't show the button if the content has been updated 30s ago or less.
|
|
||||||
const minInterval = 1000 * 30; // 5 minutes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button to refresh the page if the content has been updated.
|
|
||||||
*/
|
|
||||||
export function RefreshChangeRequestButton(props: {
|
|
||||||
spaceId: string;
|
|
||||||
changeRequestId: string;
|
|
||||||
revisionId: string;
|
|
||||||
updatedAt: number;
|
|
||||||
}) {
|
|
||||||
const { updatedAt } = props;
|
|
||||||
|
|
||||||
const [visible, setVisible] = React.useState(false);
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
const checkForUpdates = useCheckForContentUpdate(props);
|
|
||||||
|
|
||||||
const refresh = React.useCallback(async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
await checkForUpdates();
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
setVisible(false);
|
|
||||||
}
|
|
||||||
}, [checkForUpdates]);
|
|
||||||
|
|
||||||
// Show the button if the content has been updated more than 30s ago.
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (updatedAt < Date.now() - minInterval) {
|
|
||||||
setVisible(true);
|
|
||||||
}
|
|
||||||
}, [updatedAt]);
|
|
||||||
|
|
||||||
// 30sec after being hidden, we show the button again
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!visible) {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setVisible(true);
|
|
||||||
}, minInterval);
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
if (!visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolbarButton
|
|
||||||
title="Refresh"
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
refresh();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="rotate" className={tcls('size-4', loading ? 'animate-spin' : null)} />
|
|
||||||
</ToolbarButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
export function Toolbar(props: { children: React.ReactNode }) {
|
|
||||||
const { children } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={tcls(
|
|
||||||
'flex',
|
|
||||||
'flex-row',
|
|
||||||
'items-center',
|
|
||||||
'gap-4',
|
|
||||||
'text-sm',
|
|
||||||
'px-4',
|
|
||||||
'py-1',
|
|
||||||
'rounded-full',
|
|
||||||
'truncate',
|
|
||||||
'text-light',
|
|
||||||
'dark:text-light',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ToolbarBody(props: { children: React.ReactNode }) {
|
|
||||||
return <div className="flex flex-col gap-1">{props.children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ToolbarButtonGroups(props: { children: React.ReactNode }) {
|
|
||||||
return <div className="flex flex-row gap-2">{props.children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ToolbarButton(props: React.HTMLProps<HTMLAnchorElement>) {
|
|
||||||
const { children, ...rest } = props;
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
{...rest}
|
|
||||||
className={tcls(
|
|
||||||
'flex',
|
|
||||||
'flex-col',
|
|
||||||
'items-center',
|
|
||||||
'justify-center',
|
|
||||||
'size-11',
|
|
||||||
'gap-1',
|
|
||||||
'text-sm',
|
|
||||||
'rounded-full',
|
|
||||||
'hover:bg-dark-1',
|
|
||||||
'hover:text-white',
|
|
||||||
'truncate',
|
|
||||||
'text-light',
|
|
||||||
'dark:text-light',
|
|
||||||
'dark:hover:bg-dark-2',
|
|
||||||
'hover:shadow-lg',
|
|
||||||
'cursor-pointer',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user