├── .changeset ├── README.md ├── big-gorillas-perform.md ├── breezy-falcons-drop.md ├── cold-buckets-divide.md ├── config.json ├── cool-jars-matter.md ├── cool-seas-approve.md ├── fair-crews-wink.md ├── fast-trees-battle.md ├── gorgeous-cycles-cheat.md ├── nasty-moles-visit.md ├── nervous-students-judge.md ├── rotten-seals-rush.md ├── sharp-hats-applaud.md ├── slow-lizards-obey.md ├── strong-poets-move.md ├── stupid-plums-perform.md ├── thick-chefs-repeat.md ├── thin-buckets-grow.md ├── violet-schools-care.md └── wise-gifts-smash.md ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ └── bug_report.md ├── actions │ ├── gradual-deploy-cloudflare │ │ └── action.yaml │ └── setup-playwright │ │ └── action.yml ├── composite │ ├── deploy-cloudflare │ │ └── action.yaml │ ├── deploy-vercel │ │ └── action.yaml │ └── setup-bun │ │ └── action.yaml └── workflows │ ├── ci.yaml │ ├── deploy-preview.yaml │ ├── deploy-production.yaml │ ├── deploy-staging.yaml │ └── publish.yaml ├── .gitignore ├── .nvmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── assets └── published-site.png ├── biome.json ├── bun.lock ├── bunfig.toml ├── package.json ├── packages ├── cache-do │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── CacheObject.ts │ │ ├── CacheObjectStub.ts │ │ ├── api.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── turbo.json │ └── wrangler.toml ├── cache-tags │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── colors │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── colors.ts │ │ ├── index.ts │ │ └── transformations.ts │ └── tsconfig.json ├── emoji-codepoints │ ├── .gitignore │ ├── CHANGELOG.md │ ├── build.ts │ └── package.json ├── gitbook-v2 │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── open-next.config.ts │ ├── openNext │ │ ├── customWorkers │ │ │ ├── default.js │ │ │ ├── defaultWrangler.jsonc │ │ │ ├── do.js │ │ │ ├── doWrangler.jsonc │ │ │ ├── middleware.js │ │ │ ├── middlewareWrangler.jsonc │ │ │ └── script │ │ │ │ └── updateWrangler.ts │ │ ├── incrementalCache.ts │ │ ├── queue │ │ │ ├── middleware.ts │ │ │ └── server.ts │ │ └── tagCache │ │ │ └── middleware.ts │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── app │ │ │ ├── sites │ │ │ │ ├── dynamic │ │ │ │ │ └── [mode] │ │ │ │ │ │ └── [siteURL] │ │ │ │ │ │ └── [siteData] │ │ │ │ │ │ ├── (content) │ │ │ │ │ │ ├── [pagePath] │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ │ └── ~gitbook │ │ │ │ │ │ ├── icon │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ ├── ogimage │ │ │ │ │ │ └── [pageId] │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── pdf │ │ │ │ │ │ ├── layout.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ └── static │ │ │ │ │ └── [mode] │ │ │ │ │ └── [siteURL] │ │ │ │ │ └── [siteData] │ │ │ │ │ ├── [pagePath] │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── llms.txt │ │ │ │ │ └── route.ts │ │ │ │ │ ├── robots.txt │ │ │ │ │ └── route.ts │ │ │ │ │ ├── sitemap-pages.xml │ │ │ │ │ └── route.ts │ │ │ │ │ ├── sitemap.xml │ │ │ │ │ └── route.ts │ │ │ │ │ └── ~gitbook │ │ │ │ │ ├── icon │ │ │ │ │ └── route.ts │ │ │ │ │ ├── markdown │ │ │ │ │ └── [pagePath] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── ogimage │ │ │ │ │ └── [pageId] │ │ │ │ │ └── route.ts │ │ │ ├── utils.ts │ │ │ ├── ~gitbook │ │ │ │ ├── env │ │ │ │ │ └── route.ts │ │ │ │ └── revalidate │ │ │ │ │ └── route.ts │ │ │ └── ~space │ │ │ │ └── [spaceId] │ │ │ │ ├── pdf.ts │ │ │ │ ├── ~ │ │ │ │ ├── changes │ │ │ │ │ └── [changeRequestId] │ │ │ │ │ │ └── ~gitbook │ │ │ │ │ │ └── pdf │ │ │ │ │ │ ├── layout.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ └── revisions │ │ │ │ │ └── [changeRequestId] │ │ │ │ │ └── ~gitbook │ │ │ │ │ └── pdf │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── ~gitbook │ │ │ │ └── pdf │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── lib │ │ │ ├── context.ts │ │ │ ├── data │ │ │ │ ├── api.ts │ │ │ │ ├── cloudflare.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lookup.ts │ │ │ │ ├── pages.ts │ │ │ │ ├── types.ts │ │ │ │ ├── urls.test.ts │ │ │ │ ├── urls.ts │ │ │ │ ├── visitor.test.ts │ │ │ │ └── visitor.ts │ │ │ ├── env │ │ │ │ ├── globals.ts │ │ │ │ ├── index.ts │ │ │ │ └── urls.ts │ │ │ ├── images │ │ │ │ ├── checkIsSizableImageURL.test.ts │ │ │ │ ├── checkIsSizableImageURL.ts │ │ │ │ ├── createImageResizer.ts │ │ │ │ ├── getImageResizingContextId.test.ts │ │ │ │ ├── getImageResizingContextId.ts │ │ │ │ ├── index.ts │ │ │ │ ├── resizer │ │ │ │ │ ├── cdn-cgi.ts │ │ │ │ │ ├── cf-fetch.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── resizeImage.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── signatures.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── links.test.ts │ │ │ ├── links.ts │ │ │ ├── middleware.ts │ │ │ ├── preview.test.ts │ │ │ ├── preview.ts │ │ │ ├── proxy.test.ts │ │ │ ├── proxy.ts │ │ │ ├── routes.ts │ │ │ └── server-actions.ts │ │ ├── middleware.ts │ │ └── pages │ │ │ └── api │ │ │ └── ~gitbook │ │ │ └── force-revalidate.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── turbo.json │ └── wrangler.jsonc ├── gitbook │ ├── .env.example │ ├── .gitignore │ ├── CHANGELOG.md │ ├── _routes.json │ ├── cf-env.d.ts │ ├── e2e │ │ ├── customers.spec.ts │ │ ├── internal.spec.ts │ │ └── util.ts │ ├── next.config.js │ ├── package.json │ ├── playwright.config.ts │ ├── postcss.config.js │ ├── public │ │ ├── _headers │ │ └── ~gitbook │ │ │ └── static │ │ │ └── images │ │ │ ├── ogimage-grid-black.png │ │ │ └── ogimage-grid-white.png │ ├── src │ │ ├── app │ │ │ ├── (global) │ │ │ │ └── ~gitbook │ │ │ │ │ ├── image │ │ │ │ │ └── route.ts │ │ │ │ │ └── revalidate │ │ │ │ │ └── route.ts │ │ │ ├── global-error.tsx │ │ │ ├── middleware │ │ │ │ ├── (site) │ │ │ │ │ ├── (content) │ │ │ │ │ │ ├── [[...pathname]] │ │ │ │ │ │ │ ├── PageClientLayout.tsx │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ ├── (core) │ │ │ │ │ │ ├── llms.txt │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ ├── robots.txt │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ ├── sitemap-pages.xml │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ ├── sitemap.xml │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── ~gitbook │ │ │ │ │ │ │ ├── icon │ │ │ │ │ │ │ └── route.tsx │ │ │ │ │ │ │ └── ogimage │ │ │ │ │ │ │ └── [pageId] │ │ │ │ │ │ │ └── route.tsx │ │ │ │ │ ├── error.tsx │ │ │ │ │ └── layout.tsx │ │ │ │ └── (space) │ │ │ │ │ └── ~gitbook │ │ │ │ │ └── pdf │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── pointer.ts │ │ │ └── ~scalar │ │ │ │ └── proxy │ │ │ │ └── route.ts │ │ ├── cloudflare-entrypoint.ts │ │ ├── components │ │ │ ├── Adaptive │ │ │ │ ├── AIPageLinkSummary.tsx │ │ │ │ ├── index.ts │ │ │ │ └── server-actions │ │ │ │ │ ├── api.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── streamLinkPageSummary.ts │ │ │ ├── AdminToolbar │ │ │ │ ├── AdminToolbar.tsx │ │ │ │ ├── RefreshChangeRequestButton.tsx │ │ │ │ ├── Toolbar.tsx │ │ │ │ └── index.ts │ │ │ ├── Ads │ │ │ │ ├── Ad.tsx │ │ │ │ ├── AdClassicRendering.tsx │ │ │ │ ├── AdCoverRendering.tsx │ │ │ │ ├── AdPixels.tsx │ │ │ │ ├── assets │ │ │ │ │ └── ad-rainbow.svg │ │ │ │ ├── index.ts │ │ │ │ ├── renderAd.tsx │ │ │ │ └── types.ts │ │ │ ├── Announcement │ │ │ │ ├── Announcement.tsx │ │ │ │ ├── AnnouncementBanner.tsx │ │ │ │ ├── AnnouncementDismissedScript.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ └── script.ts │ │ │ ├── AutoRefreshContent │ │ │ │ ├── index.ts │ │ │ │ ├── server-actions.ts │ │ │ │ └── useCheckForContentUpdate.ts │ │ │ ├── Cookies │ │ │ │ ├── CookiesToast.tsx │ │ │ │ └── index.ts │ │ │ ├── DocumentView │ │ │ │ ├── Annotation │ │ │ │ │ ├── Annotation.tsx │ │ │ │ │ ├── AnnotationPopover.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Block.tsx │ │ │ │ ├── BlockContentRef.tsx │ │ │ │ ├── Blocks.tsx │ │ │ │ ├── Caption.tsx │ │ │ │ ├── CodeBlock │ │ │ │ │ ├── ClientCodeBlock.tsx │ │ │ │ │ ├── CodeBlock.tsx │ │ │ │ │ ├── CodeBlockRenderer.css │ │ │ │ │ ├── CodeBlockRenderer.tsx │ │ │ │ │ ├── CopyCodeButton.tsx │ │ │ │ │ ├── PlainCodeBlock.tsx │ │ │ │ │ ├── highlight.test.ts │ │ │ │ │ ├── highlight.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── plain-highlight.ts │ │ │ │ │ └── theme.css │ │ │ │ ├── Divider.tsx │ │ │ │ ├── DocumentView.tsx │ │ │ │ ├── Drawing.tsx │ │ │ │ ├── Embed.tsx │ │ │ │ ├── Emoji.tsx │ │ │ │ ├── Expandable │ │ │ │ │ ├── Details.tsx │ │ │ │ │ ├── Expandable.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── File.tsx │ │ │ │ ├── FileIcon.tsx │ │ │ │ ├── HashLinkButton.tsx │ │ │ │ ├── Heading.tsx │ │ │ │ ├── Hint.tsx │ │ │ │ ├── Images.tsx │ │ │ │ ├── Inline.tsx │ │ │ │ ├── InlineButton.tsx │ │ │ │ ├── InlineImage.tsx │ │ │ │ ├── InlineLink.tsx │ │ │ │ ├── InlineLinkTooltip.tsx │ │ │ │ ├── Inlines.tsx │ │ │ │ ├── Integration │ │ │ │ │ ├── IntegrationBlock.tsx │ │ │ │ │ ├── contentkit.css │ │ │ │ │ ├── contentkit.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── server-actions.tsx │ │ │ │ ├── List.tsx │ │ │ │ ├── ListItem.tsx │ │ │ │ ├── Math.tsx │ │ │ │ ├── Mention.tsx │ │ │ │ ├── OpenAPI │ │ │ │ │ ├── OpenAPIOperation.tsx │ │ │ │ │ ├── OpenAPISchemas.tsx │ │ │ │ │ ├── OpenAPIWebhook.tsx │ │ │ │ │ ├── context.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── scalar.css │ │ │ │ │ └── style.css │ │ │ │ ├── Paragraph.tsx │ │ │ │ ├── Quote.tsx │ │ │ │ ├── ReusableContent.tsx │ │ │ │ ├── Stepper.tsx │ │ │ │ ├── StepperStep.tsx │ │ │ │ ├── Table │ │ │ │ │ ├── RecordCard.tsx │ │ │ │ │ ├── RecordColumnValue.tsx │ │ │ │ │ ├── RecordRow.tsx │ │ │ │ │ ├── Table.tsx │ │ │ │ │ ├── ViewCards.tsx │ │ │ │ │ ├── ViewGrid.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── table.module.css │ │ │ │ │ └── utils.ts │ │ │ │ ├── Tabs │ │ │ │ │ ├── DynamicTabs.tsx │ │ │ │ │ ├── Tabs.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Text.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── spacing.ts │ │ │ │ └── utils │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── isBlockOffscreen.test.ts │ │ │ │ │ └── isBlockOffscreen.ts │ │ │ ├── Footer │ │ │ │ ├── Footer.tsx │ │ │ │ ├── FooterLinksGroup.tsx │ │ │ │ └── index.ts │ │ │ ├── Header │ │ │ │ ├── CurrentContentIcon.tsx │ │ │ │ ├── DropdownMenu.tsx │ │ │ │ ├── Header.tsx │ │ │ │ ├── HeaderLink.tsx │ │ │ │ ├── HeaderLinkMore.tsx │ │ │ │ ├── HeaderLinks.tsx │ │ │ │ ├── HeaderLogo.tsx │ │ │ │ ├── HeaderMobileMenu.tsx │ │ │ │ ├── SpacesDropdown.tsx │ │ │ │ ├── SpacesDropdownMenuItem.tsx │ │ │ │ ├── headerLinks.module.css │ │ │ │ └── index.ts │ │ │ ├── Insights │ │ │ │ ├── InsightsProvider.tsx │ │ │ │ ├── TrackPageViewEvent.tsx │ │ │ │ ├── cookies.ts │ │ │ │ ├── index.ts │ │ │ │ ├── sessions.ts │ │ │ │ ├── useVisitedPages.tsx │ │ │ │ ├── utils.ts │ │ │ │ └── visitorId.ts │ │ │ ├── Integrations │ │ │ │ ├── LoadIntegrations.tsx │ │ │ │ └── index.ts │ │ │ ├── PDF │ │ │ │ ├── PDFPage.tsx │ │ │ │ ├── PDFRootLayout.tsx │ │ │ │ ├── PageControlButtons.tsx │ │ │ │ ├── PrintButton.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── pdf.css │ │ │ │ └── urls.ts │ │ │ ├── PageAside │ │ │ │ ├── AsideSectionHighlight.tsx │ │ │ │ ├── PageAside.tsx │ │ │ │ ├── ScrollSectionsList.tsx │ │ │ │ └── index.ts │ │ │ ├── PageBody │ │ │ │ ├── PageBody.tsx │ │ │ │ ├── PageBodyBlankslate.tsx │ │ │ │ ├── PageCover.tsx │ │ │ │ ├── PageFooterNavigation.tsx │ │ │ │ ├── PageHeader.tsx │ │ │ │ ├── PreservePageLayout.tsx │ │ │ │ ├── default-page-cover.svg │ │ │ │ └── index.ts │ │ │ ├── PageContext │ │ │ │ ├── PageContext.tsx │ │ │ │ └── index.ts │ │ │ ├── PageFeedback │ │ │ │ ├── PageFeedbackForm.tsx │ │ │ │ └── index.ts │ │ │ ├── PageIcon │ │ │ │ ├── PageIcon.tsx │ │ │ │ └── index.ts │ │ │ ├── RootLayout │ │ │ │ ├── ClientContexts.tsx │ │ │ │ ├── CustomizationRootLayout.tsx │ │ │ │ ├── globals.css │ │ │ │ └── index.ts │ │ │ ├── Search │ │ │ │ ├── HighlightQuery.tsx │ │ │ │ ├── SearchAskAnswer.tsx │ │ │ │ ├── SearchAskContext.tsx │ │ │ │ ├── SearchButton.tsx │ │ │ │ ├── SearchModal.tsx │ │ │ │ ├── SearchPageResultItem.tsx │ │ │ │ ├── SearchQuestionResultItem.tsx │ │ │ │ ├── SearchResults.tsx │ │ │ │ ├── SearchScopeToggle.tsx │ │ │ │ ├── SearchSectionResultItem.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── isQuestion.ts │ │ │ │ ├── server-actions.tsx │ │ │ │ └── useSearch.ts │ │ │ ├── SiteLayout │ │ │ │ ├── ClientContexts.tsx │ │ │ │ ├── RocketLoaderDetector.tsx │ │ │ │ ├── SiteLayout.tsx │ │ │ │ └── index.ts │ │ │ ├── SitePage │ │ │ │ ├── PageClientLayout.tsx │ │ │ │ ├── SitePage.tsx │ │ │ │ ├── SitePageNotFound.tsx │ │ │ │ ├── SitePageSkeleton.tsx │ │ │ │ ├── fetch.ts │ │ │ │ └── index.ts │ │ │ ├── SiteSections │ │ │ │ ├── SectionIcon.tsx │ │ │ │ ├── SiteSectionList.tsx │ │ │ │ ├── SiteSectionTabs.tsx │ │ │ │ ├── encodeClientSiteSections.ts │ │ │ │ └── index.ts │ │ │ ├── SpaceLayout │ │ │ │ ├── SpaceLayout.tsx │ │ │ │ ├── SpaceLayoutContext.tsx │ │ │ │ └── index.ts │ │ │ ├── TableOfContents │ │ │ │ ├── PageDocumentItem.tsx │ │ │ │ ├── PageGroupItem.tsx │ │ │ │ ├── PageLinkItem.tsx │ │ │ │ ├── PagesList.tsx │ │ │ │ ├── TOCPageIcon.tsx │ │ │ │ ├── TOCScroller.tsx │ │ │ │ ├── TableOfContents.tsx │ │ │ │ ├── TableOfContentsScript.tsx │ │ │ │ ├── ToggleableLinkItem.tsx │ │ │ │ ├── Trademark.tsx │ │ │ │ └── index.ts │ │ │ ├── ThemeToggler │ │ │ │ ├── ThemeToggler.tsx │ │ │ │ └── index.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCurrentPagePath.ts │ │ │ │ ├── useHasBeenInViewport.ts │ │ │ │ ├── useHash.ts │ │ │ │ ├── useInViewportListener.ts │ │ │ │ ├── useIsMounted.ts │ │ │ │ ├── useScrollActiveId.ts │ │ │ │ ├── useScrollListener.ts │ │ │ │ ├── useScrollPage.ts │ │ │ │ └── useToggleAnimation.ts │ │ │ ├── layout.ts │ │ │ ├── primitives │ │ │ │ ├── Button.tsx │ │ │ │ ├── Card.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ ├── DateRelative.tsx │ │ │ │ ├── Emoji │ │ │ │ │ ├── Emoji.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Link.tsx │ │ │ │ ├── Loading.tsx │ │ │ │ ├── LoadingPane.tsx │ │ │ │ ├── Skeleton.tsx │ │ │ │ ├── StyledLink.tsx │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ │ ├── Image.tsx │ │ │ │ ├── ZoomImage.module.css │ │ │ │ ├── ZoomImage.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ ├── fonts │ │ │ ├── ABCFavorit │ │ │ │ ├── ABCFavorit-Bold.otf │ │ │ │ ├── ABCFavorit-Bold.woff │ │ │ │ ├── ABCFavorit-Bold.woff2 │ │ │ │ ├── ABCFavorit-BoldItalic.otf │ │ │ │ ├── ABCFavorit-BoldItalic.woff │ │ │ │ ├── ABCFavorit-BoldItalic.woff2 │ │ │ │ ├── ABCFavorit-Book.otf │ │ │ │ ├── ABCFavorit-Book.woff │ │ │ │ ├── ABCFavorit-Book.woff2 │ │ │ │ ├── ABCFavorit-BookItalic.otf │ │ │ │ ├── ABCFavorit-BookItalic.woff │ │ │ │ ├── ABCFavorit-BookItalic.woff2 │ │ │ │ ├── ABCFavorit-Light.otf │ │ │ │ ├── ABCFavorit-Light.woff │ │ │ │ ├── ABCFavorit-Light.woff2 │ │ │ │ ├── ABCFavorit-LightItalic.otf │ │ │ │ ├── ABCFavorit-LightItalic.woff │ │ │ │ ├── ABCFavorit-LightItalic.woff2 │ │ │ │ ├── ABCFavorit-Medium.otf │ │ │ │ ├── ABCFavorit-Medium.woff │ │ │ │ ├── ABCFavorit-Medium.woff2 │ │ │ │ ├── ABCFavorit-MediumItalic.otf │ │ │ │ ├── ABCFavorit-MediumItalic.woff │ │ │ │ ├── ABCFavorit-MediumItalic.woff2 │ │ │ │ ├── ABCFavorit-Regular.otf │ │ │ │ ├── ABCFavorit-Regular.woff │ │ │ │ ├── ABCFavorit-Regular.woff2 │ │ │ │ ├── ABCFavorit-RegularItalic.otf │ │ │ │ ├── ABCFavorit-RegularItalic.woff │ │ │ │ ├── ABCFavorit-RegularItalic.woff2 │ │ │ │ ├── ABCFavorit-Variable.ttf │ │ │ │ ├── ABCFavorit-Variable.woff │ │ │ │ └── ABCFavorit-Variable.woff2 │ │ │ ├── Inter │ │ │ │ ├── Inter-Bold.ttf │ │ │ │ └── Inter-Regular.ttf │ │ │ ├── custom.test.ts │ │ │ ├── custom.ts │ │ │ ├── default.ts │ │ │ └── index.ts │ │ ├── intl │ │ │ ├── client.ts │ │ │ ├── server.ts │ │ │ ├── translate.tsx │ │ │ └── translations │ │ │ │ ├── de.ts │ │ │ │ ├── en.ts │ │ │ │ ├── es.ts │ │ │ │ ├── fr.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ja.ts │ │ │ │ ├── nl.ts │ │ │ │ ├── no.ts │ │ │ │ ├── pt-br.ts │ │ │ │ ├── types.ts │ │ │ │ └── zh.ts │ │ ├── lib │ │ │ ├── __snapshots__ │ │ │ │ └── markdown.test.ts.snap │ │ │ ├── adaptive.ts │ │ │ ├── api.ts │ │ │ ├── app.ts │ │ │ ├── arrays.ts │ │ │ ├── assets.ts │ │ │ ├── async.test.ts │ │ │ ├── async.ts │ │ │ ├── browser-cookies.ts │ │ │ ├── build.ts │ │ │ ├── cache │ │ │ │ ├── backends.ts │ │ │ │ ├── cache.test.ts │ │ │ │ ├── cache.ts │ │ │ │ ├── cloudflare-cache.ts │ │ │ │ ├── cloudflare-do.ts │ │ │ │ ├── cloudflare-kv.ts │ │ │ │ ├── http.ts │ │ │ │ ├── index.ts │ │ │ │ ├── memory.ts │ │ │ │ ├── response.ts │ │ │ │ ├── revalidateTags.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── csp.ts │ │ │ ├── customization.ts │ │ │ ├── document-sections.ts │ │ │ ├── document.test.ts │ │ │ ├── document.ts │ │ │ ├── emojis.test.ts │ │ │ ├── emojis.ts │ │ │ ├── files.ts │ │ │ ├── local-storage.ts │ │ │ ├── markdown.test.ts │ │ │ ├── markdown.ts │ │ │ ├── middleware.ts │ │ │ ├── openapi │ │ │ │ ├── enrich.test.ts │ │ │ │ ├── enrich.ts │ │ │ │ ├── fetch.ts │ │ │ │ ├── fixtures │ │ │ │ │ └── multiline-spec.yaml │ │ │ │ ├── resolveOpenAPIOperationBlock.ts │ │ │ │ ├── resolveOpenAPISchemasBlock.ts │ │ │ │ ├── resolveOpenAPIWebhookBlock.ts │ │ │ │ └── types.ts │ │ │ ├── pages.test.ts │ │ │ ├── pages.ts │ │ │ ├── paths.test.ts │ │ │ ├── paths.ts │ │ │ ├── pointer.ts │ │ │ ├── proxy.test.ts │ │ │ ├── proxy.ts │ │ │ ├── references.tsx │ │ │ ├── security-error.ts │ │ │ ├── seo.ts │ │ │ ├── sitemap.ts │ │ │ ├── sites.ts │ │ │ ├── tailwind.ts │ │ │ ├── tracing.ts │ │ │ ├── tracking.ts │ │ │ ├── typescript.ts │ │ │ ├── urls.ts │ │ │ ├── utils.ts │ │ │ ├── v1.ts │ │ │ ├── v2.ts │ │ │ ├── visitors.test.ts │ │ │ ├── visitors.ts │ │ │ └── waitUntil.ts │ │ ├── middleware.ts │ │ └── routes │ │ │ ├── icon.tsx │ │ │ ├── image.ts │ │ │ ├── llms.ts │ │ │ ├── markdownPage.ts │ │ │ ├── ogimage.tsx │ │ │ ├── robots.ts │ │ │ └── sitemap.ts │ ├── tailwind.config.ts │ ├── tests │ │ ├── pagespeed-testing.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── turbo.json │ └── types │ │ ├── content-security-policy-merger.d.ts │ │ ├── gitbook-integrations-global.d.ts │ │ ├── global.d.ts │ │ ├── images.d.ts │ │ └── memoizee.d.ts ├── icons │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ ├── gen-list.js │ │ ├── gitbook-icons.js │ │ └── kit.js │ ├── package.json │ ├── src │ │ ├── Icon.tsx │ │ ├── IconsProvider.tsx │ │ ├── getIconStyle.ts │ │ ├── icons.ts │ │ ├── index.ts │ │ ├── pro.test.ts │ │ ├── style.css │ │ └── types.ts │ ├── tsconfig.json │ └── turbo.json ├── openapi-parser │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── error.ts │ │ ├── filesystem.test.ts │ │ ├── filesystem.ts │ │ ├── fixtures │ │ │ ├── recursive-spec.json │ │ │ ├── remote-ref │ │ │ │ ├── root │ │ │ │ │ ├── invalid.txt │ │ │ │ │ ├── pet.yaml │ │ │ │ │ ├── spec.yaml │ │ │ │ │ └── user.yaml │ │ │ │ └── tag.yaml │ │ │ └── spec-v2.json │ │ ├── helpers │ │ │ └── shouldIgnoreEntity.ts │ │ ├── index.ts │ │ ├── parse.test.ts │ │ ├── parse.ts │ │ ├── scalar-plugins │ │ │ └── fetchURLs.ts │ │ ├── schemas.ts │ │ ├── traverse.test.ts │ │ ├── traverse.ts │ │ ├── types.ts │ │ ├── v2.test.ts │ │ ├── v2.ts │ │ └── v3.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── react-contentkit │ ├── .gitignore │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── ContentKit.tsx │ │ ├── ContentKitOutput.tsx │ │ ├── Element.tsx │ │ ├── ElementBlock.tsx │ │ ├── ElementBox.tsx │ │ ├── ElementButton.tsx │ │ ├── ElementCard.tsx │ │ ├── ElementCodeBlock.tsx │ │ ├── ElementCodeBlockClient.tsx │ │ ├── ElementDivider.tsx │ │ ├── ElementIcon.tsx │ │ ├── ElementImage.tsx │ │ ├── ElementInput.tsx │ │ ├── ElementMarkdown.tsx │ │ ├── ElementMarkdownClient.tsx │ │ ├── ElementModal.tsx │ │ ├── ElementStack.tsx │ │ ├── ElementText.tsx │ │ ├── ElementTextInput.tsx │ │ ├── ElementWebframe.tsx │ │ ├── context.ts │ │ ├── dynamic.ts │ │ ├── index.ts │ │ └── types.ts │ └── tsconfig.json ├── react-math │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── gitbook-math.js │ ├── css │ │ └── default.css │ ├── package.json │ ├── src │ │ ├── KaTeX.tsx │ │ ├── KaTeXCSS.tsx │ │ ├── MathFormula.tsx │ │ ├── MathJaX.tsx │ │ ├── MathJaXLazy.tsx │ │ └── index.ts │ └── tsconfig.json └── react-openapi │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── InteractiveSection.tsx │ ├── Markdown.tsx │ ├── OpenAPICodeSample.tsx │ ├── OpenAPICodeSampleInteractive.tsx │ ├── OpenAPICodeSampleSelector.tsx │ ├── OpenAPICopyButton.tsx │ ├── OpenAPIDisclosure.tsx │ ├── OpenAPIDisclosureGroup.tsx │ ├── OpenAPIExample.tsx │ ├── OpenAPIMediaType.tsx │ ├── OpenAPIOperation.tsx │ ├── OpenAPIOperationContext.tsx │ ├── OpenAPIOperationDescription.tsx │ ├── OpenAPIOperationStability.tsx │ ├── OpenAPIPath.tsx │ ├── OpenAPIRequestBody.tsx │ ├── OpenAPIResponse.tsx │ ├── OpenAPIResponseExample.tsx │ ├── OpenAPIResponseExampleContent.tsx │ ├── OpenAPIResponses.tsx │ ├── OpenAPISchema.test.ts │ ├── OpenAPISchema.tsx │ ├── OpenAPISchemaName.tsx │ ├── OpenAPISchemaServer.tsx │ ├── OpenAPISecurities.tsx │ ├── OpenAPISelect.tsx │ ├── OpenAPISpec.tsx │ ├── OpenAPITabs.tsx │ ├── OpenAPIWebhook.tsx │ ├── OpenAPIWebhookExample.tsx │ ├── ScalarApiButton.tsx │ ├── StaticSection.tsx │ ├── __snapshots__ │ │ └── json2xml.test.ts.snap │ ├── code-samples.test.ts │ ├── code-samples.ts │ ├── common │ │ ├── OpenAPIColumnSpec.tsx │ │ ├── OpenAPIOperationDescription.tsx │ │ ├── OpenAPIStability.tsx │ │ └── OpenAPISummary.tsx │ ├── contentTypeChecks.ts │ ├── context.ts │ ├── decycle.ts │ ├── dereference.ts │ ├── generateSchemaExample.test.ts │ ├── generateSchemaExample.ts │ ├── getDisclosureLabel.ts │ ├── getOrCreateStoreByKey.ts │ ├── index.ts │ ├── json2xml.test.ts │ ├── json2xml.ts │ ├── resolveOpenAPIOperation.test.ts │ ├── resolveOpenAPIOperation.ts │ ├── resolveOpenAPIWebhook.ts │ ├── schemas │ │ ├── OpenAPISchemaItem.tsx │ │ ├── OpenAPISchemas.tsx │ │ ├── index.ts │ │ ├── resolveOpenAPISchemas.test.ts │ │ └── resolveOpenAPISchemas.ts │ ├── stringifyOpenAPI.ts │ ├── translate.tsx │ ├── translations │ │ ├── de.ts │ │ ├── en.ts │ │ ├── es.ts │ │ ├── fr.ts │ │ ├── index.ts │ │ ├── ja.ts │ │ ├── nl.ts │ │ ├── no.ts │ │ ├── pt-br.ts │ │ ├── types.ts │ │ └── zh.ts │ ├── types.ts │ ├── util │ │ ├── example.tsx │ │ ├── server.test.ts │ │ └── server.ts │ └── utils.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── patches ├── @vercel%2Fnext@4.4.2.patch └── decode-named-character-reference@1.0.2.patch └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/big-gorillas-perform.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Fix three small visual issues 6 | 7 | - Fix sidebar showing on `no-toc` pages in the gradient theme 8 | - Fix variant selector truncating incorrectly in header when sections are present 9 | - Fix page cover alignment on `lg` screens without TOC 10 | -------------------------------------------------------------------------------- /.changeset/breezy-falcons-drop.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Respect fullWidth and defaultWidth for images 6 | -------------------------------------------------------------------------------- /.changeset/cold-buckets-divide.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | fix nested a tag causing hydration error 6 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/cool-jars-matter.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | fix href being empty in TOC 6 | -------------------------------------------------------------------------------- /.changeset/cool-seas-approve.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Fix navigation between sections/variants when previewing a site in v2 6 | -------------------------------------------------------------------------------- /.changeset/fair-crews-wink.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": minor 3 | --- 4 | 5 | Add circular corners and depth styling 6 | -------------------------------------------------------------------------------- /.changeset/fast-trees-battle.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@gitbook/react-openapi': patch 3 | --- 4 | 5 | Add authorization header for OAuth2 6 | -------------------------------------------------------------------------------- /.changeset/gorgeous-cycles-cheat.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook-v2": patch 3 | --- 4 | 5 | add a force-revalidate api route to force bust the cache in case of errors 6 | -------------------------------------------------------------------------------- /.changeset/nasty-moles-visit.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook-v2": patch 3 | --- 4 | 5 | fix ISR on preview env 6 | -------------------------------------------------------------------------------- /.changeset/nervous-students-judge.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook-v2": patch 3 | "gitbook": patch 4 | --- 5 | 6 | Fix concurrent execution in Vercel causing pages to not be attached to the proper tags. 7 | -------------------------------------------------------------------------------- /.changeset/rotten-seals-rush.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@gitbook/react-openapi': patch 3 | --- 4 | 5 | Indent JSON python code sample 6 | -------------------------------------------------------------------------------- /.changeset/sharp-hats-applaud.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Fix missing title on button to close the announcement banner. 6 | -------------------------------------------------------------------------------- /.changeset/slow-lizards-obey.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Make TOC height dynamic based on visible header and footer elements 6 | -------------------------------------------------------------------------------- /.changeset/strong-poets-move.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | --- 4 | 5 | Fix bold header links hover color 6 | -------------------------------------------------------------------------------- /.changeset/stupid-plums-perform.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook": patch 3 | "gitbook-v2": patch 4 | --- 5 | 6 | cache fonts and static image used in OGImage in memory 7 | -------------------------------------------------------------------------------- /.changeset/thick-chefs-repeat.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@gitbook/react-openapi': patch 3 | --- 4 | 5 | Handle nested deprecated properties in generateSchemaExample 6 | -------------------------------------------------------------------------------- /.changeset/thin-buckets-grow.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook-v2": patch 3 | --- 4 | 5 | Add `urlObject.hash` to `linker.toLinkForContent` to pass through URL fragment identifiers, used in search 6 | -------------------------------------------------------------------------------- /.changeset/violet-schools-care.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@gitbook/react-openapi': patch 3 | --- 4 | 5 | Deduplicate path parameters from OpenAPI spec 6 | -------------------------------------------------------------------------------- /.changeset/wise-gifts-smash.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gitbook-v2": patch 3 | --- 4 | 5 | remove trailing slash from linker 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something not working as expected? Let us look into it 4 | labels: bug 5 | --- 6 | 7 | ## Bug description 8 | 9 | _Please describe._ 10 | _If this affects the front-end, screenshots would be of great help._ 11 | 12 | ## How to reproduce 13 | 14 | 1. 15 | 2. 16 | 3. 17 | 18 | ## Additional context 19 | -------------------------------------------------------------------------------- /.github/composite/setup-bun/action.yaml: -------------------------------------------------------------------------------- 1 | name: 'Setup Bun' 2 | description: 'Install Bun and cache dependencies' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Setup bun 7 | uses: oven-sh/setup-bun@v2 8 | with: 9 | bun-version-file: 'package.json' 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # misc 9 | .DS_Store 10 | *.pem 11 | 12 | # debug 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Turbo 18 | .turbo 19 | 20 | # Vercel 21 | .vercel 22 | 23 | # Env files 24 | .env.local 25 | 26 | # TypeScript 27 | *.tsbuildinfo 28 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.6 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tailwindCSS.experimental.classRegex": [ 3 | ["Style \\=([^;]*);", "'([^']*)'"], 4 | ["Style \\=([^;]*);", "\"([^\"]*)\""], 5 | ["Style \\=([^;]*);", "\\`([^\\`]*)\\`"], 6 | ["style \\=([^;]*);", "'([^']*)'"], 7 | ["style \\=([^;]*);", "\"([^\"]*)\""], 8 | ["style \\=([^;]*);", "\\`([^\\`]*)\\`"] 9 | ], 10 | "tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"], 11 | "prettier.enable": false, 12 | "editor.formatOnSave": true, 13 | "editor.defaultFormatter": "biomejs.biome", 14 | "editor.codeActionsOnSave": { 15 | "source.organizeImports.biome": "explicit", 16 | "source.fixAll.biome": "explicit" 17 | }, 18 | "[typescript]": { 19 | "editor.defaultFormatter": "biomejs.biome" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/published-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/assets/published-site.png -------------------------------------------------------------------------------- /bunfig.toml: -------------------------------------------------------------------------------- 1 | [install.scopes] 2 | "gitbook" = { token = "$NPM_TOKEN_READONLY", url = "https://registry.npmjs.org" } 3 | -------------------------------------------------------------------------------- /packages/cache-do/.gitignore: -------------------------------------------------------------------------------- 1 | .wrangler 2 | worker-configuration.d.ts 3 | dist/ 4 | -------------------------------------------------------------------------------- /packages/cache-do/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/cache-do 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - b7a5106: Disable cloudflare observability in production 8 | 9 | ## 0.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 9b8d519: Experiment with optimizing billable duration in Cloudflare by using multiple RPC sessions instead of one 14 | - 636b868: First version of a new cache backend powered by Cloudflare Durable Objects 15 | 16 | ### Patch Changes 17 | 18 | - 56f5fa1: Enable Workers observability with a sampling of 0.1 19 | -------------------------------------------------------------------------------- /packages/cache-do/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/cache-do` 2 | 3 | Cache backend, powered by Cloudflare Durable Objects. The cache is optimized for GitBook use-cases. 4 | 5 | ### Performances 6 | 7 | The cache backend is optimized for performances by being distributed and accessible close to the worker locations that are reading it. 8 | 9 | ### Geo-distribution 10 | 11 | To achieve a good balance between **performances** and **consistency**, cache objects are distributed over 7 locations, representing continents. 12 | 13 | It makes it possible to purge all 7 locations in one go and achieve fast consistency. 14 | 15 | ### Concepts 16 | 17 | **Cache tag**: unique tag in the cache environment. A cache tag groups multiple keys that should be purged together in one operation. 18 | Cache tags should not contain a large set of unique keys. Exceeding thousands could lead to performances or reliability issues. 19 | 20 | **Cache key**: unique key in the cache environment. Each key should be assigned to a `tag`. 21 | 22 | **Location**: cache is distributed over 7 unique locations, one for each continent. 23 | -------------------------------------------------------------------------------- /packages/cache-do/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/cache-do", 3 | "type": "module", 4 | "private": true, 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "development": "./src/index.ts", 9 | "default": "./dist/index.js" 10 | }, 11 | "./api": { 12 | "types": "./dist/api.d.ts", 13 | "development": "./src/api.ts", 14 | "default": "./dist/api.js" 15 | } 16 | }, 17 | "version": "0.1.1", 18 | "dependencies": { 19 | "@msgpack/msgpack": "^3.0.0-beta2", 20 | "lru_map": "^0.4.1" 21 | }, 22 | "devDependencies": { 23 | "typescript": "^5.5.3", 24 | "wrangler": "^4.10.0" 25 | }, 26 | "scripts": { 27 | "generate": "wrangler types", 28 | "build": "tsc", 29 | "typecheck": "tsc --noEmit", 30 | "dev": "tsc -w", 31 | "release": "wrangler deploy", 32 | "release:preview": "wrangler deploy && wrangler deploy --env preview" 33 | }, 34 | "files": ["dist", "src", "bin", "data", "README.md", "CHANGELOG.md"] 35 | } 36 | -------------------------------------------------------------------------------- /packages/cache-do/src/api.ts: -------------------------------------------------------------------------------- 1 | export * from './CacheObjectStub'; 2 | -------------------------------------------------------------------------------- /packages/cache-do/src/index.ts: -------------------------------------------------------------------------------- 1 | import { WorkerEntrypoint } from 'cloudflare:workers'; 2 | 3 | export * from './CacheObject'; 4 | 5 | export default class Worker extends WorkerEntrypoint { 6 | fetch() { 7 | return new Response('Hello, world!'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cache-do/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": false, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "incremental": true, 17 | "types": ["./worker-configuration.d.ts"] 18 | }, 19 | "include": ["src/**/*.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/cache-do/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "generate": { 5 | "outputs": ["worker-configuration.d.ts"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cache-do/wrangler.toml: -------------------------------------------------------------------------------- 1 | main = "./src/index.ts" 2 | name = "gitbook-open-cache" 3 | compatibility_date = "2024-09-02" 4 | 5 | durable_objects.bindings = [ 6 | {name = "CACHE", class_name = "CacheObject"} 7 | ] 8 | 9 | migrations = [ 10 | {tag = "v1", new_classes = ["CacheObject"]} 11 | ] 12 | 13 | [observability] 14 | enabled = false 15 | 16 | [env.preview] 17 | name = "gitbook-open-cache-preview" 18 | durable_objects.bindings = [ 19 | {name = "CACHE", class_name = "CacheObject"} 20 | ] 21 | migrations = [ 22 | {tag = "v1", new_classes = ["CacheObject"]} 23 | ] 24 | 25 | [env.preview.observability] 26 | enabled = true 27 | head_sampling_rate = 0.1 28 | -------------------------------------------------------------------------------- /packages/cache-tags/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/cache-tags/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/cache-tags 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - 77397ca: Fix version of @gitbook/api referenced in package.json 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - 116575c: Improve typing of getComputedContentSourceCacheTags to match latest API specification 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - f32bf1f: Export function `getCacheTagForURL` to easily get the cache tag for a URL. 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - 05ffd0e: Initial version of the package 26 | -------------------------------------------------------------------------------- /packages/cache-tags/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/cache-tags` 2 | 3 | Utility to generate cache tags for GitBook Open. -------------------------------------------------------------------------------- /packages/cache-tags/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/cache-tags", 3 | "type": "module", 4 | "exports": { 5 | ".": { 6 | "types": "./dist/index.d.ts", 7 | "development": "./src/index.ts", 8 | "default": "./dist/index.js" 9 | } 10 | }, 11 | "version": "0.3.1", 12 | "dependencies": { 13 | "@gitbook/api": "^0.118.0", 14 | "assert-never": "^1.2.1" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.5.3" 18 | }, 19 | "scripts": { 20 | "build": "tsc", 21 | "typecheck": "tsc --noEmit", 22 | "dev": "tsc -w" 23 | }, 24 | "files": ["dist", "src", "README.md", "CHANGELOG.md"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/cache-tags/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": false, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true, 18 | "types": [ 19 | "bun-types" // add Bun global 20 | ] 21 | }, 22 | "include": ["src/**/*.ts", "src/**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/colors/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/colors/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/colors 2 | 3 | ## 0.3.3 4 | 5 | ### Patch Changes 6 | 7 | - c3f6b8c: Update chroma ratio per step 8 | - 5e975ab: Fix code highlighting for HTTP 9 | - f7a3470: Change lightness check for color step 9 to allow input colors with a higher-than-needed contrast 10 | 11 | ## 0.3.2 12 | 13 | ### Patch Changes 14 | 15 | - cdffd7c: Desaturate text colors by decreasing chroma for the last steps of the color scale 16 | 17 | ## 0.3.1 18 | 19 | ### Patch Changes 20 | 21 | - fb90eb0: Reduce chroma of first color scale step 22 | 23 | ## 0.3.0 24 | 25 | ### Minor Changes 26 | 27 | - 4f0a772: Override tint lightness if supplied color is out of bounds 28 | 29 | ## 0.2.0 30 | 31 | ### Minor Changes 32 | 33 | - 445baaa: Initial release 34 | -------------------------------------------------------------------------------- /packages/colors/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/colors` 2 | 3 | A set of default colors and transformation functions used throughout the GitBook Open and app. 4 | -------------------------------------------------------------------------------- /packages/colors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/colors", 3 | "type": "module", 4 | "exports": { 5 | ".": { 6 | "types": "./dist/index.d.ts", 7 | "development": "./src/index.ts", 8 | "default": "./dist/index.js" 9 | } 10 | }, 11 | "version": "0.3.3", 12 | "devDependencies": { 13 | "typescript": "^5.5.3" 14 | }, 15 | "scripts": { 16 | "build": "tsc", 17 | "typecheck": "tsc --noEmit", 18 | "dev": "tsc -w" 19 | }, 20 | "files": ["dist", "src", "README.md", "CHANGELOG.md"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/colors/src/colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default primary color throughout the GitBook ecosystem. 3 | */ 4 | export const DEFAULT_PRIMARY_COLOR = '#346DDB'; 5 | 6 | /** 7 | * The darkest color that exists in GitBook, used as the relative minimum of every generated color scale. 8 | */ 9 | export const DARK_BASE = '#1D1D1D'; 10 | 11 | /** 12 | * The lightest color that exists in GitBook, used as the relative maximum of every generated color scale. 13 | */ 14 | export const LIGHT_BASE = '#FFFFFF'; 15 | 16 | /** 17 | * Used as the basis of all UI elements that are not colored by the primary color. Neutral gray by default, overridden by site customization. 18 | */ 19 | export const DEFAULT_TINT_COLOR = '#787878'; 20 | 21 | /** 22 | * Used for informational messages and neutral alerts. 23 | */ 24 | export const DEFAULT_HINT_INFO_COLOR = '#787878'; 25 | 26 | /** 27 | * Used for showing important information or non-critical warnings. 28 | */ 29 | export const DEFAULT_HINT_WARNING_COLOR = '#FE9A00'; 30 | 31 | /** 32 | * Used for destructive actions or raising attention to critical information. 33 | */ 34 | export const DEFAULT_HINT_DANGER_COLOR = '#FB2C36'; 35 | 36 | /** 37 | * Used for showing positive actions or achievements. 38 | */ 39 | export const DEFAULT_HINT_SUCCESS_COLOR = '#00C950'; 40 | -------------------------------------------------------------------------------- /packages/colors/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './colors'; 2 | export * from './transformations'; 3 | -------------------------------------------------------------------------------- /packages/colors/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": false, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true, 18 | "types": [ 19 | "bun-types" // add Bun global 20 | ] 21 | }, 22 | "include": ["src/**/*.ts", "src/**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/emoji-codepoints/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/emoji-codepoints/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/emoji-codepoints 2 | 3 | ## 0.2.0 4 | 5 | ### Minor Changes 6 | 7 | - 57adb3e: Second release to fix publishing with changeset 8 | 9 | ## 0.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 5f8a8fe: Initial release 14 | -------------------------------------------------------------------------------- /packages/emoji-codepoints/build.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import emojisRaws from 'emoji-assets/emoji.json'; 4 | 5 | interface EmojiData { 6 | code_points: { 7 | base: string; 8 | fully_qualified: string; 9 | }; 10 | } 11 | 12 | const emojis = emojisRaws as Record; 13 | const output: Record = {}; 14 | 15 | Object.entries(emojis).forEach(([key, value]) => { 16 | const emoji = value.code_points?.fully_qualified; 17 | if (emoji && key !== emoji) { 18 | output[key] = emoji; 19 | } else if (!emoji) { 20 | } 21 | }); 22 | 23 | fs.mkdirSync(path.resolve(__dirname, 'dist'), { recursive: true }); 24 | fs.writeFileSync( 25 | path.resolve(__dirname, 'dist/index.ts'), 26 | `export const emojiCodepoints: Record = ${JSON.stringify(output, null, 4)};` 27 | ); 28 | -------------------------------------------------------------------------------- /packages/emoji-codepoints/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/emoji-codepoints", 3 | "description": "Optimized mapping of codepoints to the fully qualified emoji codepoints", 4 | "version": "0.2.0", 5 | "private": true, 6 | "exports": "./dist/index.ts", 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "emoji-assets": "^8.0.0" 10 | }, 11 | "scripts": { 12 | "generate": "bun ./build.ts", 13 | "clean": "rm -rf ./dist" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook-v2/.gitignore: -------------------------------------------------------------------------------- 1 | # next.js 2 | /.next/ 3 | 4 | # vercel 5 | .vercel 6 | 7 | # cloudflare 8 | .open-next 9 | .wrangler 10 | 11 | # Symbolic links 12 | public 13 | -------------------------------------------------------------------------------- /packages/gitbook-v2/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook-v2/README.md -------------------------------------------------------------------------------- /packages/gitbook-v2/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /packages/gitbook-v2/open-next.config.ts: -------------------------------------------------------------------------------- 1 | import type { OpenNextConfig } from '@opennextjs/cloudflare'; 2 | 3 | export default { 4 | default: { 5 | override: { 6 | wrapper: 'cloudflare-node', 7 | converter: 'edge', 8 | proxyExternalRequest: 'fetch', 9 | queue: () => import('./openNext/queue/middleware').then((m) => m.default), 10 | incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), 11 | tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default), 12 | }, 13 | }, 14 | middleware: { 15 | external: true, 16 | override: { 17 | wrapper: 'cloudflare-edge', 18 | converter: 'edge', 19 | proxyExternalRequest: 'fetch', 20 | queue: () => import('./openNext/queue/middleware').then((m) => m.default), 21 | incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), 22 | tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default), 23 | }, 24 | }, 25 | dangerous: { 26 | enableCacheInterception: true, 27 | }, 28 | edgeExternals: ['node:crypto'], 29 | } satisfies OpenNextConfig; 30 | -------------------------------------------------------------------------------- /packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts: -------------------------------------------------------------------------------- 1 | // In this script, we use the args from the cli to update the PREVIEW_URL vars in the wrangler config file for the middleware 2 | import fs from 'node:fs'; 3 | import path from 'node:path'; 4 | 5 | const wranglerConfigPath = path.join(__dirname, '../middlewareWrangler.jsonc'); 6 | 7 | const file = fs.readFileSync(wranglerConfigPath, 'utf-8'); 8 | 9 | const args = process.argv.slice(2); 10 | // The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL 11 | const versionId = args[0]; 12 | 13 | // The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev 14 | const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; 15 | 16 | let updatedFile = file.replace( 17 | /"PREVIEW_HOSTNAME": "TO_REPLACE"/, 18 | `"PREVIEW_HOSTNAME": "${previewHostname}"` 19 | ); 20 | 21 | updatedFile = updatedFile.replaceAll( 22 | /"WORKER_VERSION_ID": "TO_REPLACE"/g, 23 | `"WORKER_VERSION_ID": "${versionId}"` 24 | ); 25 | 26 | fs.writeFileSync(wranglerConfigPath, updatedFile); 27 | -------------------------------------------------------------------------------- /packages/gitbook-v2/openNext/queue/middleware.ts: -------------------------------------------------------------------------------- 1 | import { trace } from '@/lib/tracing'; 2 | import type { Queue } from '@opennextjs/aws/types/overrides.js'; 3 | import { getCloudflareContext } from '@opennextjs/cloudflare'; 4 | import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue'; 5 | 6 | export default { 7 | name: 'GitbookISRQueue', 8 | send: async (msg) => { 9 | return trace({ operation: 'gitbookISRQueueSend', name: msg.MessageBody.url }, async () => { 10 | const { ctx } = getCloudflareContext(); 11 | ctx.waitUntil(doQueue.send(msg)); 12 | }); 13 | }, 14 | } satisfies Queue; 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/openNext/queue/server.ts: -------------------------------------------------------------------------------- 1 | import type { Queue } from '@opennextjs/aws/types/overrides.js'; 2 | 3 | export default { 4 | name: 'GitbookISRQueue', 5 | send: async (msg) => { 6 | // We should never reach this point in the server. If that's the case, we should log it. 7 | console.warn('GitbookISRQueue: send called on server side, this should not happen.', msg); 8 | }, 9 | } satisfies Queue; 10 | -------------------------------------------------------------------------------- /packages/gitbook-v2/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { SitePageSkeleton } from '@/components/SitePage'; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { SitePageNotFound } from '@/components/SitePage'; 2 | 3 | export default async function NotFound() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/icon/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveIcon } from '@/routes/icon'; 4 | import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils'; 5 | 6 | export async function GET( 7 | request: NextRequest, 8 | { params }: { params: Promise } 9 | ) { 10 | const { context } = await getDynamicSiteContext(await params); 11 | return serveIcon(context, request); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/ogimage/[pageId]/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import type { PageIdParams } from '@/components/SitePage'; 4 | import { serveOGImage } from '@/routes/ogimage'; 5 | import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils'; 6 | 7 | export async function GET( 8 | _request: NextRequest, 9 | { params }: { params: Promise } 10 | ) { 11 | const { context } = await getDynamicSiteContext(await params); 12 | return serveOGImage(context, await params); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/layout.tsx: -------------------------------------------------------------------------------- 1 | import { PDFRootLayout } from '@/components/PDF'; 2 | import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils'; 3 | 4 | export default async function RootLayout(props: { 5 | params: Promise; 6 | children: React.ReactNode; 7 | }) { 8 | const { params, children } = props; 9 | const { context } = await getDynamicSiteContext(await params); 10 | 11 | return {children}; 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/page.tsx: -------------------------------------------------------------------------------- 1 | import { PDFPage, generatePDFMetadata } from '@/components/PDF'; 2 | import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils'; 3 | 4 | export async function generateMetadata({ 5 | params, 6 | }: { 7 | params: Promise; 8 | }) { 9 | const { context } = await getDynamicSiteContext(await params); 10 | return generatePDFMetadata(context); 11 | } 12 | 13 | export default async function Page(props: { 14 | params: Promise; 15 | searchParams: Promise<{ [key: string]: string }>; 16 | }) { 17 | const { params, searchParams } = props; 18 | const { context } = await getDynamicSiteContext(await params); 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/[pagePath]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { SitePageNotFound } from '@/components/SitePage'; 2 | 3 | export default async function NotFound() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/llms.txt/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveLLMsTxt } from '@/routes/llms'; 4 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 5 | 6 | export const dynamic = 'force-static'; 7 | 8 | export async function GET( 9 | _request: NextRequest, 10 | { params }: { params: Promise } 11 | ) { 12 | const { context } = await getStaticSiteContext(await params); 13 | return serveLLMsTxt(context, { withMarkdownPages: true }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/robots.txt/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveRobotsTxt } from '@/routes/robots'; 4 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 5 | 6 | export const dynamic = 'force-static'; 7 | 8 | export async function GET( 9 | _request: NextRequest, 10 | { params }: { params: Promise } 11 | ) { 12 | const { context } = await getStaticSiteContext(await params); 13 | return serveRobotsTxt(context); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/sitemap-pages.xml/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { servePagesSitemap } from '@/routes/sitemap'; 4 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 5 | 6 | export const dynamic = 'force-static'; 7 | 8 | export async function GET( 9 | _request: NextRequest, 10 | { params }: { params: Promise } 11 | ) { 12 | const { context } = await getStaticSiteContext(await params); 13 | return servePagesSitemap(context); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/sitemap.xml/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveRootSitemap } from '@/routes/sitemap'; 4 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 5 | 6 | export const dynamic = 'force-static'; 7 | 8 | export async function GET( 9 | _request: NextRequest, 10 | { params }: { params: Promise } 11 | ) { 12 | const { context } = await getStaticSiteContext(await params); 13 | return serveRootSitemap(context); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/icon/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveIcon } from '@/routes/icon'; 4 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 5 | 6 | export const dynamic = 'force-static'; 7 | 8 | export async function GET( 9 | request: NextRequest, 10 | { params }: { params: Promise } 11 | ) { 12 | const { context } = await getStaticSiteContext(await params); 13 | return serveIcon(context, request); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown/[pagePath]/route.ts: -------------------------------------------------------------------------------- 1 | import { servePageMarkdown } from '@/routes/markdownPage'; 2 | import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@v2/app/utils'; 3 | import type { NextRequest } from 'next/server'; 4 | 5 | export const dynamic = 'force-static'; 6 | 7 | export async function GET(_request: NextRequest, { params }: { params: Promise }) { 8 | const { context } = await getStaticSiteContext(await params); 9 | const pathname = getPagePathFromParams(await params); 10 | return servePageMarkdown(context, pathname); 11 | } 12 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/ogimage/[pageId]/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import type { PageIdParams } from '@/components/SitePage'; 4 | import { serveOGImage } from '@/routes/ogimage'; 5 | import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; 6 | 7 | export const dynamic = 'force-static'; 8 | 9 | export async function GET( 10 | _request: NextRequest, 11 | { params }: { params: Promise } 12 | ) { 13 | const { context } = await getStaticSiteContext(await params); 14 | return serveOGImage(context, await params); 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~gitbook/env/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from 'next/server'; 2 | 3 | import { 4 | GITBOOK_API_PUBLIC_URL, 5 | GITBOOK_API_TOKEN, 6 | GITBOOK_API_URL, 7 | GITBOOK_APP_URL, 8 | GITBOOK_ASSETS_URL, 9 | GITBOOK_DISABLE_TRACKING, 10 | GITBOOK_FONTS_URL, 11 | GITBOOK_ICONS_URL, 12 | GITBOOK_IMAGE_RESIZE_SIGNING_KEY, 13 | GITBOOK_INTEGRATIONS_HOST, 14 | GITBOOK_SECRET, 15 | GITBOOK_URL, 16 | GITBOOK_USER_AGENT, 17 | } from '@v2/lib/env'; 18 | 19 | /** 20 | * Output the public environment variables for this deployment 21 | */ 22 | export async function GET(_req: NextRequest) { 23 | return NextResponse.json({ 24 | GITBOOK_URL, 25 | GITBOOK_APP_URL, 26 | GITBOOK_API_URL, 27 | GITBOOK_API_PUBLIC_URL, 28 | GITBOOK_ASSETS_URL, 29 | GITBOOK_FONTS_URL, 30 | GITBOOK_ICONS_URL, 31 | GITBOOK_USER_AGENT, 32 | GITBOOK_INTEGRATIONS_HOST, 33 | GITBOOK_DISABLE_TRACKING, 34 | 35 | // Secret envs 36 | GITBOOK_SECRET: !!GITBOOK_SECRET, 37 | GITBOOK_API_TOKEN: !!GITBOOK_API_TOKEN, 38 | GITBOOK_IMAGE_RESIZE_SIGNING_KEY: !!GITBOOK_IMAGE_RESIZE_SIGNING_KEY, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~gitbook/revalidate/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from 'next/server'; 2 | 3 | import { withVerifySignature } from '@v2/lib/routes'; 4 | import { revalidateTag } from 'next/cache'; 5 | 6 | interface JsonBody { 7 | tags: string[]; 8 | } 9 | 10 | /** 11 | * Revalidate cached data based on tags. 12 | * The body should be a JSON with { tags: string[] } 13 | */ 14 | export async function POST(req: NextRequest) { 15 | return withVerifySignature(req, async (body) => { 16 | if (!body.tags || !Array.isArray(body.tags)) { 17 | return NextResponse.json( 18 | { 19 | error: 'tags must be an array', 20 | }, 21 | { status: 400 } 22 | ); 23 | } 24 | 25 | body.tags.forEach((tag) => { 26 | // biome-ignore lint/suspicious/noConsole: we want to log here 27 | console.log(`Revalidating tag: ${tag}`); 28 | revalidateTag(tag); 29 | }); 30 | 31 | return NextResponse.json({ 32 | success: true, 33 | }); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~/changes/[changeRequestId]/~gitbook/pdf/layout.tsx: -------------------------------------------------------------------------------- 1 | import RootLayout from '@v2/app/~space/[spaceId]/~gitbook/pdf/layout'; 2 | export default RootLayout; 3 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~/changes/[changeRequestId]/~gitbook/pdf/page.tsx: -------------------------------------------------------------------------------- 1 | import PDFPage, { generateMetadata } from '@v2/app/~space/[spaceId]/~gitbook/pdf/page'; 2 | 3 | export default PDFPage; 4 | export { generateMetadata }; 5 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~/revisions/[changeRequestId]/~gitbook/pdf/layout.tsx: -------------------------------------------------------------------------------- 1 | import RootLayout from '@v2/app/~space/[spaceId]/~gitbook/pdf/layout'; 2 | export default RootLayout; 3 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~/revisions/[changeRequestId]/~gitbook/pdf/page.tsx: -------------------------------------------------------------------------------- 1 | import PDFPage, { generateMetadata } from '@v2/app/~space/[spaceId]/~gitbook/pdf/page'; 2 | 3 | export default PDFPage; 4 | export { generateMetadata }; 5 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~gitbook/pdf/layout.tsx: -------------------------------------------------------------------------------- 1 | import { PDFRootLayout } from '@/components/PDF'; 2 | import { type SpacePDFRouteParams, getSpacePDFContext } from '@v2/app/~space/[spaceId]/pdf'; 3 | 4 | export default async function RootLayout(props: { 5 | params: Promise; 6 | children: React.ReactNode; 7 | }) { 8 | const { params, children } = props; 9 | const context = await getSpacePDFContext(await params); 10 | 11 | return {children}; 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/app/~space/[spaceId]/~gitbook/pdf/page.tsx: -------------------------------------------------------------------------------- 1 | import { PDFPage, generatePDFMetadata } from '@/components/PDF'; 2 | import { type SpacePDFRouteParams, getSpacePDFContext } from '@v2/app/~space/[spaceId]/pdf'; 3 | 4 | export async function generateMetadata({ 5 | params, 6 | }: { 7 | params: Promise; 8 | }) { 9 | const context = await getSpacePDFContext(await params); 10 | return generatePDFMetadata(context); 11 | } 12 | 13 | export default async function Page(props: { 14 | params: Promise; 15 | searchParams: Promise<{ [key: string]: string }>; 16 | }) { 17 | const { params, searchParams } = props; 18 | const context = await getSpacePDFContext(await params); 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/data/cloudflare.ts: -------------------------------------------------------------------------------- 1 | import { getCloudflareContext as getCloudflareContextOpenNext } from '@opennextjs/cloudflare'; 2 | import { GITBOOK_RUNTIME } from '../env'; 3 | 4 | /** 5 | * Return the Cloudflare context or null when not running in Cloudflare. 6 | */ 7 | export function getCloudflareContext() { 8 | if (GITBOOK_RUNTIME !== 'cloudflare') { 9 | return null; 10 | } 11 | 12 | return getCloudflareContextOpenNext(); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './types'; 3 | export * from './pages'; 4 | export * from './urls'; 5 | export * from './errors'; 6 | export * from './lookup'; 7 | export * from './visitor'; 8 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/data/pages.ts: -------------------------------------------------------------------------------- 1 | import type { JSONDocument, RevisionPageDocument, Space } from '@gitbook/api'; 2 | import { getDataOrNull } from './errors'; 3 | import type { GitBookDataFetcher } from './types'; 4 | 5 | /** 6 | * Get the document for a page. 7 | */ 8 | export async function getPageDocument( 9 | dataFetcher: GitBookDataFetcher, 10 | space: Space, 11 | page: RevisionPageDocument 12 | ): Promise { 13 | if (page.documentId) { 14 | return getDataOrNull( 15 | dataFetcher.getDocument({ spaceId: space.id, documentId: page.documentId }) 16 | ); 17 | } 18 | if ('computed' in page && page.computed) { 19 | return getDataOrNull( 20 | dataFetcher.getComputedDocument({ 21 | organizationId: space.organization, 22 | spaceId: space.id, 23 | source: page.computed, 24 | seed: page.computedSeed, 25 | }) 26 | ); 27 | } 28 | 29 | return null; 30 | } 31 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/data/visitor.ts: -------------------------------------------------------------------------------- 1 | import { withLeadingSlash, withTrailingSlash } from '@/lib/paths'; 2 | import type { PublishedSiteContent } from '@gitbook/api'; 3 | import { getProxyRequestIdentifier, isProxyRequest } from '@v2/lib/proxy'; 4 | 5 | /** 6 | * Get the appropriate base path for the visitor authentication cookie. 7 | */ 8 | export function getVisitorAuthBasePath( 9 | siteRequestURL: URL, 10 | siteURLData: PublishedSiteContent 11 | ): string { 12 | // The siteRequestURL for proxy requests is of the form `https://proxy.gitbook.com/site/siteId/...` 13 | // In such cases, we should not use the resolved siteBasePath for the cookie because for subsequent requests 14 | // we will not have the siteBasePath in the request URL in order to retrieve the cookie. So we use the 15 | // proxy identifier instead. 16 | return isProxyRequest(siteRequestURL) 17 | ? withLeadingSlash(withTrailingSlash(getProxyRequestIdentifier(siteRequestURL))) 18 | : siteURLData.siteBasePath; 19 | } 20 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/env/index.ts: -------------------------------------------------------------------------------- 1 | export * from './globals'; 2 | export * from './urls'; 3 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/env/urls.ts: -------------------------------------------------------------------------------- 1 | import { GITBOOK_ASSETS_URL, GITBOOK_URL } from './globals'; 2 | 3 | /** 4 | * Check if the URL is a GitBook host URL. 5 | */ 6 | export function isGitBookHostURL(input: URL | string): boolean { 7 | const url = typeof input === 'string' ? new URL(input) : input; 8 | 9 | if (!GITBOOK_URL) { 10 | return false; 11 | } 12 | 13 | const gitbookHost = new URL(GITBOOK_URL).host; 14 | 15 | if (url.host === gitbookHost) { 16 | return true; 17 | } 18 | 19 | // Handle the Cloudflare preview URLs that are prefixed with a random hash 20 | // https://developers.cloudflare.com/workers/configuration/previews/ 21 | if (url.host.endsWith(`-${gitbookHost}`)) { 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | /** 29 | * Check if the URL is a GitBook assets host URL. 30 | */ 31 | export function isGitBookAssetsHostURL(input: URL | string): boolean { 32 | const url = typeof input === 'string' ? new URL(input) : input; 33 | 34 | if (!GITBOOK_ASSETS_URL) { 35 | return false; 36 | } 37 | 38 | const gitbookAssetsHost = new URL(GITBOOK_ASSETS_URL).host; 39 | 40 | if (url.host === gitbookAssetsHost) { 41 | return true; 42 | } 43 | 44 | return false; 45 | } 46 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/getImageResizingContextId.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { getImageResizingContextId } from './getImageResizingContextId'; 3 | 4 | describe('getImageResizingContextId', () => { 5 | it('should return proxy identifier for proxy requests', () => { 6 | const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world'); 7 | expect(getImageResizingContextId(proxyRequestURL)).toBe('sites/site_foo'); 8 | }); 9 | 10 | it('should return preview identifier for preview requests', () => { 11 | const previewRequestURL = new URL('https://preview/site_foo/hello/world'); 12 | expect(getImageResizingContextId(previewRequestURL)).toBe('site_foo'); 13 | }); 14 | 15 | it('should return host for regular requests', () => { 16 | const regularRequestURL = new URL('https://example.com/docs/foo/hello/world'); 17 | expect(getImageResizingContextId(regularRequestURL)).toBe('example.com'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/getImageResizingContextId.ts: -------------------------------------------------------------------------------- 1 | import { getPreviewRequestIdentifier, isPreviewRequest } from '@v2/lib/preview'; 2 | import { getProxyRequestIdentifier, isProxyRequest } from '@v2/lib/proxy'; 3 | 4 | /** 5 | * Get the site identifier to use for image resizing for an incoming request. 6 | * This identifier can be obtained before resolving the request URL. 7 | */ 8 | export function getImageResizingContextId(url: URL): string { 9 | if (isProxyRequest(url)) { 10 | return getProxyRequestIdentifier(url); 11 | } 12 | if (isPreviewRequest(url)) { 13 | return getPreviewRequestIdentifier(url); 14 | } 15 | 16 | return url.host; 17 | } 18 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './createImageResizer'; 3 | export * from './signatures'; 4 | export * from './utils'; 5 | export * from './getImageResizingContextId'; 6 | export * from './resizer'; 7 | export * from './checkIsSizableImageURL'; 8 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/resizer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './resizeImage'; 3 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/resizer/types.ts: -------------------------------------------------------------------------------- 1 | export interface CloudflareImageJsonFormat { 2 | width: number; 3 | height: number; 4 | original: { 5 | file_size: number; 6 | width: number; 7 | height: number; 8 | format: string; 9 | }; 10 | } 11 | 12 | /** 13 | * https://developers.cloudflare.com/images/image-resizing/resize-with-workers/ 14 | */ 15 | export interface CloudflareImageOptions { 16 | format?: 'webp' | 'avif' | 'json' | 'jpeg'; 17 | fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad'; 18 | width?: number; 19 | height?: number; 20 | dpr?: number; 21 | anim?: boolean; 22 | quality?: number; 23 | } 24 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/resizer/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy a response to make sure it can be mutated by the rest of the middleware. 3 | * To avoid errors "Can't modify immutable headers". 4 | */ 5 | export function copyImageResponse(response: Response) { 6 | return new Response(response.body, response); 7 | } 8 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/types.ts: -------------------------------------------------------------------------------- 1 | export type GetImageSizeOptions = { 2 | dpr?: number; 3 | }; 4 | 5 | export type ResizeImageOptions = GetImageSizeOptions & { 6 | width?: number; 7 | height?: number; 8 | dpr?: number; 9 | quality?: number; 10 | }; 11 | 12 | interface ImageSize { 13 | width: number; 14 | height: number; 15 | } 16 | 17 | export type ImageResizer = { 18 | /** 19 | * Resize an image. 20 | * @param input - The image URL to resize. 21 | * @param options - The options to resize the image. 22 | */ 23 | getResizedImageURL(imageURL: string): null | ((options: ResizeImageOptions) => Promise); 24 | 25 | /** 26 | * Get the size of an image. 27 | */ 28 | getImageSize(imageURL: string, options: GetImageSizeOptions): Promise; 29 | }; 30 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/images/utils.ts: -------------------------------------------------------------------------------- 1 | import type { ImageResizer, ResizeImageOptions } from './types'; 2 | 3 | /** 4 | * Quick utility to get a resized image URL. 5 | */ 6 | export async function getResizedImageURL( 7 | resizer: ImageResizer | undefined, 8 | url: string, 9 | options: ResizeImageOptions 10 | ) { 11 | const getURL = resizer?.getResizedImageURL(url); 12 | if (!getURL) { 13 | return url; 14 | } 15 | 16 | return await getURL(options); 17 | } 18 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/preview.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { getPreviewRequestIdentifier, isPreviewRequest } from './preview'; 3 | 4 | describe('isPreviewRequest', () => { 5 | it('should return true for preview requests', () => { 6 | const previewRequestURL = new URL('https://preview/site_foo/hello/world'); 7 | expect(isPreviewRequest(previewRequestURL)).toBe(true); 8 | }); 9 | 10 | it('should return false for non-preview requests', () => { 11 | const nonPreviewRequestURL = new URL('https://example.com/docs/foo/hello/world'); 12 | expect(isPreviewRequest(nonPreviewRequestURL)).toBe(false); 13 | }); 14 | }); 15 | 16 | describe('getPreviewRequestIdentifier', () => { 17 | it('should return the correct identifier for preview requests', () => { 18 | const previewRequestURL = new URL('https://preview/site_foo/hello/world'); 19 | expect(getPreviewRequestIdentifier(previewRequestURL)).toBe('site_foo'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/preview.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the request to the site is a preview request. 3 | */ 4 | export function isPreviewRequest(requestURL: URL): boolean { 5 | return requestURL.host === 'preview'; 6 | } 7 | 8 | export function getPreviewRequestIdentifier(requestURL: URL): string { 9 | // For preview requests, we extract the site ID from the pathname 10 | // e.g. https://preview/site_id/... 11 | const pathname = requestURL.pathname.slice(1).split('/'); 12 | return pathname[0]; 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/proxy.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { getProxyRequestIdentifier, isProxyRequest } from './proxy'; 3 | 4 | describe('isProxyRequest', () => { 5 | it('should return true for proxy requests', () => { 6 | const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world'); 7 | expect(isProxyRequest(proxyRequestURL)).toBe(true); 8 | }); 9 | 10 | it('should return false for non-proxy requests', () => { 11 | const nonProxyRequestURL = new URL('https://example.com/docs/foo/hello/world'); 12 | expect(isProxyRequest(nonProxyRequestURL)).toBe(false); 13 | }); 14 | }); 15 | 16 | describe('getProxyRequestIdentifier', () => { 17 | it('should return the correct identifier for proxy requests', () => { 18 | const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world'); 19 | expect(getProxyRequestIdentifier(proxyRequestURL)).toBe('sites/site_foo'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the request to the site was through a proxy. 3 | */ 4 | export function isProxyRequest(requestURL: URL): boolean { 5 | return ( 6 | requestURL.host === 'proxy.gitbook.site' || requestURL.host === 'proxy.gitbook-staging.site' 7 | ); 8 | } 9 | 10 | export function getProxyRequestIdentifier(requestURL: URL): string { 11 | // For proxy requests, we extract the site ID from the pathname 12 | // e.g. https://proxy.gitbook.site/site/siteId/... 13 | const pathname = requestURL.pathname.slice(1).split('/'); 14 | return pathname.slice(0, 2).join('/'); 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook-v2/src/lib/server-actions.ts: -------------------------------------------------------------------------------- 1 | import { type GitBookBaseContext, fetchSiteContextByURLLookup, getBaseContext } from './context'; 2 | import { 3 | getSiteURLDataFromMiddleware, 4 | getSiteURLFromMiddleware, 5 | getURLModeFromMiddleware, 6 | } from './middleware'; 7 | 8 | /** 9 | * Get the base context for a server action. 10 | * This function should only be called in a server action. 11 | */ 12 | export async function getServerActionBaseContext() { 13 | const siteURL = await getSiteURLFromMiddleware(); 14 | const siteURLData = await getSiteURLDataFromMiddleware(); 15 | const urlMode = await getURLModeFromMiddleware(); 16 | 17 | return getBaseContext({ 18 | siteURL, 19 | siteURLData, 20 | urlMode, 21 | }); 22 | } 23 | 24 | /** 25 | * Fetch the context for a site in a server action. 26 | * The server action is always dynamic and the request is passed through the middleware. 27 | */ 28 | export async function fetchServerActionSiteContext(baseContext: GitBookBaseContext) { 29 | const siteURLData = await getSiteURLDataFromMiddleware(); 30 | return fetchSiteContextByURLLookup(baseContext, siteURLData); 31 | } 32 | -------------------------------------------------------------------------------- /packages/gitbook-v2/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../gitbook/tailwind.config'; 2 | 3 | export default { 4 | ...config, 5 | content: [ 6 | '../gitbook/src/pages/**/*.{js,ts,jsx,tsx,mdx}', 7 | '../gitbook/src/components/**/*.{js,ts,jsx,tsx,mdx}', 8 | '../gitbook/src/app/**/*.{js,ts,jsx,tsx,mdx}', 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/gitbook-v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "module": "esnext", 11 | "esModuleInterop": true, 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@v2/*": ["./src/*"], 23 | "@/*": ["../gitbook/src/*"] 24 | }, 25 | "types": [ 26 | "bun-types" // add Bun global 27 | ] 28 | }, 29 | "include": [ 30 | "next-env.d.ts", 31 | ".next/types/**/*.ts", 32 | "**/*.ts", 33 | "**/*.tsx", 34 | "../gitbook/types/**/*.d.ts", 35 | "../gitbook/cf-env.d.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "packages/openapi-parser", 40 | "packages/react-openapi", 41 | "packages/react-math", 42 | "packages/gitbook" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /packages/gitbook-v2/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "generate": { 5 | "outputs": ["public"] 6 | }, 7 | "typecheck": { 8 | "dependsOn": ["^typecheck"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/gitbook/.env.example: -------------------------------------------------------------------------------- 1 | # Configurations 2 | 3 | ### API ### 4 | 5 | ## API token to use when looking up the published content 6 | # GITBOOK_API_URL=https://api.gitbook.com 7 | # GITBOOK_TOKEN=xxx 8 | 9 | ## User agent to use when making requests to the API 10 | # GITBOOK_USER_AGENT=GitBook/1.0.0 11 | # GITBOOK_USER_AGENT_COMMENT=something 12 | 13 | ### URL of the application ### 14 | # NEXT_PUBLIC_GITBOOK_APP_URL=https://app.gitbook.com 15 | 16 | ### Image resizing ### 17 | # GITBOOK_IMAGE_RESIZE_SIGNING_KEY=1234567890 18 | # GITBOOK_IMAGE_RESIZE_URL=https://mycompany.com/cdn-cgi/image/ 19 | 20 | ### SEO ### 21 | # GITBOOK_BLOCK_SEARCH_INDEXATION=true 22 | 23 | ## Caching 24 | # GITBOOK_OUTPUT_CACHE=true 25 | 26 | ### Silent logs 27 | # SILENT=true -------------------------------------------------------------------------------- /packages/gitbook/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # testing 4 | /coverage 5 | 6 | # next.js 7 | /.next/ 8 | /out/ 9 | 10 | # production 11 | /build 12 | 13 | # local env files 14 | .env*.local 15 | 16 | # vercel 17 | .vercel 18 | 19 | # typescript 20 | *.tsbuildinfo 21 | next-env.d.ts 22 | 23 | # visual tests 24 | screenshots/ 25 | 26 | /test-results/ 27 | /playwright-report/ 28 | /blob-report/ 29 | /playwright/.cache/ 30 | 31 | # Generated public files 32 | /public/~gitbook/static/* 33 | !/public/~gitbook/static/images 34 | -------------------------------------------------------------------------------- /packages/gitbook/_routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "exclude": ["/~gitbook/static/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/gitbook/cf-env.d.ts: -------------------------------------------------------------------------------- 1 | import type { CacheObject } from '@gitbook/cache-do'; 2 | 3 | declare global { 4 | interface CloudflareEnv { 5 | CACHE?: DurableObjectNamespace; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/gitbook/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | testDir: './e2e', 5 | fullyParallel: true, 6 | forbidOnly: !!process.env.CI, 7 | retries: process.env.CI ? 2 : 0, 8 | reporter: [ 9 | process.env.CI ? ['dot'] : ['list'], 10 | ['@argos-ci/playwright/reporter', { uploadToArgos: !!process.env.CI }], 11 | ], 12 | projects: [ 13 | { 14 | name: 'chromium', 15 | use: { ...devices['Desktop Chrome'] }, 16 | }, 17 | ], 18 | use: { 19 | trace: 'on-first-retry', 20 | screenshot: 'only-on-failure', 21 | contextOptions: { 22 | reducedMotion: 'reduce', 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/gitbook/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/gitbook/public/_headers: -------------------------------------------------------------------------------- 1 | # GitBook immutable static assets 2 | # Duplicated from next.config.mjs until OpenNext supports generating static headers 3 | /~gitbook/static/* 4 | cache-control: public,max-age=31536000,immutable 5 | Access-Control-Allow-Origin: * 6 | /_next/static/* 7 | Access-Control-Allow-Origin: * 8 | -------------------------------------------------------------------------------- /packages/gitbook/public/~gitbook/static/images/ogimage-grid-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/public/~gitbook/static/images/ogimage-grid-black.png -------------------------------------------------------------------------------- /packages/gitbook/public/~gitbook/static/images/ogimage-grid-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/public/~gitbook/static/images/ogimage-grid-white.png -------------------------------------------------------------------------------- /packages/gitbook/src/app/(global)/~gitbook/image/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { serveResizedImage } from '@/routes/image'; 4 | 5 | export const runtime = 'edge'; 6 | 7 | export async function GET(request: NextRequest) { 8 | return serveResizedImage(request); 9 | } 10 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import NextError from 'next/error'; 4 | 5 | export default function GlobalError() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(content)/[[...pathname]]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { SitePageSkeleton } from '@/components/SitePage'; 2 | 3 | /** 4 | * Placeholder when loading a page. 5 | */ 6 | export default function PageSkeleton() { 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(content)/[[...pathname]]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { SitePageNotFound } from '@/components/SitePage'; 2 | 3 | export default async function NotFound() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/llms.txt/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { getSiteContentPointer } from '@/lib/pointer'; 4 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 5 | import { serveLLMsTxt } from '@/routes/llms'; 6 | 7 | export const runtime = 'edge'; 8 | 9 | export async function GET(_req: NextRequest) { 10 | const pointer = await getSiteContentPointer(); 11 | const context = await fetchV1ContextForSitePointer(pointer); 12 | 13 | return serveLLMsTxt(context); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/robots.txt/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { getSiteContentPointer } from '@/lib/pointer'; 4 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 5 | import { serveRobotsTxt } from '@/routes/robots'; 6 | 7 | export const runtime = 'edge'; 8 | 9 | export async function GET(_request: NextRequest) { 10 | const pointer = await getSiteContentPointer(); 11 | const context = await fetchV1ContextForSitePointer(pointer); 12 | 13 | return serveRobotsTxt(context); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/sitemap-pages.xml/route.ts: -------------------------------------------------------------------------------- 1 | import { getSiteContentPointer } from '@/lib/pointer'; 2 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 3 | import { servePagesSitemap } from '@/routes/sitemap'; 4 | 5 | export const runtime = 'edge'; 6 | 7 | export async function GET() { 8 | const pointer = await getSiteContentPointer(); 9 | const context = await fetchV1ContextForSitePointer(pointer); 10 | 11 | return servePagesSitemap(context); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/sitemap.xml/route.ts: -------------------------------------------------------------------------------- 1 | import { getSiteContentPointer } from '@/lib/pointer'; 2 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 3 | import { serveRootSitemap } from '@/routes/sitemap'; 4 | 5 | export const runtime = 'edge'; 6 | 7 | export async function GET() { 8 | const pointer = await getSiteContentPointer(); 9 | const context = await fetchV1ContextForSitePointer(pointer); 10 | 11 | return serveRootSitemap(context); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/~gitbook/icon/route.tsx: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import { getSiteContentPointer } from '@/lib/pointer'; 4 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 5 | import { serveIcon } from '@/routes/icon'; 6 | 7 | export const runtime = 'edge'; 8 | 9 | export async function GET(req: NextRequest) { 10 | const pointer = await getSiteContentPointer(); 11 | const context = await fetchV1ContextForSitePointer(pointer); 12 | 13 | return serveIcon(context, req); 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | 3 | import type { PageIdParams } from '@/components/SitePage'; 4 | import { getSiteContentPointer } from '@/lib/pointer'; 5 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 6 | import { serveOGImage } from '@/routes/ogimage'; 7 | 8 | export const runtime = 'edge'; 9 | 10 | export async function GET(_req: NextRequest, { params }: { params: Promise }) { 11 | const pointer = await getSiteContentPointer(); 12 | const baseContext = await fetchV1ContextForSitePointer(pointer); 13 | 14 | return serveOGImage(baseContext, await params); 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(site)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { CustomizationRootLayout } from '@/components/RootLayout'; 2 | import { getSiteContentPointer } from '@/lib/pointer'; 3 | import { fetchV1ContextForSitePointer } from '@/lib/v1'; 4 | 5 | /** 6 | * Layout to be used for the site root. It fetches the customization data for the site 7 | * and initializes the CustomizationRootLayout with it. 8 | */ 9 | export default async function SiteRootLayout(props: { children: React.ReactNode }) { 10 | const { children } = props; 11 | 12 | const pointer = await getSiteContentPointer(); 13 | const { customization } = await fetchV1ContextForSitePointer(pointer); 14 | 15 | return ( 16 | {children} 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/layout.tsx: -------------------------------------------------------------------------------- 1 | import { PDFRootLayout } from '@/components/PDF'; 2 | import { getV1ContextForPDF } from './pointer'; 3 | 4 | export default async function RootLayout(props: { children: React.ReactNode }) { 5 | const { children } = props; 6 | const context = await getV1ContextForPDF(); 7 | 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx: -------------------------------------------------------------------------------- 1 | import { PDFPage, generatePDFMetadata } from '@/components/PDF'; 2 | import { getV1ContextForPDF } from './pointer'; 3 | 4 | export const runtime = 'edge'; 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export async function generateMetadata() { 8 | const context = await getV1ContextForPDF(); 9 | return generatePDFMetadata(context); 10 | } 11 | 12 | export default async function Page(props: { 13 | searchParams: Promise<{ [key: string]: string }>; 14 | }) { 15 | const context = await getV1ContextForPDF(); 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /packages/gitbook/src/cloudflare-entrypoint.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import nextOnPagesHandler from '@cloudflare/next-on-pages/fetch-handler'; 3 | 4 | import { withResponseCacheTags } from './lib/cache/response'; 5 | import { withMiddlewareHeadersStorage } from './lib/middleware'; 6 | 7 | /** 8 | * We use a custom entrypoint until we can move to opennext (https://github.com/opennextjs/opennextjs-cloudflare/issues/92). 9 | * There is a bug in next-on-pages where headers can't be set on the response in the middleware for RSC requests (https://github.com/cloudflare/next-on-pages/issues/897). 10 | */ 11 | export default { 12 | async fetch(request, env, ctx) { 13 | const response = await withResponseCacheTags(() => 14 | withMiddlewareHeadersStorage(() => nextOnPagesHandler.fetch(request, env, ctx)) 15 | ); 16 | 17 | return response; 18 | }, 19 | } as ExportedHandler<{ ASSETS: Fetcher }>; 20 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Adaptive/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AIPageLinkSummary'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Adaptive/server-actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './streamLinkPageSummary'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/AdminToolbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AdminToolbar'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Ads/AdPixels.tsx: -------------------------------------------------------------------------------- 1 | import { tcls } from '@/lib/tailwind'; 2 | 3 | /** 4 | * Render attribution or verification pixels. 5 | * https://docs.buysellads.com/ad-serving-api#pixels 6 | */ 7 | export function AdPixels({ rawPixel }: { rawPixel: string }) { 8 | const pixels = rawPixel.split('||'); 9 | const time = String(Math.round(Date.now() / 1e4) | 0); 10 | 11 | return ( 12 |
13 | {pixels.map((pixel, index) => { 14 | return ( 15 | Ads tracking pixel 23 | ); 24 | })} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Ads/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Ad'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Announcement/Announcement.tsx: -------------------------------------------------------------------------------- 1 | import { resolveContentRef } from '@/lib/references'; 2 | import type { GitBookSiteContext } from '@v2/lib/context'; 3 | import { AnnouncementBanner } from './AnnouncementBanner'; 4 | 5 | /** 6 | * Server-side component to resolve content refs and pass down to client-side component 7 | */ 8 | export async function Announcement(props: { 9 | context: GitBookSiteContext; 10 | }) { 11 | const { context } = props; 12 | const { customization } = context; 13 | 14 | if ( 15 | !customization.announcement || 16 | !customization.announcement.enabled || 17 | !customization.announcement.message 18 | ) { 19 | return null; 20 | } 21 | 22 | const resolvedContentRef = customization.announcement?.link 23 | ? await resolveContentRef(customization.announcement?.link?.to, context) 24 | : null; 25 | 26 | return ( 27 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Announcement/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The local storage key for the announcement banner. 3 | */ 4 | export const ANNOUNCEMENT_STORAGE_KEY = '@gitbook/announcement'; 5 | /** 6 | * The CSS class to hide the announcement banner. Applies to the element. 7 | */ 8 | export const ANNOUNCEMENT_CSS_CLASS = 'announcement-hidden'; 9 | /** 10 | * The number of days until the announcement banner resets. 11 | */ 12 | export const ANNOUNCEMENT_DAYS_TILL_RESET = 7; 13 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Announcement/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Announcement'; 2 | export * from './AnnouncementDismissedScript'; 3 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/AutoRefreshContent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCheckForContentUpdate'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/AutoRefreshContent/server-actions.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { getChangeRequest } from '@/lib/api'; 4 | 5 | /** 6 | * Return true if a change-request has been updated. 7 | */ 8 | export async function hasContentBeenUpdated(props: { 9 | spaceId: string; 10 | changeRequestId: string; 11 | revisionId: string; 12 | }) { 13 | const changeRequest = await getChangeRequest.revalidate(props.spaceId, props.changeRequestId); 14 | if (!changeRequest) { 15 | return false; 16 | } 17 | return changeRequest.revision !== props.revisionId; 18 | } 19 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/AutoRefreshContent/useCheckForContentUpdate.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { hasContentBeenUpdated } from './server-actions'; 6 | 7 | /** 8 | * Return a callback to check if a change request has been updated and to refresh the page if it has. 9 | */ 10 | export function useCheckForContentUpdate(props: { 11 | spaceId: string; 12 | changeRequestId: string; 13 | revisionId: string; 14 | }) { 15 | const { spaceId, changeRequestId, revisionId } = props; 16 | 17 | return React.useCallback(async () => { 18 | const updated = await hasContentBeenUpdated({ spaceId, changeRequestId, revisionId }); 19 | 20 | if (updated) { 21 | window.location.reload(); 22 | } 23 | }, [spaceId, changeRequestId, revisionId]); 24 | } 25 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Cookies/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CookiesToast'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/Annotation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Annotation'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/CodeBlock/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeBlock'; 2 | export * from './PlainCodeBlock'; 3 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/Divider.tsx: -------------------------------------------------------------------------------- 1 | import type { DocumentBlockDivider } from '@gitbook/api'; 2 | 3 | import { tcls } from '@/lib/tailwind'; 4 | 5 | import type { BlockProps } from './Block'; 6 | 7 | export function Divider(props: BlockProps) { 8 | const { style } = props; 9 | 10 | return
; 11 | } 12 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/Drawing.tsx: -------------------------------------------------------------------------------- 1 | import type { DocumentBlockDrawing } from '@gitbook/api'; 2 | 3 | import { resolveContentRef } from '@/lib/references'; 4 | 5 | import { Image } from '../utils'; 6 | import type { BlockProps } from './Block'; 7 | import { Caption } from './Caption'; 8 | import { imageBlockSizes } from './Images'; 9 | 10 | export async function Drawing(props: BlockProps) { 11 | const { block, context } = props; 12 | 13 | const resolved = 14 | block.data.ref && context.contentContext 15 | ? await resolveContentRef(block.data.ref, context.contentContext) 16 | : null; 17 | if (!resolved) { 18 | return null; 19 | } 20 | 21 | return ( 22 | 23 | Drawing 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/Emoji.tsx: -------------------------------------------------------------------------------- 1 | import type { DocumentInlineEmoji } from '@gitbook/api'; 2 | 3 | import { Emoji as EmojiPrimitive } from '@/components/primitives'; 4 | 5 | import type { InlineProps } from './Inline'; 6 | 7 | export async function Emoji(props: InlineProps) { 8 | const { inline } = props; 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/Expandable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Expandable'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/FileIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@gitbook/icons'; 2 | 3 | import type { SimplifiedFileType } from '@/lib/files'; 4 | 5 | /** 6 | * Render an appropriate icon for a file. 7 | */ 8 | export function FileIcon(props: { contentType: SimplifiedFileType | null; className: string }) { 9 | const { contentType, className } = props; 10 | 11 | switch (contentType) { 12 | case 'pdf': 13 | return ; 14 | case 'image': 15 | return ; 16 | case 'archive': 17 | return ; 18 | default: 19 | return ; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/DocumentView/InlineButton.tsx: -------------------------------------------------------------------------------- 1 | import { resolveContentRef } from '@/lib/references'; 2 | import * as api from '@gitbook/api'; 3 | import { Button } from '../primitives'; 4 | import type { InlineProps } from './Inline'; 5 | 6 | export async function InlineButton(props: InlineProps) { 7 | const { inline, context } = props; 8 | 9 | if (!context.contentContext) { 10 | throw new Error('InlineButton requires a contentContext'); 11 | } 12 | 13 | const resolved = await resolveContentRef(inline.data.ref, context.contentContext); 14 | 15 | if (!resolved) { 16 | return null; 17 | } 18 | 19 | return ( 20 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PDF/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PDFRootLayout'; 2 | export * from './PDFPage'; 3 | export * from './urls'; 4 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PDF/pdf.css: -------------------------------------------------------------------------------- 1 | @page { 2 | size: A4; 3 | margin: 60pt 60pt; 4 | 5 | @bottom-right { 6 | content: counter(page); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageAside/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PageAside'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageBody/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PageBody'; 2 | export * from './PageCover'; 3 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageContext/PageContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | export type PageContextType = { 6 | pageId: string; 7 | spaceId: string; 8 | title: string; 9 | }; 10 | 11 | export const PageContext = React.createContext(null); 12 | 13 | /** 14 | * Client side context provider to pass information about the current page. 15 | */ 16 | export function PageContextProvider(props: PageContextType & { children: React.ReactNode }) { 17 | const { pageId, spaceId, title, children } = props; 18 | 19 | const value = React.useMemo(() => ({ pageId, spaceId, title }), [pageId, spaceId, title]); 20 | 21 | return {children}; 22 | } 23 | 24 | /** 25 | * Hook to use the page context. 26 | */ 27 | export function usePageContext() { 28 | const context = React.useContext(PageContext); 29 | if (!context) { 30 | throw new Error('usePageContext must be used within a PageContextProvider'); 31 | } 32 | return context; 33 | } 34 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageContext/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PageContext'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageFeedback/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PageFeedbackForm'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageIcon/PageIcon.tsx: -------------------------------------------------------------------------------- 1 | import type { RevisionPage } from '@gitbook/api'; 2 | import { Icon, type IconName } from '@gitbook/icons'; 3 | 4 | import { Emoji } from '@/components/primitives'; 5 | import { type ClassValue, tcls } from '@/lib/tailwind'; 6 | 7 | export function PageIcon(props: { page: RevisionPage; style?: ClassValue }) { 8 | const { page, style } = props; 9 | 10 | if (page.emoji) { 11 | return ( 12 | 21 | ); 22 | } 23 | 24 | if (page.icon) { 25 | return ; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/PageIcon/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PageIcon'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/RootLayout/ClientContexts.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type React from 'react'; 4 | 5 | import { TranslateContext } from '@/intl/client'; 6 | import type { TranslationLanguage } from '@/intl/translations'; 7 | 8 | export function ClientContexts(props: { 9 | language: TranslationLanguage; 10 | children: React.ReactNode; 11 | }) { 12 | const { children, language } = props; 13 | 14 | return {children}; 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/RootLayout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CustomizationRootLayout'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Search/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SearchButton'; 2 | export * from './SearchModal'; 3 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/Search/isQuestion.ts: -------------------------------------------------------------------------------- 1 | const questionWords = new Set([ 2 | 'who', 3 | 'what', 4 | 'where', 5 | 'when', 6 | 'why', 7 | 'how', 8 | 'explain', 9 | 'is', 10 | 'are', 11 | 'was', 12 | 'were', 13 | 'do', 14 | 'does', 15 | 'did', 16 | 'which', 17 | 'whom', 18 | 'whose', 19 | 'can', 20 | 'have', 21 | 'give', 22 | 'tell', 23 | 'show', 24 | 'find', 25 | ]); 26 | 27 | /** 28 | * Return true if an input query looks like a question. 29 | */ 30 | export function isQuestion(query: string): boolean { 31 | if (query.length > 25 || query.includes('?') || query.includes(' ')) { 32 | return true; 33 | } 34 | 35 | const words = query.toLowerCase().trim().split(/\s+/); 36 | 37 | if (words.length === 0) { 38 | return false; 39 | } 40 | 41 | for (const word of words) { 42 | if (questionWords.has(word)) { 43 | return true; 44 | } 45 | } 46 | 47 | return false; 48 | } 49 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SiteLayout/ClientContexts.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { CustomizationThemeMode } from '@gitbook/api'; 4 | import { ThemeProvider } from 'next-themes'; 5 | import type React from 'react'; 6 | 7 | export function ClientContexts(props: { 8 | nonce?: string; 9 | forcedTheme: CustomizationThemeMode | undefined; 10 | children: React.ReactNode; 11 | }) { 12 | const { children, forcedTheme } = props; 13 | 14 | /** 15 | * A bug in ThemeProvider is causing the nonce to be included incorrectly 16 | * on the client-side. Original issue: https://github.com/pacocoursey/next-themes/issues/218 17 | * 18 | * This is a workaround for it, until next-themes fixes it in their library. There is already 19 | * a PR: https://github.com/pacocoursey/next-themes/pull/223 20 | */ 21 | const nonce = typeof window === 'undefined' || !props.nonce ? props.nonce : ''; 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SiteLayout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SiteLayout'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SitePage/SitePageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import { SkeletonHeading, SkeletonParagraph } from '@/components/primitives'; 2 | import { tcls } from '@/lib/tailwind'; 3 | 4 | /** 5 | * Placeholder when loading a page. 6 | */ 7 | export function SitePageSkeleton() { 8 | return ( 9 |
22 |
23 | 24 | 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SitePage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SitePageNotFound'; 2 | export * from './SitePage'; 3 | export * from './fetch'; 4 | export * from './SitePageSkeleton'; 5 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SiteSections/SectionIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, type IconName } from '@gitbook/icons'; 2 | 3 | import { type ClassValue, tcls } from '@/lib/tailwind'; 4 | 5 | /** 6 | * Icon shown beside a section in the site section tabs. 7 | */ 8 | export function SectionIcon(props: { icon: IconName; isActive: boolean; className?: ClassValue }) { 9 | const { icon, isActive, className } = props; 10 | 11 | return ( 12 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SiteSections/index.ts: -------------------------------------------------------------------------------- 1 | export * from './encodeClientSiteSections'; 2 | export * from './SiteSectionList'; 3 | export * from './SiteSectionTabs'; 4 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SpaceLayout/SpaceLayoutContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | const SpaceLayoutContext = React.createContext({ 6 | basePath: '', 7 | }); 8 | 9 | /** 10 | * Provide the client context about the currently rendered space. 11 | */ 12 | export function SpaceLayoutContextProvider( 13 | props: React.PropsWithChildren<{ 14 | basePath: string; 15 | }> 16 | ) { 17 | const { basePath, children } = props; 18 | 19 | const value = React.useMemo(() => ({ basePath }), [basePath]); 20 | 21 | return {children}; 22 | } 23 | 24 | /** 25 | * Return the base path of the currently rendered space. 26 | */ 27 | export function useSpaceBasePath() { 28 | const context = React.useContext(SpaceLayoutContext); 29 | if (!context) { 30 | throw new Error('SpaceLayoutContext not found'); 31 | } 32 | return context.basePath; 33 | } 34 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/SpaceLayout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SpaceLayout'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/TableOfContents/index.ts: -------------------------------------------------------------------------------- 1 | export { TableOfContents } from './TableOfContents'; 2 | export { PagesList } from './PagesList'; 3 | export { TOCScrollContainer } from './TOCScroller'; 4 | export { Trademark } from './Trademark'; 5 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/ThemeToggler/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ThemeToggler'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useScrollActiveId'; 2 | export * from './useScrollPage'; 3 | export * from './useHash'; 4 | export * from './useIsMounted'; 5 | export * from './useToggleAnimation'; 6 | export * from './useCurrentPagePath'; 7 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/hooks/useCurrentPagePath.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useParams, useSelectedLayoutSegment } from 'next/navigation'; 4 | 5 | import { removeLeadingSlash } from '@/lib/paths'; 6 | import { useMemo } from 'react'; 7 | 8 | /** 9 | * Return the page of the current page being rendered. 10 | */ 11 | export function useCurrentPagePath() { 12 | // For V2, we use the params to get the page path. 13 | const params = useParams<{ pagePath?: string }>(); 14 | 15 | // For V1, we use the selected layout segment. 16 | const rawActiveSegment = useSelectedLayoutSegment() ?? ''; 17 | 18 | return useMemo(() => { 19 | if (typeof params.pagePath === 'string') { 20 | return removeLeadingSlash(decodeURIComponent(params.pagePath)); 21 | } 22 | 23 | return decodeURIComponent(rawActiveSegment); 24 | }, [params.pagePath, rawActiveSegment]); 25 | } 26 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/hooks/useHash.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useParams } from 'next/navigation'; 4 | import React from 'react'; 5 | 6 | function getHash(): string | null { 7 | if (typeof window === 'undefined') { 8 | return null; 9 | } 10 | return window.location.hash.slice(1); 11 | } 12 | 13 | /** 14 | * Hook to get the current hash from the URL. 15 | * @see https://github.com/vercel/next.js/discussions/49465 16 | */ 17 | export function useHash() { 18 | const params = useParams(); 19 | const [hash, setHash] = React.useState(getHash); 20 | React.useEffect(() => { 21 | setHash(getHash()); 22 | }, [params]); 23 | return hash; 24 | } 25 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/hooks/useIsMounted.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * Hook to check if a component is mounted. 5 | */ 6 | export function useIsMounted() { 7 | const [mounted, setMounted] = React.useState(false); 8 | 9 | React.useEffect(() => { 10 | setMounted(true); 11 | }, []); 12 | 13 | return mounted; 14 | } 15 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/layout.ts: -------------------------------------------------------------------------------- 1 | import type { ClassValue } from '@/lib/tailwind'; 2 | 3 | /** 4 | * Height of the desktop header. 5 | */ 6 | export const HEADER_HEIGHT_DESKTOP = 64 as const; 7 | 8 | /** 9 | * Style for the container to adapt between normal and full width. 10 | */ 11 | export const CONTAINER_STYLE: ClassValue = [ 12 | 'px-4', 13 | 'sm:px-6', 14 | 'md:px-8', 15 | 'max-w-screen-2xl', 16 | 'mx-auto', 17 | 'page-full-width:max-w-full', 18 | ]; 19 | 20 | /** 21 | * Height of the page cover. 22 | */ 23 | export const PAGE_COVER_HEIGHT: ClassValue = ['h-[240px]']; 24 | 25 | /** 26 | * Side column positioning, with and without a header. 27 | */ 28 | export const SIDE_COLUMN_WITH_HEADER: ClassValue = ['top-[64px]', 'h-[calc(100vh-64px)]']; 29 | export const SIDE_COLUMN_WITH_HEADER_AND_COVER: ClassValue = [ 30 | 'top-[304px]', 31 | 'h-[calc(100vh-304px)]', 32 | ]; 33 | export const SIDE_COLUMN_WITHOUT_HEADER: ClassValue = ['top-0', 'h-screen']; 34 | export const SIDE_COLUMN_WITHOUT_HEADER_AND_COVER: ClassValue = [ 35 | 'top-[240px]', 36 | 'h-[calc(100vh-240px)]', 37 | ]; 38 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/primitives/Emoji/Emoji.tsx: -------------------------------------------------------------------------------- 1 | import { getEmojiForCode } from '@/lib/emojis'; 2 | import { type ClassValue, tcls } from '@/lib/tailwind'; 3 | 4 | /** 5 | * Render an emoji by its codepoint. 6 | * It renders the UTF-8 character and use Emoji font defined in Tailwind CSS. 7 | */ 8 | export async function Emoji(props: { code: string; style?: ClassValue }) { 9 | const { code, style } = props; 10 | 11 | const fallback = getEmojiForCode(code); 12 | return {fallback}; 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/primitives/Emoji/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Emoji'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/primitives/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Checkbox'; 2 | export * from './Button'; 3 | export * from './Loading'; 4 | export * from './Card'; 5 | export * from './Skeleton'; 6 | export * from './Link'; 7 | export * from './StyledLink'; 8 | export * from './DateRelative'; 9 | export * from './Emoji'; 10 | export * from './LoadingPane'; 11 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/utils/ZoomImage.module.css: -------------------------------------------------------------------------------- 1 | html:has(.zoomModal) { 2 | overflow: hidden; 3 | } 4 | 5 | .zoomImg { 6 | cursor: zoom-in; 7 | } 8 | 9 | .zoomImageActive { 10 | view-transition-name: zoom-image; 11 | } 12 | 13 | .zoomModal { 14 | /** Unstyled */ 15 | } 16 | 17 | .zoomModal img { 18 | view-transition-name: zoom-image; 19 | cursor: zoom-out; 20 | } 21 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Image'; 2 | -------------------------------------------------------------------------------- /packages/gitbook/src/components/utils/types.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | 3 | export type AsProp = { as?: Tag }; 4 | 5 | export type PolymorphicComponentProp< 6 | Tag extends React.ElementType, 7 | Props = {}, 8 | > = React.PropsWithChildren> & 9 | Omit, keyof (AsProp & Props)>; 10 | 11 | export type PolymorphicComponentPropWithRef< 12 | Tag extends React.ElementType, 13 | Props = {}, 14 | > = PolymorphicComponentProp & { ref?: PolymorphicRef }; 15 | 16 | /* 17 | * Handle forwarding ref to a polymorphic component type. 18 | */ 19 | export type PolymorphicRef = React.ComponentPropsWithRef['ref']; 20 | -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Bold.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BoldItalic.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Book.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-BookItalic.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Light.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-LightItalic.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Medium.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-MediumItalic.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Regular.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.otf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-RegularItalic.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.ttf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.woff -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/ABCFavorit/ABCFavorit-Variable.woff2 -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/Inter/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/Inter/Inter-Bold.ttf -------------------------------------------------------------------------------- /packages/gitbook/src/fonts/Inter/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitbookIO/gitbook/6aa3ff9c5efa2ebd6dd1fb96683c4d5b4f8d2cd4/packages/gitbook/src/fonts/Inter/Inter-Regular.ttf -------------------------------------------------------------------------------- /packages/gitbook/src/intl/client.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { TranslationLanguage } from './translations'; 4 | 5 | export * from './translate'; 6 | 7 | export const TranslateContext = React.createContext(null); 8 | 9 | /** 10 | * Use the current language to translate a string. 11 | */ 12 | export function useLanguage(): TranslationLanguage { 13 | const language = React.useContext(TranslateContext); 14 | if (!language) { 15 | throw new Error('The hook useLanguage should be wrapped in a '); 16 | } 17 | return language; 18 | } 19 | -------------------------------------------------------------------------------- /packages/gitbook/src/intl/server.ts: -------------------------------------------------------------------------------- 1 | import type { SiteCustomizationSettings } from '@gitbook/api'; 2 | 3 | import { type TranslationLanguage, languages } from './translations'; 4 | 5 | export * from './translate'; 6 | 7 | export const DEFAULT_LOCALE = 'en'; 8 | 9 | /** 10 | * Get the locale of the customization. 11 | */ 12 | export function getCustomizationLocale(customization: SiteCustomizationSettings): string { 13 | return customization.internationalization.locale; 14 | } 15 | 16 | /** 17 | * Create the translation context for a space to use in the server components. 18 | */ 19 | export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage { 20 | const fallback = languages[DEFAULT_LOCALE]; 21 | 22 | const locale = getCustomizationLocale(customization); 23 | 24 | let language = fallback; 25 | // @ts-ignore 26 | if (locale !== DEFAULT_LOCALE && languages[locale]) { 27 | // @ts-ignore 28 | language = languages[locale]; 29 | } 30 | 31 | return { 32 | ...fallback, 33 | ...language, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /packages/gitbook/src/intl/translations/index.ts: -------------------------------------------------------------------------------- 1 | import type { CustomizationLocale } from '@gitbook/api'; 2 | 3 | import { de } from './de'; 4 | import { en } from './en'; 5 | import { es } from './es'; 6 | import { fr } from './fr'; 7 | import { ja } from './ja'; 8 | import { nl } from './nl'; 9 | import { no } from './no'; 10 | import { pt_br } from './pt-br'; 11 | import type { TranslationLanguage } from './types'; 12 | import { zh } from './zh'; 13 | 14 | export * from './types'; 15 | 16 | export const languages: { 17 | [key: string]: TranslationLanguage; 18 | } & { 19 | [locale in CustomizationLocale]: TranslationLanguage; 20 | } = { 21 | de, 22 | en, 23 | fr, 24 | es, 25 | zh, 26 | ja, 27 | nl, 28 | no, 29 | 'pt-br': pt_br, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/gitbook/src/intl/translations/types.ts: -------------------------------------------------------------------------------- 1 | import type { en } from './en'; 2 | 3 | export type TranslationKey = keyof typeof en; 4 | 5 | export type TranslationLanguage = { 6 | [key in TranslationKey]: string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/adaptive.ts: -------------------------------------------------------------------------------- 1 | import type { SiteAPIToken } from '@gitbook/api'; 2 | import type { SiteURLData } from '@v2/lib/context'; 3 | import { jwtDecode } from 'jwt-decode'; 4 | 5 | /** 6 | * Claims about the visitor, stored in the VA and auth token. 7 | */ 8 | export type VisitorAuthClaims = Record; 9 | 10 | /** 11 | * Get the visitor auth claims from the API response obtained from `getPublishedContentByUrl`. 12 | */ 13 | export function getVisitorAuthClaims(siteData: SiteURLData): VisitorAuthClaims { 14 | const { apiToken } = siteData; 15 | 16 | return getVisitorAuthClaimsFromToken(jwtDecode(apiToken)); 17 | } 18 | 19 | /** 20 | * Get the visitor auth claims from a decoded API token. 21 | */ 22 | export function getVisitorAuthClaimsFromToken(token: SiteAPIToken): VisitorAuthClaims { 23 | return token.claims ?? {}; 24 | } 25 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/app.ts: -------------------------------------------------------------------------------- 1 | import { GITBOOK_APP_URL } from '@v2/lib/env'; 2 | 3 | /** 4 | * Create an absolute href in the GitBook application. 5 | */ 6 | export function getGitBookAppHref(pathname: string): string { 7 | const appUrl = new URL(GITBOOK_APP_URL); 8 | appUrl.pathname = pathname; 9 | 10 | return appUrl.toString(); 11 | } 12 | 13 | /** 14 | * Sanitize a URL to be a valid GitBook.com app URL. 15 | */ 16 | export function sanitizeGitBookAppURL(input: string): string | null { 17 | if (!URL.canParse(input)) { 18 | return null; 19 | } 20 | 21 | const url = new URL(input); 22 | if (url.origin !== GITBOOK_APP_URL) { 23 | return null; 24 | } 25 | 26 | return url.toString(); 27 | } 28 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/arrays.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Partition an array into equal (+ rest items) parts. 3 | * Adapted from https://stackoverflow.com/a/68917937 4 | */ 5 | export function partition( 6 | /** 7 | * Array to split up 8 | */ 9 | array: any[], 10 | /** 11 | * Amount of parts you want to split into 12 | */ 13 | parts: number 14 | ) { 15 | const rest = array.length % parts; 16 | const size = Math.floor(array.length / parts); 17 | let j = 0; 18 | 19 | return Array.from({ length: Math.min(array.length, parts) }, (_, i) => 20 | array.slice(j, (j += size + (i < rest ? 1 : 0))) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/assets.ts: -------------------------------------------------------------------------------- 1 | import { GITBOOK_ASSETS_URL, GITBOOK_URL } from '@v2/lib/env'; 2 | import { joinPath, joinPathWithBaseURL } from './paths'; 3 | 4 | /** 5 | * Create a public URL for an asset. 6 | */ 7 | export function getAssetURL(path: string): string { 8 | return joinPathWithBaseURL( 9 | GITBOOK_ASSETS_URL || GITBOOK_URL, 10 | joinPath('~gitbook/static', path) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/build.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get a string representing the build version. 3 | */ 4 | export function buildVersion(): string { 5 | return process.env.BUILD_VERSION || '0.0.0'; 6 | } 7 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/cache/backends.ts: -------------------------------------------------------------------------------- 1 | import { cloudflareCache } from './cloudflare-cache'; 2 | import { cloudflareDOCache } from './cloudflare-do'; 3 | import { memoryCache } from './memory'; 4 | 5 | export const cacheBackends = [ 6 | // Cache local to the process 7 | // (can't be globally purged or shared between processes) 8 | memoryCache, 9 | // Cache local to the datacenter 10 | // It can't be purged globally but it's faster 11 | cloudflareCache, 12 | // Global cache with slower performances 13 | cloudflareDOCache, 14 | ]; 15 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/cache/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cache'; 2 | export * from './http'; 3 | export * from './revalidateTags'; 4 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/cache/utils.ts: -------------------------------------------------------------------------------- 1 | import type { CacheEntryMeta } from './types'; 2 | 3 | /** 4 | * For mutable entries, we limit the cache to 30 seconds 5 | * as it could be invalidated at any time. 6 | */ 7 | export const NON_IMMUTABLE_LOCAL_CACHE_MAX_AGE_SECONDS = 30; 8 | 9 | /** 10 | * Get the max-age in seconds for a cache entry. 11 | */ 12 | export function getCacheMaxAge(meta: CacheEntryMeta, min?: number, max?: number): number { 13 | let maxAge = Math.max(min ?? 0, Math.round((meta.expiresAt - Date.now()) / 1000)); 14 | 15 | if (max) { 16 | maxAge = Math.min(max, maxAge); 17 | } 18 | 19 | return maxAge; 20 | } 21 | 22 | /** 23 | * Return true if a cache entry can be considered immutable. 24 | */ 25 | export function isCacheEntryImmutable(meta: CacheEntryMeta): boolean { 26 | return !meta.tag; 27 | } 28 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/csp.ts: -------------------------------------------------------------------------------- 1 | import { GITBOOK_ASSETS_URL } from '@v2/lib/env'; 2 | 3 | /** 4 | * Return the Content Security Policy for the current environment. 5 | */ 6 | export function getContentSecurityPolicy(): string { 7 | const csp = ` 8 | default-src 'self' *; 9 | script-src 'self' 'unsafe-inline' 'unsafe-eval' *; 10 | style-src 'self' 'unsafe-inline' *; 11 | img-src * 'self' blob: data:; 12 | connect-src *; 13 | font-src *; 14 | frame-src *; 15 | object-src 'none'; 16 | base-uri 'self' ${GITBOOK_ASSETS_URL}; 17 | form-action 'self' ${GITBOOK_ASSETS_URL} *; 18 | frame-ancestors https: ${process.env.NODE_ENV !== 'production' ? 'http:' : ''}; 19 | `; 20 | 21 | return csp 22 | .replace(/\n/g, ' ') 23 | .replace(/\s{2,}/g, ' ') 24 | .trim(); 25 | } 26 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/customization.ts: -------------------------------------------------------------------------------- 1 | import type { SiteCustomizationSettings } from '@gitbook/api'; 2 | import { MiddlewareHeaders } from '@v2/lib/middleware'; 3 | import { headers } from 'next/headers'; 4 | import rison from 'rison'; 5 | 6 | /** 7 | * Selects the customization settings from the x-gitbook-customization header if present, 8 | * otherwise returns the original API-provided settings. 9 | */ 10 | export async function getDynamicCustomizationSettings( 11 | settings: SiteCustomizationSettings 12 | ): Promise { 13 | const headersList = await headers(); 14 | const extend = headersList.get(MiddlewareHeaders.Customization); 15 | if (extend) { 16 | try { 17 | const parsedSettings = rison.decode_object(extend); 18 | 19 | return parsedSettings; 20 | } catch (_error) {} 21 | } 22 | 23 | return settings; 24 | } 25 | 26 | /** 27 | * Validate that the customization settings passed are valid. 28 | */ 29 | export function validateSerializedCustomization(raw: string): boolean { 30 | try { 31 | rison.decode_object(raw); 32 | return true; 33 | } catch { 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/emojis.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | 3 | import { getEmojiForCode } from './emojis'; 4 | 5 | describe('getEmojiForCode', () => { 6 | it('should return the emoji character for the given emoji code', () => { 7 | expect(getEmojiForCode('1f600')).toBe('😀'); 8 | }); 9 | 10 | it('should handle complex codes', () => { 11 | expect(getEmojiForCode('1f935-2642')).toEqual('🤵‍♂️'); 12 | expect(getEmojiForCode('1f3c3-2642')).toEqual('🏃‍♂️'); 13 | expect(getEmojiForCode('1f468-2696')).toEqual('👨‍⚖️'); 14 | }); 15 | 16 | it('should return an empty string if invalid', () => { 17 | expect(getEmojiForCode('invalid')).toEqual(''); 18 | }); 19 | 20 | it('should handle one with uppercase chars', () => { 21 | expect(getEmojiForCode('1f4A5')).toEqual('💥'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/emojis.ts: -------------------------------------------------------------------------------- 1 | import { emojiCodepoints } from '@gitbook/emoji-codepoints'; 2 | 3 | /** 4 | * Returns the emoji character for the given emoji code. 5 | */ 6 | export function getEmojiForCode(code: string): string { 7 | if (!code) { 8 | return ''; 9 | } 10 | 11 | code = code.toLowerCase(); 12 | const fullCode = emojiCodepoints[code] ?? code; 13 | 14 | const codePoints = fullCode.split('-').map((elt) => Number.parseInt(elt, 16)); 15 | 16 | try { 17 | return String.fromCodePoint(...codePoints); 18 | } catch { 19 | return ''; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/files.ts: -------------------------------------------------------------------------------- 1 | export type SimplifiedFileType = 'image' | 'pdf' | 'archive'; 2 | 3 | /** 4 | * Get a simplified content type for the given mime type. 5 | */ 6 | export function getSimplifiedContentType(mimeType: string): SimplifiedFileType | null { 7 | if (mimeType.startsWith('image')) { 8 | return 'image'; 9 | } 10 | 11 | switch (mimeType) { 12 | case 'application/pdf': 13 | case 'application/x-pdf': 14 | return 'pdf'; 15 | case 'application/zip': 16 | case 'application/x-7z-compressed': 17 | case 'application/x-zip-compressed': 18 | case 'application/x-tar': 19 | case 'application/x-rar-compressed': 20 | case 'application/vnd.rar': 21 | return 'archive'; 22 | default: 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/local-storage.ts: -------------------------------------------------------------------------------- 1 | import { checkIsSecurityError } from './security-error'; 2 | 3 | /** 4 | * Get an item from local storage safely. 5 | */ 6 | export function getItem(key: string, defaultValue: T): T { 7 | try { 8 | if (typeof localStorage !== 'undefined' && localStorage && 'getItem' in localStorage) { 9 | const stored = localStorage.getItem(key); 10 | return stored ? (JSON.parse(stored) as T) : defaultValue; 11 | } 12 | return defaultValue; 13 | } catch (error) { 14 | if (checkIsSecurityError(error)) { 15 | return defaultValue; 16 | } 17 | throw error; 18 | } 19 | } 20 | 21 | /** 22 | * Set an item in local storage safely. 23 | */ 24 | export function setItem(key: string, value: unknown) { 25 | try { 26 | if (typeof localStorage !== 'undefined' && localStorage && 'setItem' in localStorage) { 27 | localStorage.setItem(key, JSON.stringify(value)); 28 | } 29 | } catch (error) { 30 | if (checkIsSecurityError(error)) { 31 | return; 32 | } 33 | throw error; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/markdown.ts: -------------------------------------------------------------------------------- 1 | import rehypeSanitize from 'rehype-sanitize'; 2 | import rehypeStringify from 'rehype-stringify'; 3 | import remarkGfm from 'remark-gfm'; 4 | import remarkParse from 'remark-parse'; 5 | import remarkRehype from 'remark-rehype'; 6 | import { unified } from 'unified'; 7 | 8 | /** 9 | * Parse markdown and output HTML. 10 | */ 11 | export async function parseMarkdown(markdown: string): Promise { 12 | const promise = unified() 13 | .use(remarkParse) 14 | .use(remarkGfm) 15 | .use(remarkRehype) 16 | .use(rehypeSanitize) 17 | .use(rehypeStringify) 18 | .process(markdown) 19 | .then((file) => file.toString()); 20 | 21 | return promise; 22 | } 23 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/openapi/enrich.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { parseOpenAPI } from '@gitbook/openapi-parser'; 3 | 4 | import { enrichFilesystem } from './enrich'; 5 | 6 | const spec = await Bun.file(new URL('./fixtures/multiline-spec.yaml', import.meta.url)).text(); 7 | 8 | describe('#enrichFilesystem', () => { 9 | it('supports multiline descriptions', async () => { 10 | const filesystem = await parseOpenAPI({ 11 | value: spec, 12 | rootURL: null, 13 | }); 14 | const enriched = await enrichFilesystem(filesystem); 15 | expect(enriched[0].specification.paths['/pet'].put['x-gitbook-description-html']).toBe( 16 | '

Social platform

' 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/paths.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { getExtension } from './paths'; 3 | 4 | describe('getExtension', () => { 5 | it('should return the extension of a path', () => { 6 | expect(getExtension('test.txt')).toBe('.txt'); 7 | }); 8 | 9 | it('should return an empty string if there is no extension', () => { 10 | expect(getExtension('test/path/to/file')).toBe(''); 11 | }); 12 | 13 | it('should return the extension of a path with multiple dots', () => { 14 | expect(getExtension('test.with.multiple.dots.txt')).toBe('.txt'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/proxy.ts: -------------------------------------------------------------------------------- 1 | import type { PublishedSiteContent } from '@gitbook/api'; 2 | import { joinPath, removeTrailingSlash, withLeadingSlash } from './paths'; 3 | 4 | /** 5 | * Compute the final base path for a site served in proxy mode. 6 | * For e.g. if the input URL is `https://example.com/docs/v2/foo/bar` on which 7 | * the site is served at `https://example.com/docs` and the resolved base path is `/v2` 8 | * then the proxy site path would be `/docs/v2/`. 9 | */ 10 | export function getProxyModeBasePath( 11 | input: URL, 12 | resolved: Pick 13 | ): string { 14 | const inputPathname = new URL(input).pathname; 15 | const proxySitePath = inputPathname 16 | .replace(removeTrailingSlash(resolved.pathname), '') 17 | .replace(removeTrailingSlash(resolved.basePath), ''); 18 | 19 | const result = joinPath(withLeadingSlash(proxySitePath), resolved.basePath); 20 | return result.endsWith('/') ? result : `${result}/`; 21 | } 22 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/security-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if the error is a security error returned by the browser when cookies or local storage are blocked. 3 | */ 4 | export function checkIsSecurityError(error: unknown): error is Error { 5 | return ( 6 | error instanceof Error && 7 | // Safari 8 | (error.name === 'SecurityError' || 9 | // Firefox 10 | error.name === 'NS_ERROR_FAILURE' || 11 | error.name === 'NS_ERROR_ABORT') 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/tailwind.ts: -------------------------------------------------------------------------------- 1 | import { type ClassNameValue, twMerge } from 'tailwind-merge'; 2 | 3 | export type { ClassNameValue as ClassValue }; 4 | 5 | /** 6 | * Create a tailwind className for a component. 7 | */ 8 | export function tcls(...values: ClassNameValue[]): string { 9 | return twMerge(...values); 10 | } 11 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/tracking.ts: -------------------------------------------------------------------------------- 1 | import { headers } from 'next/headers'; 2 | 3 | /** 4 | * Return true if events should be tracked on the site. 5 | */ 6 | export async function shouldTrackEvents(): Promise { 7 | const headersList = await headers(); 8 | 9 | if ( 10 | process.env.NODE_ENV === 'development' || 11 | (process.env.GITBOOK_BLOCK_PAGE_VIEWS_TRACKING && 12 | !headersList.has('x-gitbook-track-page-views')) 13 | ) { 14 | return false; 15 | } 16 | 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/typescript.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter function to exclude `null` values 3 | */ 4 | export function filterOutNullable(value: T): value is NonNullable { 5 | return !!value; 6 | } 7 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/urls.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if a URL is an HTTP URL. 3 | */ 4 | export function checkIsHttpURL(input: string | URL): boolean { 5 | if (!URL.canParse(input)) { 6 | return false; 7 | } 8 | const parsed = new URL(input); 9 | return parsed.protocol === 'http:' || parsed.protocol === 'https:'; 10 | } 11 | -------------------------------------------------------------------------------- /packages/gitbook/src/lib/v2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the code is running in v2. 3 | */ 4 | export function isV2() { 5 | return process.env.GITBOOK_V2 === 'true'; 6 | } 7 | 8 | /** 9 | * Assert that the code is not running in v2. 10 | */ 11 | export function assertIsNotV2() { 12 | if (isV2()) { 13 | throw new Error('This code is not available in V2'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/gitbook/src/routes/markdownPage.ts: -------------------------------------------------------------------------------- 1 | import { resolvePagePath } from '@/lib/pages'; 2 | import { RevisionPageType } from '@gitbook/api'; 3 | import type { GitBookSiteContext } from '@v2/lib/context'; 4 | import { throwIfDataError } from '@v2/lib/data'; 5 | 6 | /** 7 | * Generate a markdown version of a page. 8 | */ 9 | export async function servePageMarkdown(context: GitBookSiteContext, pagePath: string) { 10 | const pageLookup = resolvePagePath(context.pages, pagePath); 11 | if (!pageLookup) { 12 | return new Response(`Page "${pagePath}" not found`, { status: 404 }); 13 | } 14 | 15 | const { page } = pageLookup; 16 | 17 | if (page.type !== RevisionPageType.Document) { 18 | return new Response(`Page "${pagePath}" is not a document`, { status: 404 }); 19 | } 20 | 21 | const markdown = await throwIfDataError( 22 | context.dataFetcher.getRevisionPageMarkdown({ 23 | spaceId: context.space.id, 24 | revisionId: context.revisionId, 25 | pageId: page.id, 26 | }) 27 | ); 28 | 29 | return new Response(markdown, { 30 | headers: { 31 | 'Content-Type': 'text/markdown; charset=utf-8', 32 | }, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/gitbook/src/routes/robots.ts: -------------------------------------------------------------------------------- 1 | import { isSiteIndexable } from '@/lib/seo'; 2 | import { type GitBookSiteContext, checkIsRootSiteContext } from '@v2/lib/context'; 3 | 4 | /** 5 | * Generate a robots.txt for a site. 6 | */ 7 | export async function serveRobotsTxt(context: GitBookSiteContext) { 8 | const { linker } = context; 9 | 10 | const isRoot = checkIsRootSiteContext(context); 11 | const isIndexable = await isSiteIndexable(context); 12 | 13 | const lines = isIndexable 14 | ? [ 15 | 'User-agent: *', 16 | // Allow image resizing and icon generation routes for favicons and search results 17 | 'Allow: /~gitbook/image?*', 18 | 'Allow: /~gitbook/icon?*', 19 | // Disallow other dynamic routes / search queries 20 | 'Disallow: /*?', 21 | 'Allow: /', 22 | `Sitemap: ${linker.toAbsoluteURL(linker.toPathInSpace(isRoot ? '/sitemap.xml' : '/sitemap-pages.xml'))}`, 23 | ] 24 | : ['User-agent: *', 'Disallow: /']; 25 | 26 | const robotsTxt = lines.join('\n'); 27 | return new Response(robotsTxt, { 28 | headers: { 29 | 'Content-Type': 'text/plain', 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/gitbook/tests/pagespeed-testing.ts: -------------------------------------------------------------------------------- 1 | import psi from 'psi'; 2 | 3 | import { getContentTestURL } from './utils'; 4 | 5 | interface Test { 6 | url: string; 7 | strategy: 'mobile' | 'desktop'; 8 | threshold: number; 9 | } 10 | 11 | // We use low values for now to avoid failing the tests 12 | // and to be able to see the results, and only catch major regressions. 13 | const tests: Array = [ 14 | { 15 | url: 'https://gitbook.com/docs', 16 | strategy: 'desktop', 17 | threshold: 60, 18 | }, 19 | 20 | { 21 | url: 'https://gitbook.com/docs', 22 | strategy: 'mobile', 23 | threshold: 30, 24 | }, 25 | ]; 26 | 27 | for (const test of tests) { 28 | const url = getContentTestURL(test.url); 29 | await psi.output(url, { 30 | strategy: test.strategy, 31 | threshold: test.threshold, 32 | key: process.env.PAGESPEED_API_KEY, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/gitbook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"], 23 | "@v2/*": ["../gitbook-v2/src/*"] 24 | }, 25 | "types": [ 26 | "bun-types" // add Bun global 27 | ] 28 | }, 29 | "include": ["next-env.d.ts", "cf-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 30 | "exclude": [ 31 | "node_modules", 32 | "packages/openapi-parser", 33 | "packages/react-openapi", 34 | "packages/react-math" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/gitbook/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "generate": { 5 | "outputs": ["public/~gitbook/static/icons/**/*", "public/~gitbook/static/math/**/*"] 6 | }, 7 | "typecheck": { 8 | "dependsOn": ["^typecheck"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/gitbook/types/content-security-policy-merger.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'content-security-policy-merger' { 2 | export function merge(...policies: string[]): string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/gitbook/types/gitbook-integrations-global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | type GitBookIntegrationEvent = 'load' | 'unload'; 3 | 4 | type GitBookIntegrationEventCallback = (...args: any[]) => void; 5 | 6 | interface Window { 7 | /** 8 | * Global `window.GitBook` object accessible by integrations. 9 | */ 10 | GitBook?: { 11 | events: Map; 12 | addEventListener: ( 13 | type: GitBookIntegrationEvent, 14 | func: GitBookIntegrationEventCallback 15 | ) => void; 16 | removeEventListener: ( 17 | type: GitBookIntegrationEvent, 18 | func: GitBookIntegrationEventCallback 19 | ) => void; 20 | }; 21 | } 22 | } 23 | 24 | export {}; 25 | -------------------------------------------------------------------------------- /packages/gitbook/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Needed because of https://github.com/oven-sh/bun/issues/358 2 | 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /packages/gitbook/types/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const value: { 3 | src: string; 4 | width: number; 5 | height: number; 6 | }; 7 | export default value; 8 | } 9 | 10 | declare module '*.png' { 11 | const value: { 12 | src: string; 13 | width: number; 14 | height: number; 15 | }; 16 | export default value; 17 | } 18 | 19 | declare module '*.jpg' { 20 | const value: { 21 | src: string; 22 | width: number; 23 | height: number; 24 | }; 25 | export default value; 26 | } 27 | -------------------------------------------------------------------------------- /packages/gitbook/types/memoizee.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'memoizee' { 2 | declare function memoizee any>( 3 | f: F, 4 | options?: { normalizer?: (args: any[]) => string } 5 | ): F; 6 | 7 | export = memoizee; 8 | } 9 | 10 | declare module 'memoizee/weak' { 11 | declare function memoizee any>(f: F): F; 12 | 13 | export = memoizee; 14 | } 15 | -------------------------------------------------------------------------------- /packages/icons/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | src/data/*.json 3 | -------------------------------------------------------------------------------- /packages/icons/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/icons 2 | 3 | ## 0.2.0 4 | 5 | ### Minor Changes 6 | 7 | - 88f64b0: Fix the build to support Node.js v20 8 | 9 | ## 0.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 5c35f36: Initial release for `@gitbook/icons`, a package of content icons to be used for pages, documents, etc. 14 | - 776bc31: Fix bin gitbook-icons when installed without the Pro token 15 | 16 | ### Patch Changes 17 | 18 | - d0f4860: Update version for static assets to fix invalid caching 19 | - ef9d012: Fix first run of the package in development mode 20 | -------------------------------------------------------------------------------- /packages/icons/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/icons` 2 | 3 | Icon sets useed in GitBook content. 4 | 5 | ## Usage 6 | 7 | ```tsx 8 | import { Icon } from '@gitbook/icons'; 9 | 10 | ; 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/icons/bin/kit.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import url from 'node:url'; 3 | 4 | /** 5 | * Get the path to the kit, depending on the Pro or Free version. 6 | */ 7 | export function getKitPath() { 8 | let source = path.dirname( 9 | url.fileURLToPath(import.meta.resolve('@fortawesome/fontawesome-free/package.json')) 10 | ); 11 | try { 12 | source = path.resolve( 13 | path.dirname( 14 | url.fileURLToPath(import.meta.resolve('@gitbook/fontawesome-pro/package.json')) 15 | ), 16 | 'icons' 17 | ); 18 | } catch (_error) {} 19 | 20 | return source; 21 | } 22 | -------------------------------------------------------------------------------- /packages/icons/src/getIconStyle.ts: -------------------------------------------------------------------------------- 1 | import stylesMap from './data/styles-map.json' with { type: 'json' }; 2 | import type { IconName, IconStyle } from './types'; 3 | 4 | const cache = new Map>(); 5 | 6 | /** 7 | * Return the style to load an icon from by its name. 8 | * Some icons are only available for certain styles. 9 | */ 10 | export function getIconStyle(style: IconStyle, icon: IconName): [string, IconName] { 11 | const cached = cache.get(icon)?.get(style); 12 | 13 | if (cached) { 14 | return cached; 15 | } 16 | 17 | // Check for exceptions 18 | let result = [style, icon] as [string, IconName]; 19 | 20 | for (const [onlyStyle, icons] of Object.entries(stylesMap)) { 21 | if (icons.includes(icon)) { 22 | result = [onlyStyle, icon]; 23 | break; 24 | } 25 | } 26 | 27 | if (!cache.has(icon)) { 28 | cache.set(icon, new Map()); 29 | } 30 | cache.get(icon)?.set(style, result); 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /packages/icons/src/icons.ts: -------------------------------------------------------------------------------- 1 | import rawIcons from './data/icons.json' with { type: 'json' }; 2 | import type { IconName, IconStyle } from './types'; 3 | 4 | export interface IconStyleDefinition { 5 | title: string; 6 | style: IconStyle; 7 | } 8 | 9 | export interface IconDefinition { 10 | icon: IconName; 11 | label: string; 12 | search?: string[]; 13 | } 14 | 15 | /** 16 | * List of all icons available in the library. 17 | */ 18 | // @ts-ignore 19 | export const icons: IconDefinition[] = rawIcons; 20 | 21 | let iconNamesSet: Set | null = null; 22 | 23 | /** 24 | * Validate that the icon name is valid. 25 | */ 26 | export function validateIconName(icon: IconName | string): icon is IconName { 27 | if (!iconNamesSet) { 28 | iconNamesSet = new Set(icons.map((icon) => icon.icon)); 29 | } 30 | 31 | return iconNamesSet.has(icon); 32 | } 33 | -------------------------------------------------------------------------------- /packages/icons/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Icon'; 2 | export * from './types'; 3 | export * from './IconsProvider'; 4 | -------------------------------------------------------------------------------- /packages/icons/src/pro.test.ts: -------------------------------------------------------------------------------- 1 | import { it } from 'bun:test'; 2 | import { validateIconName } from './icons'; 3 | 4 | it('should have the GitBook custom icon', () => { 5 | if (!validateIconName('gitbook')) { 6 | const message = 7 | 'The GitBook icon is missing. It indicates that the dependencies were installed without the correct font-awesome package. These changes have probably been persisted in the Bun lockfile. Read the README for more information.'; 8 | 9 | if (process.env.CI) { 10 | throw new Error(message); 11 | } 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /packages/icons/src/style.css: -------------------------------------------------------------------------------- 1 | /* Normal */ 2 | svg.gb-icon { 3 | background: currentColor; 4 | } 5 | 6 | /* Sprite */ 7 | svg.gb-icon-s path, 8 | svg.gb-icon-s use { 9 | fill: currentColor; 10 | stroke: currentColor; 11 | } 12 | -------------------------------------------------------------------------------- /packages/icons/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { IconName as BasicIconName } from '@fortawesome/fontawesome-svg-core'; 2 | 3 | export enum IconStyle { 4 | // Regular 5 | Regular = 'regular', 6 | SharpRegular = 'sharp-regular', 7 | // Solid 8 | Solid = 'solid', 9 | SharpSolid = 'sharp-solid', 10 | // Duotone 11 | Duotone = 'duotone', 12 | SharpDuotoneSolid = 'sharp-duotone-solid', 13 | // Light 14 | Light = 'light', 15 | SharpLight = 'sharp-light', 16 | // Thin 17 | Thin = 'thin', 18 | SharpThin = 'sharp-thin', 19 | } 20 | 21 | type CustomIconName = 'gitbook'; 22 | 23 | export type IconName = BasicIconName | CustomIconName; 24 | -------------------------------------------------------------------------------- /packages/icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2023"], 4 | "module": "ESNext", 5 | "target": "es2022", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "bundler", 10 | "allowJs": true, 11 | "noEmit": false, 12 | "declaration": true, 13 | "outDir": "dist", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true, 18 | "types": [ 19 | "bun-types" // add Bun global 20 | ] 21 | }, 22 | "include": ["src/**/*.ts", "src/**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/icons/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "generate": { 5 | "inputs": ["bin/**/*", "package.json"], 6 | "outputs": ["src/data/*.json", "dist/data/*.json"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/openapi-parser/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/openapi-parser/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/openapi-parser` 2 | 3 | Modern OpenAPI parser written in TypeScript with support for OpenAPI 3.1, OpenAPI 3.0 and Swagger 2.0. -------------------------------------------------------------------------------- /packages/openapi-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/openapi-parser", 3 | "description": "Modern OpenAPI parser written in TypeScript with support for OpenAPI 3.1, OpenAPI 3.0 and Swagger 2.0.", 4 | "type": "module", 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "development": "./src/index.ts", 9 | "default": "./dist/index.js" 10 | } 11 | }, 12 | "version": "2.1.4", 13 | "sideEffects": false, 14 | "dependencies": { 15 | "@scalar/openapi-parser": "^0.10.10", 16 | "@scalar/openapi-types": "^0.1.9" 17 | }, 18 | "devDependencies": { 19 | "@tsconfig/strictest": "^2.0.5", 20 | "@tsconfig/node20": "^20.1.4", 21 | "@types/swagger2openapi": "^7.0.4", 22 | "bun-types": "^1.1.20", 23 | "typescript": "^5.5.3" 24 | }, 25 | "scripts": { 26 | "build": "tsc --project tsconfig.build.json", 27 | "typecheck": "tsc --noEmit", 28 | "unit": "bun test", 29 | "dev": "bun run build -- --watch", 30 | "clean": "rm -rf ./dist" 31 | }, 32 | "files": ["dist", "src", "README.md", "CHANGELOG.md"] 33 | } 34 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/error.ts: -------------------------------------------------------------------------------- 1 | import type { ErrorObject } from '@scalar/openapi-parser'; 2 | 3 | type OpenAPIParseErrorCode = 4 | | 'invalid' 5 | | 'parse-v2-in-v3' 6 | | 'v2-conversion' 7 | | 'dereference' 8 | | 'yaml-parse'; 9 | 10 | /** 11 | * Error thrown when the OpenAPI document is invalid. 12 | */ 13 | export class OpenAPIParseError extends Error { 14 | public override name = 'OpenAPIParseError'; 15 | public code: OpenAPIParseErrorCode; 16 | public rootURL: string | null; 17 | public errors: ErrorObject[] | undefined; 18 | constructor( 19 | message: string, 20 | options: { 21 | code: OpenAPIParseErrorCode; 22 | rootURL?: string | null; 23 | cause?: Error; 24 | errors?: ErrorObject[] | undefined; 25 | } 26 | ) { 27 | super(message, { cause: options.cause }); 28 | this.code = options.code; 29 | this.rootURL = options.rootURL ?? null; 30 | this.errors = options.errors; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/filesystem.ts: -------------------------------------------------------------------------------- 1 | import { load } from '@scalar/openapi-parser'; 2 | import type { ParseOpenAPIInput } from './parse'; 3 | import { fetchURLs } from './scalar-plugins/fetchURLs'; 4 | import type { Filesystem } from './types'; 5 | 6 | /** 7 | * Create a filesystem from an OpenAPI document. 8 | * Fetches all the URLs specified in references and builds a filesystem. 9 | */ 10 | export async function createFileSystem( 11 | input: Pick 12 | ): Promise { 13 | const { value, rootURL, options } = input; 14 | 15 | const { filesystem } = await load(value, { 16 | plugins: [fetchURLs({ rootURL }), ...(options?.plugins || [])], 17 | }); 18 | 19 | return filesystem; 20 | } 21 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/fixtures/remote-ref/root/invalid.txt: -------------------------------------------------------------------------------- 1 | { "invalid" <> } -------------------------------------------------------------------------------- /packages/openapi-parser/src/fixtures/remote-ref/root/user.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | User: 4 | x-swagger-router-model: io.swagger.petstore.model.User 5 | properties: 6 | id: 7 | type: integer 8 | format: int64 9 | example: 10 10 | username: 11 | type: string 12 | example: theUser 13 | firstName: 14 | type: string 15 | example: John 16 | lastName: 17 | type: string 18 | example: James 19 | email: 20 | type: string 21 | example: john@email.com 22 | password: 23 | type: string 24 | example: 12345 25 | phone: 26 | type: string 27 | example: 12345 28 | userStatus: 29 | type: integer 30 | format: int32 31 | example: 1 32 | description: User Status 33 | xml: 34 | name: user 35 | type: object 36 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/fixtures/remote-ref/tag.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | Tag: 4 | x-swagger-router-model: io.swagger.petstore.model.Tag 5 | properties: 6 | id: 7 | type: integer 8 | format: int64 9 | name: 10 | type: string 11 | xml: 12 | name: tag 13 | type: object 14 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/helpers/shouldIgnoreEntity.ts: -------------------------------------------------------------------------------- 1 | import type { OpenAPIV3 } from '@scalar/openapi-types'; 2 | 3 | /** 4 | * Check if an entity should be ignored 5 | */ 6 | export function shouldIgnoreEntity( 7 | data: undefined | OpenAPIV3.TagObject | OpenAPIV3.OperationObject 8 | ) { 9 | return data?.['x-internal'] === true || data?.['x-gitbook-ignore'] === true; 10 | } 11 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | export { parseOpenAPI } from './parse'; 2 | 3 | export { dereference } from '@scalar/openapi-parser'; 4 | export type { AnyObject } from '@scalar/openapi-parser'; 5 | export type * from '@scalar/openapi-types'; 6 | export * from './error'; 7 | export * from './helpers/shouldIgnoreEntity'; 8 | export * from './traverse'; 9 | export * from './schemas'; 10 | export type * from './types'; 11 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'bun:test'; 2 | import { parseOpenAPI } from './parse'; 3 | 4 | const spec = await Bun.file(new URL('./fixtures/recursive-spec.json', import.meta.url)).text(); 5 | 6 | describe('#parseOpenAPI', () => { 7 | it('parses an OpenAPI document', async () => { 8 | const schema = await parseOpenAPI({ 9 | value: spec, 10 | rootURL: null, 11 | }); 12 | // Ensure the structure returned is not recursive (not dereferenced). 13 | JSON.stringify(schema); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/schemas.ts: -------------------------------------------------------------------------------- 1 | import type { OpenAPIV3, OpenAPIV3_1 } from '@scalar/openapi-types'; 2 | import { shouldIgnoreEntity } from './helpers/shouldIgnoreEntity'; 3 | import type { OpenAPISchema } from './types'; 4 | 5 | /** 6 | * Extract selected schemas from the OpenAPI document. 7 | */ 8 | export function filterSelectedOpenAPISchemas( 9 | schema: OpenAPIV3.Document | OpenAPIV3_1.Document, 10 | selectedSchemas: string[] 11 | ): OpenAPISchema[] { 12 | const componentsSchemas = schema.components?.schemas ?? {}; 13 | 14 | // Preserve the order of the selected schemas 15 | return selectedSchemas 16 | .map((name) => { 17 | const schema = componentsSchemas[name]; 18 | if (schema && !shouldIgnoreEntity(schema)) { 19 | return { 20 | name, 21 | schema, 22 | }; 23 | } 24 | return null; 25 | }) 26 | .filter((schema): schema is OpenAPISchema => !!schema); 27 | } 28 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/v2.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'bun:test'; 2 | import { convertOpenAPIV2ToOpenAPIV3 } from './v2'; 3 | 4 | const specV2 = await Bun.file(new URL('./fixtures/spec-v2.json', import.meta.url)).text(); 5 | 6 | describe('#convertOpenAPIV2ToOpenAPIV3', () => { 7 | it('converts an OpenAPIV2 in V3', async () => { 8 | const schema = await convertOpenAPIV2ToOpenAPIV3({ 9 | value: specV2, 10 | rootURL: null, 11 | }); 12 | // Ensure the structure returned is not recursive (not dereferenced). 13 | JSON.stringify(schema); 14 | expect(schema[0]?.specification.openapi).toBe('3.1.1'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/openapi-parser/src/v2.ts: -------------------------------------------------------------------------------- 1 | import { upgrade } from '@scalar/openapi-parser'; 2 | import { OpenAPIParseError } from './error'; 3 | import type { ParseOpenAPIInput } from './parse'; 4 | import type { Filesystem, OpenAPIV3xDocument } from './types'; 5 | import { parseOpenAPIV3 } from './v3'; 6 | 7 | /** 8 | * Convert a Swagger 2.0 schema to an OpenAPI 3.0 schema. 9 | */ 10 | export async function convertOpenAPIV2ToOpenAPIV3( 11 | input: ParseOpenAPIInput 12 | ): Promise> { 13 | const { value, rootURL } = input; 14 | 15 | const upgradeResult = (() => { 16 | try { 17 | return upgrade(value); 18 | } catch (error) { 19 | if (error instanceof Error) { 20 | throw new OpenAPIParseError('Failed to convert Swagger 2.0 to OpenAPI 3.1.1', { 21 | code: 'v2-conversion', 22 | rootURL, 23 | cause: error, 24 | }); 25 | } 26 | 27 | throw error; 28 | } 29 | })(); 30 | 31 | return parseOpenAPIV3({ ...input, rootURL, value: upgradeResult.specification }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/openapi-parser/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": ["./tsconfig.json"], 4 | "exclude": ["**/*.test.ts"], 5 | "compilerOptions": { 6 | "declaration": true, 7 | "noEmit": false, 8 | "outDir": "dist" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/openapi-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": ["@tsconfig/strictest/tsconfig.json", "@tsconfig/node20/tsconfig.json"], 4 | "compilerOptions": { 5 | "lib": ["ESNext", "DOM"], 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "isolatedModules": true, 9 | "incremental": true, 10 | "noEmit": true, 11 | "noPropertyAccessFromIndexSignature": false, 12 | "types": [ 13 | "bun-types" // add Bun global 14 | ] 15 | }, 16 | "include": ["src/**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-contentkit/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/react-contentkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/react-contentkit", 3 | "version": "0.7.0", 4 | "exports": { 5 | ".": { 6 | "types": "./dist/index.d.ts", 7 | "development": "./src/index.ts", 8 | "default": "./dist/index.js" 9 | } 10 | }, 11 | "dependencies": { 12 | "classnames": "^2.5.1", 13 | "@gitbook/api": "^0.118.0", 14 | "@gitbook/icons": "workspace:*" 15 | }, 16 | "peerDependencies": { 17 | "react": "*" 18 | }, 19 | "devDependencies": { 20 | "typescript": "^5.5.3" 21 | }, 22 | "scripts": { 23 | "build": "tsc", 24 | "typecheck": "tsc --noEmit", 25 | "dev": "tsc -w", 26 | "clean": "rm -rf ./dist" 27 | }, 28 | "files": ["dist", "src", "README.md", "CHANGELOG.md"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ContentKitOutput.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitRenderOutput } from '@gitbook/api'; 2 | 3 | import { Element } from './Element'; 4 | import type { ContentKitServerContext } from './types'; 5 | 6 | /** 7 | * Generic component to render a ContentKit output. 8 | * The component can be used both as a client and server one. 9 | */ 10 | export function ContentKitOutput(props: { 11 | context: ContentKitServerContext; 12 | output: ContentKitRenderOutput; 13 | }) { 14 | const { output, context } = props; 15 | 16 | if (output.type === 'complete') { 17 | return null; 18 | } 19 | 20 | return ( 21 | <> 22 | {process.env.NODE_ENV === 'development' ? ( 23 |
{JSON.stringify(props.output, null, 2)}
24 | ) : null} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementBlock.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitBlock } from '@gitbook/api'; 2 | import type React from 'react'; 3 | 4 | import type { ContentKitServerElementProps } from './types'; 5 | 6 | export function ElementBlock( 7 | props: React.PropsWithChildren> 8 | ) { 9 | const { children } = props; 10 | 11 | return <>{children}; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementBox.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitBox } from '@gitbook/api'; 2 | import type React from 'react'; 3 | 4 | import type { ContentKitServerElementProps } from './types'; 5 | 6 | export function ElementBox( 7 | props: React.PropsWithChildren> 8 | ) { 9 | const { element, children } = props; 10 | 11 | return
{children}
; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementCodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitCodeBlock } from '@gitbook/api'; 2 | 3 | import { DefaultCodeBlock, ElementCodeBlockClient } from './ElementCodeBlockClient'; 4 | import { resolveDynamicBinding } from './dynamic'; 5 | import type { ContentKitServerElementProps } from './types'; 6 | 7 | export function ElementCodeBlock(props: ContentKitServerElementProps) { 8 | const { element, context, state } = props; 9 | 10 | const C = context.codeBlock ?? DefaultCodeBlock; 11 | const initialCode = resolveDynamicBinding(state, element.content); 12 | 13 | return ( 14 | 15 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementCodeBlockClient.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitCodeBlock } from '@gitbook/api'; 2 | import type React from 'react'; 3 | 4 | import type { ContentKitClientElementProps } from './types'; 5 | 6 | export function ElementCodeBlockClient( 7 | props: ContentKitClientElementProps & { 8 | /** 9 | * Render a code block on the server as a fallback and swap only when needed. 10 | */ 11 | children: React.ReactNode; 12 | } 13 | ) { 14 | const { children } = props; 15 | 16 | return <>{children}; 17 | } 18 | 19 | export function DefaultCodeBlock(props: { 20 | code: string; 21 | syntax: string; 22 | lineNumbers: number | boolean; 23 | }) { 24 | return ( 25 |
26 |             {props.code}
27 |         
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementDivider.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitDivider } from '@gitbook/api'; 2 | 3 | import classNames from 'classnames'; 4 | import type { ContentKitServerElementProps } from './types'; 5 | 6 | export function ElementDivider(props: ContentKitServerElementProps) { 7 | const { element } = props; 8 | 9 | return ( 10 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementIcon.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitIcon } from '@gitbook/api'; 2 | import type { ContentKitServerContext } from './types'; 3 | 4 | export function ElementIcon(props: { icon: ContentKitIcon; context: ContentKitServerContext }) { 5 | const { icon, context } = props; 6 | const C = context.icons[icon]; 7 | if (!C) { 8 | return ; 9 | } 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementImage.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitImage } from '@gitbook/api'; 2 | 3 | import type { ContentKitServerElementProps } from './types'; 4 | 5 | export function ElementImage(props: ContentKitServerElementProps) { 6 | const { element } = props; 7 | 8 | // TODO: do block display with aspect ratio 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementMarkdown.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitMarkdown } from '@gitbook/api'; 2 | 3 | import { ElementMarkdownClient } from './ElementMarkdownClient'; 4 | import { resolveDynamicBinding } from './dynamic'; 5 | import type { ContentKitServerElementProps } from './types'; 6 | 7 | export function ElementMarkdown(props: ContentKitServerElementProps) { 8 | const { element, context, state } = props; 9 | const Markdown = context.markdown; 10 | 11 | const initialMarkdown = resolveDynamicBinding(state, element.content); 12 | 13 | async function renderMarkdown(markdown: string) { 14 | 'use server'; 15 | return ; 16 | } 17 | 18 | return ( 19 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementStack.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitHStack, ContentKitVStack } from '@gitbook/api'; 2 | import type React from 'react'; 3 | 4 | import classNames from 'classnames'; 5 | import type { ContentKitServerElementProps } from './types'; 6 | 7 | export function ElementStack( 8 | props: React.PropsWithChildren< 9 | ContentKitServerElementProps 10 | > 11 | ) { 12 | const { element, children } = props; 13 | 14 | return ( 15 |
22 | {children} 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/ElementText.tsx: -------------------------------------------------------------------------------- 1 | import type { ContentKitText } from '@gitbook/api'; 2 | import type React from 'react'; 3 | 4 | import classNames from 'classnames'; 5 | import type { ContentKitServerElementProps } from './types'; 6 | 7 | export function ElementText( 8 | props: React.PropsWithChildren> 9 | ) { 10 | const { element, children } = props; 11 | 12 | return ( 13 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-contentkit/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ContentKit'; 2 | export * from './ContentKitOutput'; 3 | export type { ContentKitServerContext } from './types'; 4 | -------------------------------------------------------------------------------- /packages/react-contentkit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": false, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-math/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/react-math/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gitbook/react-math 2 | 3 | ## 0.6.0 4 | 5 | ### Minor Changes 6 | 7 | - 5c35f36: Export binary `gitbook-math` to copy assets to a directory 8 | 9 | ## 0.5.0 10 | 11 | ### Minor Changes 12 | 13 | - 3445db4: Fix files published in the NPM packages by defining "files" in "package.json" 14 | 15 | ## 0.4.0 16 | 17 | ### Minor Changes 18 | 19 | - 24cd72e: Fix changeset CI workflow 20 | 21 | ## 0.3.0 22 | 23 | ### Minor Changes 24 | 25 | - de747b7: Refactor the repository to be a proper monorepo and publish JS files on NPM instead of TypeScript files. 26 | 27 | ## 0.2.0 28 | 29 | ### Minor Changes 30 | 31 | - 57adb3e: Second release to fix publishing with changeset 32 | 33 | ## 0.1.0 34 | 35 | ### Minor Changes 36 | 37 | - 5f8a8fe: Initial release 38 | -------------------------------------------------------------------------------- /packages/react-math/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/react-math` 2 | 3 | React component to render a Math formula. It uses KaTeX when possible and fallbacks to MathJaX if needed. 4 | -------------------------------------------------------------------------------- /packages/react-math/bin/gitbook-math.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fs from 'node:fs/promises'; 3 | import path from 'node:path'; 4 | import url from 'node:url'; 5 | 6 | /** 7 | * Scripts to copy the assets to a public folder. 8 | */ 9 | async function main() { 10 | const outputFolder = path.resolve(process.cwd(), process.argv[2] ?? 'public/math'); 11 | const source = path.dirname(url.fileURLToPath(import.meta.resolve('mathjax/package.json'))); 12 | 13 | const packageJson = JSON.parse(await fs.readFile(path.join(source, 'package.json'), 'utf8')); 14 | 15 | // Create the output folder if it doesn't exist 16 | await fs.mkdir(outputFolder, { recursive: true }); 17 | 18 | // Copy the assets 19 | await Promise.all([ 20 | fs.cp(path.join(source, 'es5'), path.join(outputFolder, `mathjax@${packageJson.version}`), { 21 | recursive: true, 22 | }), 23 | ]); 24 | } 25 | 26 | main().catch(console.error); 27 | -------------------------------------------------------------------------------- /packages/react-math/css/default.css: -------------------------------------------------------------------------------- 1 | /** Hide the KaTeX HTML output, while it's loading */ 2 | body:not(.katex-loaded) .katex-html { 3 | display: none; 4 | } 5 | 6 | /** Align the MathJax output with the font-size used by KaTeX */ 7 | mjx-container[jax="CHTML"] { 8 | font-size: 1.21em; 9 | } 10 | -------------------------------------------------------------------------------- /packages/react-math/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/react-math", 3 | "type": "module", 4 | "exports": { 5 | ".": { 6 | "types": "./dist/index.d.ts", 7 | "development": "./src/index.ts", 8 | "default": "./dist/index.js" 9 | } 10 | }, 11 | "version": "0.6.0", 12 | "dependencies": { 13 | "object-hash": "^3.0.0" 14 | }, 15 | "peerDependencies": { 16 | "react": "*" 17 | }, 18 | "devDependencies": { 19 | "typescript": "^5.5.3", 20 | "@types/katex": "^0.16.5" 21 | }, 22 | "scripts": { 23 | "build": "tsc", 24 | "typecheck": "tsc --noEmit", 25 | "dev": "tsc -w", 26 | "clean": "rm -rf ./dist" 27 | }, 28 | "bin": { 29 | "gitbook-math": "./bin/gitbook-math.js" 30 | }, 31 | "files": ["dist", "src", "css", "README.md", "CHANGELOG.md"] 32 | } 33 | -------------------------------------------------------------------------------- /packages/react-math/src/KaTeX.tsx: -------------------------------------------------------------------------------- 1 | import katex from 'katex'; 2 | import React from 'react'; 3 | 4 | const KaTeXCSS = React.lazy(() => import('./KaTeXCSS')); 5 | 6 | /** 7 | * Server component to compile the KaTeX formula to HTML. 8 | */ 9 | export function KaTeX(props: { 10 | formula: string; 11 | inline: boolean; 12 | className?: string; 13 | fallback?: React.ReactNode; 14 | }) { 15 | const { formula, inline, className } = props; 16 | 17 | try { 18 | const html = katex.renderToString(formula, { 19 | displayMode: !inline, 20 | output: 'htmlAndMathml', 21 | throwOnError: true, 22 | strict: false, 23 | }); 24 | 25 | const Tag = inline ? 'span' : 'div'; 26 | return ( 27 | <> 28 | 29 | 30 | 31 | ); 32 | } catch (_error) { 33 | return <>{props.fallback}; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-math/src/KaTeXCSS.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | /** 4 | * Dummy component to lazy load the KaTeX CSS. 5 | */ 6 | export default function KaTeXCSS() { 7 | // Load the CSS as soon as possible (this is why we don't use an effect hook here) 8 | // We lazy load the CSS to avoidNext bundling it in the main bundle 9 | loadCSS(); 10 | 11 | return null; 12 | } 13 | 14 | let loaded = false; 15 | 16 | function loadCSS() { 17 | if (loaded || typeof window === 'undefined') { 18 | return; 19 | } 20 | 21 | loaded = true; 22 | 23 | // @ts-ignore 24 | import('katex/dist/katex.min.css').then(() => { 25 | document.body.classList.add('katex-loaded'); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-math/src/MathJaXLazy.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import type { MathJaXFormulaProps } from './MathJaX'; 5 | 6 | const MathJaXFormula = React.lazy(() => import('./MathJaX')); 7 | 8 | /** 9 | * Lazy component that loads MathJax and renders the formula. 10 | */ 11 | export function MathJaXLazy(props: MathJaXFormulaProps) { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-math/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MathFormula'; 2 | -------------------------------------------------------------------------------- /packages/react-math/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": false, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-openapi/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/react-openapi/README.md: -------------------------------------------------------------------------------- 1 | # `@gitbook/react-openapi` 2 | 3 | Style-less React components to render OpenAPI operation blocks. 4 | -------------------------------------------------------------------------------- /packages/react-openapi/src/Markdown.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | export function Markdown(props: { source: string; className?: string }) { 4 | const { source, className } = props; 5 | 6 | return ( 7 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-openapi/src/OpenAPIOperationDescription.tsx: -------------------------------------------------------------------------------- 1 | import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser'; 2 | import { Markdown } from './Markdown'; 3 | import type { OpenAPIContext } from './context'; 4 | import { resolveDescription } from './utils'; 5 | 6 | /** 7 | * Display the description of an OpenAPI operation. 8 | */ 9 | export function OpenAPIOperationDescription(props: { 10 | operation: OpenAPIV3.OperationObject; 11 | context: OpenAPIContext; 12 | }) { 13 | const { operation } = props; 14 | if (operation['x-gitbook-description-document']) { 15 | return ( 16 |
17 | {props.context.renderDocument({ 18 | document: operation['x-gitbook-description-document'], 19 | })} 20 |
21 | ); 22 | } 23 | 24 | const description = resolveDescription(operation); 25 | if (!description) { 26 | return null; 27 | } 28 | 29 | return ( 30 |
31 | 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-openapi/src/OpenAPISchemaServer.tsx: -------------------------------------------------------------------------------- 1 | import type { OpenAPIV3 } from '@gitbook/openapi-parser'; 2 | import { 3 | OpenAPIRootSchemaFromServer, 4 | OpenAPISchemaPropertiesFromServer, 5 | type OpenAPISchemaPropertyEntry, 6 | } from './OpenAPISchema'; 7 | import type { OpenAPIClientContext } from './context'; 8 | import { decycle } from './decycle'; 9 | 10 | export function OpenAPISchemaProperties(props: { 11 | id?: string; 12 | properties: OpenAPISchemaPropertyEntry[]; 13 | context: OpenAPIClientContext; 14 | }) { 15 | return ( 16 | 21 | ); 22 | } 23 | 24 | export function OpenAPIRootSchema(props: { 25 | schema: OpenAPIV3.SchemaObject; 26 | context: OpenAPIClientContext; 27 | }) { 28 | return ( 29 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-openapi/src/__snapshots__/json2xml.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Bun Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getUrlFromServerState indents correctly 1`] = ` 4 | " 5 | 10 6 | doggie 7 | 8 | 1 9 | Dogs 10 | 11 | string 12 | 13 | 0 14 | string 15 | 16 | available 17 | " 18 | `; 19 | -------------------------------------------------------------------------------- /packages/react-openapi/src/common/OpenAPIOperationDescription.tsx: -------------------------------------------------------------------------------- 1 | import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser'; 2 | import { Markdown } from '../Markdown'; 3 | import type { OpenAPIContext } from '../context'; 4 | import { resolveDescription } from '../utils'; 5 | 6 | export function OpenAPIOperationDescription(props: { 7 | operation: OpenAPIV3.OperationObject; 8 | context: OpenAPIContext; 9 | }) { 10 | const { operation } = props; 11 | if (operation['x-gitbook-description-document']) { 12 | return ( 13 |
14 | {props.context.renderDocument({ 15 | document: operation['x-gitbook-description-document'], 16 | })} 17 |
18 | ); 19 | } 20 | 21 | const description = resolveDescription(operation); 22 | if (!description) { 23 | return null; 24 | } 25 | 26 | return ( 27 |
28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-openapi/src/common/OpenAPIStability.tsx: -------------------------------------------------------------------------------- 1 | import type { OpenAPIStability as OpenAPIStabilityType } from '@gitbook/openapi-parser'; 2 | 3 | const stabilityEnum: Record = { 4 | experimental: 'Experimental', 5 | alpha: 'Alpha', 6 | beta: 'Beta', 7 | } as const; 8 | 9 | export function OpenAPIStability(props: { stability: OpenAPIStabilityType }) { 10 | const { stability } = props; 11 | 12 | const foundStability = stabilityEnum[stability]; 13 | 14 | if (!foundStability) { 15 | return null; 16 | } 17 | 18 | return ( 19 |
20 | {foundStability} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-openapi/src/dereference.ts: -------------------------------------------------------------------------------- 1 | import { type Filesystem, type OpenAPIV3xDocument, dereference } from '@gitbook/openapi-parser'; 2 | 3 | const dereferenceCache = new WeakMap>(); 4 | 5 | /** 6 | * Memoized version of `dereferenceSchema`. 7 | */ 8 | export function dereferenceFilesystem(filesystem: Filesystem): Promise { 9 | if (dereferenceCache.has(filesystem)) { 10 | return dereferenceCache.get(filesystem) as Promise; 11 | } 12 | 13 | const promise = baseDereferenceFilesystem(filesystem); 14 | dereferenceCache.set(filesystem, promise); 15 | return promise; 16 | } 17 | 18 | /** 19 | * Dereference an OpenAPI schema. 20 | */ 21 | async function baseDereferenceFilesystem(filesystem: Filesystem): Promise { 22 | const result = await dereference(filesystem); 23 | 24 | if (!result.schema) { 25 | throw new Error('Failed to dereference OpenAPI document'); 26 | } 27 | 28 | return result.schema as OpenAPIV3xDocument; 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-openapi/src/getDisclosureLabel.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { OpenAPIV3 } from '@gitbook/openapi-parser'; 4 | import type { OpenAPIClientContext } from './context'; 5 | import { tString } from './translate'; 6 | 7 | export function getDisclosureLabel(props: { 8 | schema: OpenAPIV3.SchemaObject; 9 | isExpanded: boolean; 10 | context: OpenAPIClientContext; 11 | }) { 12 | const { schema, isExpanded, context } = props; 13 | let label: string; 14 | if (schema.type === 'array' && !!schema.items) { 15 | if (schema.items.oneOf) { 16 | label = tString(context.translation, 'available_items').toLowerCase(); 17 | } else { 18 | label = tString(context.translation, 'properties').toLowerCase(); 19 | } 20 | } else { 21 | label = tString(context.translation, 'properties').toLowerCase(); 22 | } 23 | 24 | return tString(context.translation, isExpanded ? 'hide' : 'show', label); 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-openapi/src/getOrCreateStoreByKey.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'zustand'; 2 | 3 | type Key = string | number; 4 | 5 | type State = { 6 | key: Key | null; 7 | }; 8 | 9 | type Actions = { setKey: (key: Key | null) => void }; 10 | 11 | export type Store = State & Actions; 12 | 13 | const createStateStore = (initial?: Key) => { 14 | return createStore()((set) => ({ 15 | key: initial ?? null, 16 | setKey: (key) => { 17 | set(() => ({ key })); 18 | }, 19 | })); 20 | }; 21 | 22 | const defaultStores = new Map>(); 23 | 24 | const createStateStoreFactory = (stores: typeof defaultStores) => { 25 | return (storeKey: string, initialKey?: Key) => { 26 | if (!stores.has(storeKey)) { 27 | stores.set(storeKey, createStateStore(initialKey)); 28 | } 29 | return stores.get(storeKey)!; 30 | }; 31 | }; 32 | 33 | export const getOrCreateStoreByKey = createStateStoreFactory(defaultStores); 34 | -------------------------------------------------------------------------------- /packages/react-openapi/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schemas'; 2 | export * from './OpenAPIOperation'; 3 | export * from './OpenAPIWebhook'; 4 | export * from './OpenAPIOperationContext'; 5 | export * from './resolveOpenAPIOperation'; 6 | export * from './resolveOpenAPIWebhook'; 7 | export type { OpenAPIOperationData, OpenAPIWebhookData } from './types'; 8 | export type { OpenAPIContextInput } from './context'; 9 | export { checkIsValidLocale } from './translations'; 10 | -------------------------------------------------------------------------------- /packages/react-openapi/src/json2xml.ts: -------------------------------------------------------------------------------- 1 | import { jsXml } from 'json-xml-parse'; 2 | 3 | /** 4 | * This function converts an object to XML. 5 | */ 6 | export function json2xml(data: Record) { 7 | return jsXml.toXmlString(data, { beautify: true }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-openapi/src/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OpenAPISchemas'; 2 | export * from './resolveOpenAPISchemas'; 3 | -------------------------------------------------------------------------------- /packages/react-openapi/src/schemas/resolveOpenAPISchemas.ts: -------------------------------------------------------------------------------- 1 | import type { Filesystem, OpenAPISchema, OpenAPIV3xDocument } from '@gitbook/openapi-parser'; 2 | import { filterSelectedOpenAPISchemas } from '@gitbook/openapi-parser'; 3 | import { dereferenceFilesystem } from '../dereference'; 4 | 5 | /** 6 | * Resolve an OpenAPI schemas from a file and compile it to a more usable format. 7 | * Schemas are extracted from the OpenAPI components.schemas 8 | */ 9 | export async function resolveOpenAPISchemas( 10 | filesystem: Filesystem, 11 | options: { 12 | schemas: string[]; 13 | } 14 | ): Promise<{ 15 | schemas: OpenAPISchema[]; 16 | } | null> { 17 | const { schemas: selectedSchemas } = options; 18 | 19 | const schema = await dereferenceFilesystem(filesystem); 20 | 21 | const schemas = filterSelectedOpenAPISchemas(schema, selectedSchemas); 22 | 23 | if (schemas.length === 0) { 24 | return null; 25 | } 26 | 27 | return { schemas }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-openapi/src/stringifyOpenAPI.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Stringify an OpenAPI object. Same API as JSON.stringify. 3 | */ 4 | export function stringifyOpenAPI( 5 | value: any, 6 | replacer?: ((this: any, key: string, value: any) => any) | null, 7 | space?: string | number 8 | ): string { 9 | return JSON.stringify( 10 | value, 11 | (key, value) => { 12 | // Ignore internal keys 13 | if (key.startsWith('x-gitbook-')) { 14 | return undefined; 15 | } 16 | 17 | if (replacer) { 18 | return replacer(key, value); 19 | } 20 | 21 | return value; 22 | }, 23 | space 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-openapi/src/translations/index.ts: -------------------------------------------------------------------------------- 1 | import { de } from './de'; 2 | import { en } from './en'; 3 | import { es } from './es'; 4 | import { fr } from './fr'; 5 | import { ja } from './ja'; 6 | import { nl } from './nl'; 7 | import { no } from './no'; 8 | import { pt_br } from './pt-br'; 9 | import type { Translation } from './types'; 10 | import { zh } from './zh'; 11 | 12 | export * from './types'; 13 | 14 | export const translations = { 15 | en, 16 | de, 17 | es, 18 | fr, 19 | ja, 20 | nl, 21 | no, 22 | 'pt-br': pt_br, 23 | zh, 24 | } satisfies Record; 25 | 26 | export type TranslationLocale = keyof typeof translations; 27 | 28 | /** 29 | * Check if the locale is valid. 30 | */ 31 | export function checkIsValidLocale(locale: string): locale is TranslationLocale { 32 | return Object.prototype.hasOwnProperty.call(translations, locale); 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-openapi/src/translations/ja.ts: -------------------------------------------------------------------------------- 1 | export const ja = { 2 | required: '必須', 3 | deprecated: '非推奨', 4 | deprecated_and_sunset_on: 'この操作は非推奨であり、${1}に廃止されます。', 5 | stability_experimental: '実験的', 6 | stability_alpha: 'アルファ', 7 | stability_beta: 'ベータ', 8 | copy_to_clipboard: 'クリップボードにコピー', 9 | copied: 'コピー済み', 10 | no_content: 'コンテンツなし', 11 | unresolved_reference: '未解決の参照', 12 | circular_reference: '循環参照', 13 | read_only: '読み取り専用', 14 | write_only: '書き込み専用', 15 | optional: 'オプション', 16 | min: '最小', 17 | max: '最大', 18 | nullable: 'ヌル許容', 19 | body: '本文', 20 | payload: 'ペイロード', 21 | headers: 'ヘッダー', 22 | header: 'ヘッダー', 23 | authorizations: '認可', 24 | responses: 'レスポンス', 25 | response: 'レスポンス', 26 | path_parameters: 'パスパラメータ', 27 | query_parameters: 'クエリパラメータ', 28 | header_parameters: 'ヘッダーパラメータ', 29 | attributes: '属性', 30 | test_it: 'テストする', 31 | information: '情報', 32 | success: '成功', 33 | redirect: 'リダイレクト', 34 | error: 'エラー', 35 | show: '${1}を表示', 36 | hide: '${1}を非表示', 37 | available_items: '利用可能なアイテム', 38 | properties: 'プロパティ', 39 | or: 'または', 40 | and: 'かつ', 41 | possible_values: '可能な値', 42 | }; 43 | -------------------------------------------------------------------------------- /packages/react-openapi/src/translations/types.ts: -------------------------------------------------------------------------------- 1 | import type { en } from './en'; 2 | 3 | export type TranslationKey = keyof typeof en; 4 | 5 | export type Translation = { 6 | [key in TranslationKey]: string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/react-openapi/src/translations/zh.ts: -------------------------------------------------------------------------------- 1 | export const zh = { 2 | required: '必填', 3 | deprecated: '已弃用', 4 | deprecated_and_sunset_on: '此操作已弃用,将于 ${1} 停止使用。', 5 | stability_experimental: '实验性', 6 | stability_alpha: 'Alpha', 7 | stability_beta: 'Beta', 8 | copy_to_clipboard: '复制到剪贴板', 9 | copied: '已复制', 10 | no_content: '无内容', 11 | unresolved_reference: '未解析的引用', 12 | circular_reference: '循环引用', 13 | read_only: '只读', 14 | write_only: '只写', 15 | optional: '可选', 16 | min: '最小值', 17 | max: '最大值', 18 | nullable: '可为 null', 19 | body: '请求体', 20 | payload: '有效载荷', 21 | headers: '头字段', 22 | header: '头部', 23 | authorizations: '授权', 24 | responses: '响应', 25 | response: '响应', 26 | path_parameters: '路径参数', 27 | query_parameters: '查询参数', 28 | header_parameters: '头参数', 29 | attributes: '属性', 30 | test_it: '测试一下', 31 | information: '信息', 32 | success: '成功', 33 | redirect: '重定向', 34 | error: '错误', 35 | show: '显示${1}', 36 | hide: '隐藏${1}', 37 | available_items: '可用项', 38 | properties: '属性', 39 | or: '或', 40 | and: '和', 41 | possible_values: '可能的值', 42 | }; 43 | -------------------------------------------------------------------------------- /packages/react-openapi/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | OpenAPICustomOperationProperties, 3 | OpenAPICustomSpecProperties, 4 | OpenAPIV3, 5 | } from '@gitbook/openapi-parser'; 6 | 7 | export type OpenAPISecurityWithRequired = OpenAPIV3.SecuritySchemeObject & { required?: boolean }; 8 | 9 | export interface OpenAPIOperationData extends OpenAPICustomSpecProperties { 10 | path: string; 11 | method: string; 12 | 13 | /** Servers to be used for this operation */ 14 | servers: OpenAPIV3.ServerObject[]; 15 | 16 | /** Spec of the operation */ 17 | operation: OpenAPIV3.OperationObject; 18 | 19 | /** Securities that should be used for this operation */ 20 | securities: [string, OpenAPISecurityWithRequired][]; 21 | } 22 | 23 | export interface OpenAPIWebhookData extends OpenAPICustomSpecProperties { 24 | name: string; 25 | method: string; 26 | 27 | /** Servers to be used for this operation */ 28 | servers: OpenAPIV3.ServerObject[]; 29 | 30 | /** Spec of the webhook */ 31 | operation: OpenAPIV3.OperationObject; 32 | } 33 | -------------------------------------------------------------------------------- /packages/react-openapi/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": ["./tsconfig.json"], 4 | "exclude": ["**/*.test.ts"], 5 | "compilerOptions": { 6 | "declaration": true, 7 | "noEmit": false, 8 | "outDir": "dist" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-openapi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "allowUnusedLabels": false, 9 | "allowUnreachableCode": false, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitOverride": true, 12 | "noImplicitReturns": true, 13 | "noPropertyAccessFromIndexSignature": false, 14 | "noUncheckedIndexedAccess": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "checkJs": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noEmit": true, 20 | "incremental": true, 21 | "esModuleInterop": true, 22 | "module": "ESNext", 23 | "moduleResolution": "Bundler", 24 | "resolveJsonModule": true, 25 | "isolatedModules": true, 26 | "jsx": "preserve", 27 | "types": [ 28 | "bun-types" // add Bun global 29 | ], 30 | "outDir": "dist" 31 | }, 32 | "include": ["src/**/*.ts", "src/**/*.tsx"] 33 | } 34 | -------------------------------------------------------------------------------- /patches/@vercel%2Fnext@4.4.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/index.js b/dist/index.js 2 | index 74c72085defa714ebfbeaf7c63b7956f6848da82..0d056888100804bc5442cf7378652b6886ecd357 100644 3 | --- a/dist/index.js 4 | +++ b/dist/index.js 5 | @@ -10473,7 +10473,7 @@ var import_path = require("path"); 6 | // src/constants.ts 7 | var KIB = 1024; 8 | var MIB = 1024 * KIB; 9 | -var EDGE_FUNCTION_SIZE_LIMIT = 4 * MIB; 10 | +var EDGE_FUNCTION_SIZE_LIMIT = 10 * MIB; 11 | var DEFAULT_MAX_UNCOMPRESSED_LAMBDA_SIZE = 250 * MIB; 12 | var LAMBDA_RESERVED_UNCOMPRESSED_SIZE = 25 * MIB; 13 | 14 | -------------------------------------------------------------------------------- /patches/decode-named-character-reference@1.0.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index 5fef2811aa86f3f1f8228daef7d867863e71db72..7d4f6b5f3824f87dda262a28970bc91d6b37fd13 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -34,7 +34,6 @@ 6 | "deno": "./index.js", 7 | "react-native": "./index.js", 8 | "worker": "./index.js", 9 | - "browser": "./index.dom.js", 10 | "default": "./index.js" 11 | } 12 | }, 13 | --------------------------------------------------------------------------------