{t(language, 'page_last_modified',
diff --git a/.changeset/afraid-gifts-sparkle.md b/.changeset/afraid-gifts-sparkle.md new file mode 100644 index 0000000000..750393d4bb --- /dev/null +++ b/.changeset/afraid-gifts-sparkle.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix UX issue about highlighting the search term in search result sections diff --git a/.changeset/big-gorillas-perform.md b/.changeset/big-gorillas-perform.md new file mode 100644 index 0000000000..b9f06847ab --- /dev/null +++ b/.changeset/big-gorillas-perform.md @@ -0,0 +1,9 @@ +--- +gitbook: patch +--- + +Fix three small visual issues + +- Fix sidebar showing on `no-toc` pages in the gradient theme +- Fix variant selector truncating incorrectly in header when sections are present +- Fix page cover alignment on `lg` screens without TOC diff --git a/.changeset/breezy-falcons-drop.md b/.changeset/breezy-falcons-drop.md new file mode 100644 index 0000000000..fbb4669b39 --- /dev/null +++ b/.changeset/breezy-falcons-drop.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Respect fullWidth and defaultWidth for images diff --git a/.changeset/bright-avocados-film.md b/.changeset/bright-avocados-film.md new file mode 100644 index 0000000000..20a16ac2fd --- /dev/null +++ b/.changeset/bright-avocados-film.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix hash with align in columns diff --git a/.changeset/brown-ducks-think.md b/.changeset/brown-ducks-think.md new file mode 100644 index 0000000000..72dbd108cd --- /dev/null +++ b/.changeset/brown-ducks-think.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Hide scrollbar on sections diff --git a/.changeset/clever-jokes-yell.md b/.changeset/clever-jokes-yell.md new file mode 100644 index 0000000000..7732753b9b --- /dev/null +++ b/.changeset/clever-jokes-yell.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Add docs.testgitbook.com to ADAPTIVE_CONTENT_HOSTS list diff --git a/.changeset/cold-buckets-divide.md b/.changeset/cold-buckets-divide.md new file mode 100644 index 0000000000..30b61814c0 --- /dev/null +++ b/.changeset/cold-buckets-divide.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +fix nested a tag causing hydration error diff --git a/.changeset/cold-toys-film.md b/.changeset/cold-toys-film.md new file mode 100644 index 0000000000..2c0fc7ddab --- /dev/null +++ b/.changeset/cold-toys-film.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Error handling for AI Chat diff --git a/.changeset/cool-jars-matter.md b/.changeset/cool-jars-matter.md new file mode 100644 index 0000000000..ac166a311c --- /dev/null +++ b/.changeset/cool-jars-matter.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +fix href being empty in TOC diff --git a/.changeset/cool-seas-approve.md b/.changeset/cool-seas-approve.md new file mode 100644 index 0000000000..3f1cb4fd1d --- /dev/null +++ b/.changeset/cool-seas-approve.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix navigation between sections/variants when previewing a site in v2 diff --git a/.changeset/curly-rules-learn.md b/.changeset/curly-rules-learn.md new file mode 100644 index 0000000000..5cf99ce7f7 --- /dev/null +++ b/.changeset/curly-rules-learn.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Add support for inline icons. diff --git a/.changeset/dry-pandas-rhyme.md b/.changeset/dry-pandas-rhyme.md new file mode 100644 index 0000000000..f088745740 --- /dev/null +++ b/.changeset/dry-pandas-rhyme.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix revision id for computed content diff --git a/.changeset/fair-crews-wink.md b/.changeset/fair-crews-wink.md new file mode 100644 index 0000000000..f6ac4f3b1b --- /dev/null +++ b/.changeset/fair-crews-wink.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Add circular corners and depth styling diff --git a/.changeset/famous-melons-compete.md b/.changeset/famous-melons-compete.md new file mode 100644 index 0000000000..c92a2f6a43 --- /dev/null +++ b/.changeset/famous-melons-compete.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix crash when integration script fails to render block. diff --git a/.changeset/fast-trees-battle.md b/.changeset/fast-trees-battle.md new file mode 100644 index 0000000000..0e75fbbeda --- /dev/null +++ b/.changeset/fast-trees-battle.md @@ -0,0 +1,5 @@ +--- +'@gitbook/react-openapi': patch +--- + +Add authorization header for OAuth2 diff --git a/.changeset/few-cars-joke.md b/.changeset/few-cars-joke.md new file mode 100644 index 0000000000..68d4ec2efe --- /dev/null +++ b/.changeset/few-cars-joke.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +Display MCP tool calls in AI chat. diff --git a/.changeset/fifty-ducks-press.md b/.changeset/fifty-ducks-press.md new file mode 100644 index 0000000000..a250d31243 --- /dev/null +++ b/.changeset/fifty-ducks-press.md @@ -0,0 +1,6 @@ +--- +'@gitbook/react-openapi': patch +'gitbook': patch +--- + +Improve support for OAuth2 security type diff --git a/.changeset/flat-wolves-poke.md b/.changeset/flat-wolves-poke.md new file mode 100644 index 0000000000..1e14bdc3f0 --- /dev/null +++ b/.changeset/flat-wolves-poke.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Adds Columns layout block to GBO diff --git a/.changeset/forty-readers-mix.md b/.changeset/forty-readers-mix.md new file mode 100644 index 0000000000..fe80c745cc --- /dev/null +++ b/.changeset/forty-readers-mix.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Support dark-mode specific page cover image diff --git a/.changeset/fresh-shrimps-flow.md b/.changeset/fresh-shrimps-flow.md new file mode 100644 index 0000000000..a2e62c9480 --- /dev/null +++ b/.changeset/fresh-shrimps-flow.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Update Models page styling diff --git a/.changeset/fuzzy-baboons-buy.md b/.changeset/fuzzy-baboons-buy.md new file mode 100644 index 0000000000..19ec20073e --- /dev/null +++ b/.changeset/fuzzy-baboons-buy.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Show scrollbars diff --git a/.changeset/fuzzy-tables-jump.md b/.changeset/fuzzy-tables-jump.md new file mode 100644 index 0000000000..e759503da2 --- /dev/null +++ b/.changeset/fuzzy-tables-jump.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Optimize performances by using a smarter per-request cache arround data cached functions diff --git a/.changeset/gorgeous-cycles-cheat.md b/.changeset/gorgeous-cycles-cheat.md new file mode 100644 index 0000000000..60dfbb7aa9 --- /dev/null +++ b/.changeset/gorgeous-cycles-cheat.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +add a force-revalidate api route to force bust the cache in case of errors diff --git a/.changeset/gorgeous-cycles-grow.md b/.changeset/gorgeous-cycles-grow.md new file mode 100644 index 0000000000..2056ac5477 --- /dev/null +++ b/.changeset/gorgeous-cycles-grow.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Add support for icons in buttons. diff --git a/.changeset/green-bulldogs-punch.md b/.changeset/green-bulldogs-punch.md new file mode 100644 index 0000000000..d94960671d --- /dev/null +++ b/.changeset/green-bulldogs-punch.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix resolution of page by resolving site redirects before space redirects diff --git a/.changeset/green-clouds-cough.md b/.changeset/green-clouds-cough.md new file mode 100644 index 0000000000..614e9d2c6a --- /dev/null +++ b/.changeset/green-clouds-cough.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Fix rendering of ogimage with SVG logos. diff --git a/.changeset/hip-bobcats-cover.md b/.changeset/hip-bobcats-cover.md new file mode 100644 index 0000000000..78edac950b --- /dev/null +++ b/.changeset/hip-bobcats-cover.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Best effort at preserving current variant when navigating between sections by matching the pathname against site spaces in the new section. diff --git a/.changeset/khaki-bees-count.md b/.changeset/khaki-bees-count.md new file mode 100644 index 0000000000..181ce84e36 --- /dev/null +++ b/.changeset/khaki-bees-count.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix crash when integration is triggering invalid requests. diff --git a/.changeset/lazy-colts-hammer.md b/.changeset/lazy-colts-hammer.md new file mode 100644 index 0000000000..b05e849f11 --- /dev/null +++ b/.changeset/lazy-colts-hammer.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Optimize the fetch of revision files by using only the getRevision cache. diff --git a/.changeset/lazy-pants-matter.md b/.changeset/lazy-pants-matter.md new file mode 100644 index 0000000000..516874dcfb --- /dev/null +++ b/.changeset/lazy-pants-matter.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +encode customization header diff --git a/.changeset/long-cameras-protect.md b/.changeset/long-cameras-protect.md new file mode 100644 index 0000000000..ff51bbc939 --- /dev/null +++ b/.changeset/long-cameras-protect.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Rework full-width layout, add support for full-width page option diff --git a/.changeset/metal-trainers-nail.md b/.changeset/metal-trainers-nail.md new file mode 100644 index 0000000000..0792a27c8b --- /dev/null +++ b/.changeset/metal-trainers-nail.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix CodeBlock layout diff --git a/.changeset/nasty-donkeys-sell.md b/.changeset/nasty-donkeys-sell.md new file mode 100644 index 0000000000..d367736685 --- /dev/null +++ b/.changeset/nasty-donkeys-sell.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Scroll to active TOC when clicking a link diff --git a/.changeset/nasty-moles-visit.md b/.changeset/nasty-moles-visit.md new file mode 100644 index 0000000000..fa65297331 --- /dev/null +++ b/.changeset/nasty-moles-visit.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +fix ISR on preview env diff --git a/.changeset/neat-walls-drum.md b/.changeset/neat-walls-drum.md new file mode 100644 index 0000000000..6eb0d6a465 --- /dev/null +++ b/.changeset/neat-walls-drum.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Add support for text alignment for headings and paragraphs. diff --git a/.changeset/nervous-students-judge.md b/.changeset/nervous-students-judge.md new file mode 100644 index 0000000000..1c39892b90 --- /dev/null +++ b/.changeset/nervous-students-judge.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix concurrent execution in Vercel causing pages to not be attached to the proper tags. diff --git a/.changeset/orange-ears-drop.md b/.changeset/orange-ears-drop.md new file mode 100644 index 0000000000..12dd7fae5d --- /dev/null +++ b/.changeset/orange-ears-drop.md @@ -0,0 +1,5 @@ +--- +"@gitbook/react-contentkit": patch +--- + +Add basic error handling when transitioning between states. diff --git a/.changeset/orange-hounds-sparkle.md b/.changeset/orange-hounds-sparkle.md new file mode 100644 index 0000000000..ec22218455 --- /dev/null +++ b/.changeset/orange-hounds-sparkle.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Generate a llms-full.txt version of the docs site diff --git a/.changeset/pink-windows-wonder.md b/.changeset/pink-windows-wonder.md new file mode 100644 index 0000000000..b244f10e34 --- /dev/null +++ b/.changeset/pink-windows-wonder.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Don't crash ogimage generation on RTL text, as a workaround until we can support it. diff --git a/.changeset/plenty-laws-buy.md b/.changeset/plenty-laws-buy.md new file mode 100644 index 0000000000..afeb96dab9 --- /dev/null +++ b/.changeset/plenty-laws-buy.md @@ -0,0 +1,5 @@ +--- +gitbook: minor +--- + +Add support for site customization option to change how external links open. diff --git a/.changeset/poor-dodos-lick.md b/.changeset/poor-dodos-lick.md new file mode 100644 index 0000000000..ca02d7382b --- /dev/null +++ b/.changeset/poor-dodos-lick.md @@ -0,0 +1,5 @@ +--- +"@gitbook/fonts": minor +--- + +Initial version of the package diff --git a/.changeset/pretty-balloons-fold.md b/.changeset/pretty-balloons-fold.md new file mode 100644 index 0000000000..4e82079e87 --- /dev/null +++ b/.changeset/pretty-balloons-fold.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix rendering of ogimage when logo or icon are AVIF images. diff --git a/.changeset/purple-cougars-breathe.md b/.changeset/purple-cougars-breathe.md new file mode 100644 index 0000000000..cf90771986 --- /dev/null +++ b/.changeset/purple-cougars-breathe.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Add margin to adjacent buttons diff --git a/.changeset/rare-pens-whisper.md b/.changeset/rare-pens-whisper.md new file mode 100644 index 0000000000..2ba1ed3793 --- /dev/null +++ b/.changeset/rare-pens-whisper.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix ogimage generation failing with some JPEG images. diff --git a/.changeset/real-trains-perform.md b/.changeset/real-trains-perform.md new file mode 100644 index 0000000000..c08f35a59b --- /dev/null +++ b/.changeset/real-trains-perform.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix page group not expanded by default diff --git a/.changeset/real-walls-glow.md b/.changeset/real-walls-glow.md new file mode 100644 index 0000000000..24bdd3f3e7 --- /dev/null +++ b/.changeset/real-walls-glow.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Don't cache unexpected API errors for more than a few minutes. diff --git a/.changeset/rich-buses-hunt.md b/.changeset/rich-buses-hunt.md new file mode 100644 index 0000000000..9cfd0f87c8 --- /dev/null +++ b/.changeset/rich-buses-hunt.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix an issue where PDF export URLs were not keeping their query params. diff --git a/.changeset/rotten-crews-tie.md b/.changeset/rotten-crews-tie.md new file mode 100644 index 0000000000..de10b1ac62 --- /dev/null +++ b/.changeset/rotten-crews-tie.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Fix markdown page generation for groups diff --git a/.changeset/rotten-donuts-bow.md b/.changeset/rotten-donuts-bow.md new file mode 100644 index 0000000000..c178bf8b3e --- /dev/null +++ b/.changeset/rotten-donuts-bow.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +add a global error boundary diff --git a/.changeset/rotten-pianos-cheat.md b/.changeset/rotten-pianos-cheat.md new file mode 100644 index 0000000000..e1bad53fe7 --- /dev/null +++ b/.changeset/rotten-pianos-cheat.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix links to other spaces within a section. diff --git a/.changeset/rotten-seals-rush.md b/.changeset/rotten-seals-rush.md new file mode 100644 index 0000000000..950e25880c --- /dev/null +++ b/.changeset/rotten-seals-rush.md @@ -0,0 +1,5 @@ +--- +'@gitbook/react-openapi': patch +--- + +Indent JSON python code sample diff --git a/.changeset/rude-games-beg.md b/.changeset/rude-games-beg.md new file mode 100644 index 0000000000..0e1d1f4612 --- /dev/null +++ b/.changeset/rude-games-beg.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Add support for cover repositioning diff --git a/.changeset/selfish-bananas-sneeze.md b/.changeset/selfish-bananas-sneeze.md new file mode 100644 index 0000000000..51fdfb1045 --- /dev/null +++ b/.changeset/selfish-bananas-sneeze.md @@ -0,0 +1,6 @@ +--- +'@gitbook/react-openapi': patch +'gitbook': patch +--- + +Handle nullish OpenAPI mediaTypeObject diff --git a/.changeset/sharp-hats-applaud.md b/.changeset/sharp-hats-applaud.md new file mode 100644 index 0000000000..bf84bf6ada --- /dev/null +++ b/.changeset/sharp-hats-applaud.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix missing title on button to close the announcement banner. diff --git a/.changeset/sharp-jeans-burn.md b/.changeset/sharp-jeans-burn.md new file mode 100644 index 0000000000..1eaf192a22 --- /dev/null +++ b/.changeset/sharp-jeans-burn.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Make icons for page groups more contrasting diff --git a/.changeset/shiny-seahorses-tie.md b/.changeset/shiny-seahorses-tie.md new file mode 100644 index 0000000000..9859d9b279 --- /dev/null +++ b/.changeset/shiny-seahorses-tie.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +Enable AI chat when AI mode is configured to assistant diff --git a/.changeset/sixty-cows-pay.md b/.changeset/sixty-cows-pay.md new file mode 100644 index 0000000000..61d7c3ecca --- /dev/null +++ b/.changeset/sixty-cows-pay.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +Add AI chat diff --git a/.changeset/slimy-cows-press.md b/.changeset/slimy-cows-press.md new file mode 100644 index 0000000000..1e012a9930 --- /dev/null +++ b/.changeset/slimy-cows-press.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Ignore case while highlighting search results. diff --git a/.changeset/slimy-hornets-share.md b/.changeset/slimy-hornets-share.md new file mode 100644 index 0000000000..80cbf8c9a2 --- /dev/null +++ b/.changeset/slimy-hornets-share.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Consistently show variant selector in section bar if site has sections diff --git a/.changeset/slow-boxes-approve.md b/.changeset/slow-boxes-approve.md new file mode 100644 index 0000000000..8ac062cf46 --- /dev/null +++ b/.changeset/slow-boxes-approve.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Include page group children under the .md route diff --git a/.changeset/slow-lizards-obey.md b/.changeset/slow-lizards-obey.md new file mode 100644 index 0000000000..3d97c1c8b6 --- /dev/null +++ b/.changeset/slow-lizards-obey.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Make TOC height dynamic based on visible header and footer elements diff --git a/.changeset/slow-masks-agree.md b/.changeset/slow-masks-agree.md new file mode 100644 index 0000000000..d59622a056 --- /dev/null +++ b/.changeset/slow-masks-agree.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Clicking an active TOC item toggles its descendants diff --git a/.changeset/slow-roses-mate.md b/.changeset/slow-roses-mate.md new file mode 100644 index 0000000000..0b70530dda --- /dev/null +++ b/.changeset/slow-roses-mate.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Improve AI Chat context popup diff --git a/.changeset/small-dots-report.md b/.changeset/small-dots-report.md new file mode 100644 index 0000000000..b3dd8899f8 --- /dev/null +++ b/.changeset/small-dots-report.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix viewing a page from a revision diff --git a/.changeset/soft-walls-change.md b/.changeset/soft-walls-change.md new file mode 100644 index 0000000000..8324a67faa --- /dev/null +++ b/.changeset/soft-walls-change.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix InlineLinkTooltip having a negative impact on performance, especially on larger pages. diff --git a/.changeset/spotty-apples-crash.md b/.changeset/spotty-apples-crash.md new file mode 100644 index 0000000000..20aa75e6d4 --- /dev/null +++ b/.changeset/spotty-apples-crash.md @@ -0,0 +1,6 @@ +--- +'@gitbook/openapi-parser': patch +'@gitbook/react-openapi': patch +--- + +Optional label in OpenAPI x-codeSamples diff --git a/.changeset/strong-poets-move.md b/.changeset/strong-poets-move.md new file mode 100644 index 0000000000..a407fd49fb --- /dev/null +++ b/.changeset/strong-poets-move.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix bold header links hover color diff --git a/.changeset/stupid-plums-perform.md b/.changeset/stupid-plums-perform.md new file mode 100644 index 0000000000..07145fb5f3 --- /dev/null +++ b/.changeset/stupid-plums-perform.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +cache fonts and static image used in OGImage in memory diff --git a/.changeset/tame-mangos-battle.md b/.changeset/tame-mangos-battle.md new file mode 100644 index 0000000000..3d7960e8e0 --- /dev/null +++ b/.changeset/tame-mangos-battle.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix border being added to cards diff --git a/.changeset/tender-poets-hang.md b/.changeset/tender-poets-hang.md new file mode 100644 index 0000000000..991a5d993f --- /dev/null +++ b/.changeset/tender-poets-hang.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Add metadata for adding site to Apple devices home diff --git a/.changeset/thick-chefs-repeat.md b/.changeset/thick-chefs-repeat.md new file mode 100644 index 0000000000..8b08d50e3e --- /dev/null +++ b/.changeset/thick-chefs-repeat.md @@ -0,0 +1,5 @@ +--- +'@gitbook/react-openapi': patch +--- + +Handle nested deprecated properties in generateSchemaExample diff --git a/.changeset/thick-cups-shout.md b/.changeset/thick-cups-shout.md new file mode 100644 index 0000000000..aad7bbf6d6 --- /dev/null +++ b/.changeset/thick-cups-shout.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix crash during rendering of ogimage for VA sites with default icon. diff --git a/.changeset/thin-buckets-grow.md b/.changeset/thin-buckets-grow.md new file mode 100644 index 0000000000..579f1033f7 --- /dev/null +++ b/.changeset/thin-buckets-grow.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Add `urlObject.hash` to `linker.toLinkForContent` to pass through URL fragment identifiers, used in search diff --git a/.changeset/thin-spiders-relate.md b/.changeset/thin-spiders-relate.md new file mode 100644 index 0000000000..e7f946f011 --- /dev/null +++ b/.changeset/thin-spiders-relate.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Allow to zoom images on mobile if relevant diff --git a/.changeset/tidy-dots-suffer.md b/.changeset/tidy-dots-suffer.md new file mode 100644 index 0000000000..7171c8f4ca --- /dev/null +++ b/.changeset/tidy-dots-suffer.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +apply customization for dynamic context diff --git a/.changeset/tiny-zoos-scream.md b/.changeset/tiny-zoos-scream.md new file mode 100644 index 0000000000..a739182859 --- /dev/null +++ b/.changeset/tiny-zoos-scream.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Reverse order of feedback smileys diff --git a/.changeset/violet-schools-care.md b/.changeset/violet-schools-care.md new file mode 100644 index 0000000000..e77a98b5cf --- /dev/null +++ b/.changeset/violet-schools-care.md @@ -0,0 +1,5 @@ +--- +'@gitbook/react-openapi': patch +--- + +Deduplicate path parameters from OpenAPI spec diff --git a/.changeset/warm-roses-sleep.md b/.changeset/warm-roses-sleep.md new file mode 100644 index 0000000000..b9b912700a --- /dev/null +++ b/.changeset/warm-roses-sleep.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +Fix ogimage using incorrect Google Font depending on language. diff --git a/.changeset/wise-gifts-smash.md b/.changeset/wise-gifts-smash.md new file mode 100644 index 0000000000..ebd6dd5dd8 --- /dev/null +++ b/.changeset/wise-gifts-smash.md @@ -0,0 +1,5 @@ +--- +gitbook: patch +--- + +remove trailing slash from linker diff --git a/.changeset/witty-forks-sell.md b/.changeset/witty-forks-sell.md new file mode 100644 index 0000000000..cfafe1a2c5 --- /dev/null +++ b/.changeset/witty-forks-sell.md @@ -0,0 +1,6 @@ +--- +'@gitbook/react-openapi': patch +'gitbook': patch +--- + +Support for OpenAPI Array request body diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..83befe8fe0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Changes to the API data cache functions can invalidate all existing data cache +# causing a massive amount of revalidation, impacting our API. +packages/gitbook/src/lib/data/api.ts @SamyPesse diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a65357a23d..6fe349387f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -53,19 +53,42 @@ After forking this repository, you'll want to [create a branch](https://docs.git #### 3. Install dependencies and run the project locally -GitBook uses [Bun](https://bun.sh/) to run the project. Make sure you're using the specified version of `node` before running any of the development commands to ensure a smooth development experience. - -You can easily do this by running the command `nvm use`. - -To start your local version of GitBook, run the command `bun dev`. +##### Prerequisites: +- Node.js (Version: >=20.6) + - Use `nvm` for easy Node management +- [Bun](https://bun.sh/) (Version: >=1.2.15) + - We use a text-based lockfile which isn't supported below 1.2.15 + +##### Setup steps: + +1. Ensure you are using the project's version of Node: +```bash +nvm use +``` + +2. Install dependencies using Bun: +```bash +bun install +``` + +3. Start the development server: +```bash +bun dev +``` + +Additional development commands: +- `bun format`: Format the code using Biome +- `bun typecheck`: Run TypeScript type checking +- `bun unit`: Run unit tests +- `bun e2e`: Run end-to-end tests #### 4. Preview your changes -When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/`. +When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/url`. -For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/docs.gitbook.com` after running the development server. +For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/url/gitbook.com/docs` after running the development server. -You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://docs.gitbook.com/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version. +You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://gitbook.com/docs/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version. ### Commit your update diff --git a/.github/actions/gradual-deploy-cloudflare/action.yaml b/.github/actions/gradual-deploy-cloudflare/action.yaml new file mode 100644 index 0000000000..adb513a1bc --- /dev/null +++ b/.github/actions/gradual-deploy-cloudflare/action.yaml @@ -0,0 +1,83 @@ +name: Gradual Deploy to Cloudflare +description: Use gradual deployment to deploy to Cloudflare. This action will upload the middleware and server versions to Cloudflare and kept them bound together +inputs: + apiToken: + description: 'Cloudflare API token' + required: true + accountId: + description: 'Cloudflare account ID' + required: true + environment: + description: 'Cloudflare environment to deploy to (staging, production, preview)' + required: true + middlewareVersionId: + description: 'Middleware version ID to deploy' + required: true + serverVersionId: + description: 'Server version ID to deploy' + required: true +outputs: + deployment-url: + description: "Deployment URL" + value: ${{ steps.deploy_middleware.outputs.deployment-url }} +runs: + using: 'composite' + steps: + - id: wrangler_status + name: Check wrangler deployment status + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + + # This step is used to get the version ID that is currently deployed to Cloudflare. + - id: extract_current_version + name: Extract current version + shell: bash + run: | + version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + + - id: deploy_server + name: Deploy server to Cloudflare at 0% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + + # Since we use version overrides headers, we can directly deploy the middleware to 100%. + - id: deploy_middleware + name: Deploy middleware to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc + + - name: Deploy server to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + + - name: Outputs + shell: bash + env: + DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }} + run: | + echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}" \ No newline at end of file diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index a63c69f8eb..0906e8bc86 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -19,10 +19,16 @@ inputs: deploy: description: 'Deploy as main version for all traffic instead of uploading versions' required: true + commitTag: + description: 'Commit branch to associate with the deployment' + required: true + commitMessage: + description: 'Commit message to associate with the deployment' + required: true outputs: deployment-url: description: "Deployment URL" - value: ${{ steps.deploy.outputs.deployment-url }} + value: ${{ steps.upload_middleware.outputs.deployment-url }} runs: using: 'composite' steps: @@ -49,24 +55,84 @@ runs: GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL + GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL - name: Build worker - run: bun run turbo build:v2:cloudflare + run: bun run turbo build:cloudflare + env: + GITBOOK_RUNTIME: cloudflare shell: bash - - id: deploy - name: Deploy to Cloudflare + + - name: Upload the DO worker uses: cloudflare/wrangler-action@v3.14.0 with: apiToken: ${{ inputs.apiToken }} accountId: ${{ inputs.accountId }} workingDirectory: ./ - wranglerVersion: '3.112.0' + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: deploy --config ./packages/gitbook/openNext/customWorkers/doWrangler.jsonc + + - id: upload_server + name: Upload server to Cloudflare + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + + - name: Extract server version worker ID + shell: bash + id: extract_server_version_id + run: | + version_id=$(echo '${{ steps.upload_server.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + + - name: Run updateWrangler scripts + shell: bash + run: | + bun run ./packages/gitbook/openNext/customWorkers/script/updateWrangler.ts ${{ steps.extract_server_version_id.outputs.version_id }} + + - id: upload_middleware + name: Upload middleware to Cloudflare + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc + + - name: Extract middleware version worker ID + shell: bash + id: extract_middleware_version_id + run: | + version_id=$(echo '${{ steps.upload_middleware.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + + - name: Deploy server and middleware to Cloudflare + if: ${{ inputs.deploy == 'true' }} + uses: ./.github/actions/gradual-deploy-cloudflare + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + opServiceAccount: ${{ inputs.opServiceAccount }} + opItem: ${{ inputs.opItem }} environment: ${{ inputs.environment }} - command: ${{ fromJSON(inputs.deploy) == true && 'deploy' || 'versions upload' }} --config ./packages/gitbook-v2/wrangler.toml + serverVersionId: ${{ steps.extract_server_version_id.outputs.version_id }} + middlewareVersionId: ${{ steps.extract_middleware_version_id.outputs.version_id }} + deploy: ${{ inputs.deploy }} + + - name: Outputs shell: bash env: - DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }} run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file + echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}" + echo "Output server: ${{ steps.upload_server.outputs.command-output }}" \ No newline at end of file diff --git a/.github/composite/deploy-vercel/action.yaml b/.github/composite/deploy-vercel/action.yaml index f0e648a4e0..74b54e8590 100644 --- a/.github/composite/deploy-vercel/action.yaml +++ b/.github/composite/deploy-vercel/action.yaml @@ -54,6 +54,7 @@ runs: GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL + GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL - name: Build Project Artifacts @@ -62,6 +63,7 @@ runs: env: VERCEL_ORG_ID: ${{ inputs.vercelOrg }} VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} + GITBOOK_RUNTIME: vercel - name: Deploy Project Artifacts to Vercel id: deploy shell: bash diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml index e7ce2a6a7f..ecca882e61 100644 --- a/.github/workflows/deploy-preview.yaml +++ b/.github/workflows/deploy-preview.yaml @@ -7,56 +7,10 @@ on: env: NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }} jobs: - deploy-v1-cloudflare: - name: Deploy v1 to Cloudflare Pages - runs-on: ubuntu-latest - environment: - name: ${{ github.ref == 'refs/heads/main' && '1c-production' || '1c-preview' }} - url: ${{ steps.deploy.outputs.deployment-url }} - permissions: - contents: read - deployments: write - issues: write - pull-requests: write - checks: write - statuses: write - outputs: - deployment-url: ${{ steps.deploy.outputs.deployment-url }} - deployment-alias-url: ${{ steps.deploy.outputs.deployment-alias-url }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Bun - uses: ./.github/composite/setup-bun - - name: Install dependencies - run: bun install --frozen-lockfile - env: - PUPPETEER_SKIP_DOWNLOAD: 1 - - name: Sets env vars for production - if: github.ref == 'refs/heads/main' - run: | - echo "GITBOOK_ASSETS_PREFIX=https://static.gitbook.com" >> $GITHUB_ENV - - name: Build Next.js with next-on-pages - run: bun run turbo gitbook#build:cloudflare - env: - NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }} - - id: deploy - name: Deploy to Cloudflare - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - workingDirectory: ./ - wranglerVersion: '3.112.0' - command: pages deploy ./packages/gitbook/.vercel/output/static --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }} --branch=${{ github.ref == 'refs/heads/main' && 'main' || format('pr{0}', github.event.pull_request.number) }} - - name: Outputs - run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" - echo "Alias URL: ${{ steps.deploy.outputs.deployment-alias-url }}" deploy-v2-vercel: name: Deploy v2 to Vercel (preview) runs-on: ubuntu-latest - environment: + environment: name: 2v-preview url: ${{ steps.deploy.outputs.deployment-url }} outputs: @@ -77,11 +31,11 @@ jobs: deploy-v2-cloudflare: name: Deploy v2 to Cloudflare Worker (preview) runs-on: ubuntu-latest - environment: + environment: name: 2c-preview url: ${{ steps.deploy.outputs.deployment-url }} outputs: - deployment-url: ${{ steps.deploy.outputs.deployment-url }} + deployment-url: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }} steps: - name: Checkout uses: actions/checkout@v4 @@ -95,15 +49,24 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} opItem: op://gitbook-open/2c-preview opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + commitTag: ${{ github.ref == 'refs/heads/main' && 'main' || format('pr{0}', github.event.pull_request.number) }} + commitMessage: ${{ github.sha }} + - name: Extract Worker ID + id: extract-worker-id + if: ${{ !steps.deploy.outputs.deployment-url }} + run: | + if [[ "${{ steps.deploy.outputs.command-output }}" =~ Worker\ Version\ ID:\ ([0-9a-f]{8})-([0-9a-f-]+) ]]; then + WORKER_ID_FIRST_PART="${BASH_REMATCH[1]}" + echo "worker-url=https://${WORKER_ID_FIRST_PART}-gitbook-open-v2-preview.gitbook.workers.dev/" >> $GITHUB_OUTPUT + fi - name: Outputs run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" + echo "URL: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}" comment-deployments: runs-on: ubuntu-latest name: Comment Deployments (preview) if: always() && !startsWith(github.ref, 'refs/heads/main') needs: - - deploy-v1-cloudflare - deploy-v2-vercel - deploy-v2-cloudflare steps: @@ -123,15 +86,6 @@ jobs: body: | Summary of the deployments: - ### Version 1 (production) - - | Version | URL | Status | - | --- | --- | --- | - | Latest commit | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}) | ${{ needs.deploy-v1-cloudflare.result == 'success' && '✅' || '❌' }} | - | PR | [${{ needs.deploy-v1-cloudflare.outputs.deployment-alias-url }}](${{ needs.deploy-v1-cloudflare.outputs.deployment-alias-url }}) | ${{ needs.deploy-v1-cloudflare.result == 'success' && '✅' || '❌' }} | - - ### Version 2 (experimental) - | Version | URL | Status | | --- | --- | --- | | Vercel | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}](${{ needs.deploy-v2-vercel.outputs.deployment-url }}) | ${{ needs.deploy-v2-vercel.result == 'success' && '✅' || '❌' }} | @@ -139,16 +93,16 @@ jobs: ### Test content - | Site | v1 | v2 | + | Site | `2v` | `2c` | | --- | --- | --- | - | GitBook | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/docs.gitbook.com) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/docs.gitbook.com) | - | E2E | [${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v1-cloudflare.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | + | GitBook | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs) | + | E2E | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | edit-mode: replace - visual-testing-v1: + visual-testing-v2-vercel: runs-on: ubuntu-latest - name: Visual Testing v1 - needs: deploy-v1-cloudflare - timeout-minutes: 8 + name: Visual Testing v2 + needs: deploy-v2-vercel + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 @@ -161,12 +115,14 @@ jobs: - name: Run Playwright tests run: bun e2e env: - BASE_URL: ${{ needs.deploy-v1-cloudflare.outputs.deployment-url }} + BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} + SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - visual-testing-v2-vercel: + ARGOS_BUILD_NAME: 'v2-vercel' + visual-testing-v2-cloudflare: runs-on: ubuntu-latest - name: Visual Testing v2 - needs: deploy-v2-vercel + name: Visual Testing v2 (Cloudflare) + needs: deploy-v2-cloudflare timeout-minutes: 10 steps: - name: Checkout @@ -180,15 +136,15 @@ jobs: - name: Run Playwright tests run: bun e2e env: - BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} - SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ + BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }} + SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'v2-vercel' - visual-testing-customers-v1: + ARGOS_BUILD_NAME: 'v2-cloudflare' + visual-testing-customers-v2: runs-on: ubuntu-latest - name: Visual Testing Customers v1 - needs: deploy-v1-cloudflare - timeout-minutes: 6 + name: Visual Testing Customers v2 + needs: deploy-v2-vercel + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 @@ -201,14 +157,15 @@ jobs: - name: Run Playwright tests run: bun e2e-customers env: - BASE_URL: ${{ needs.deploy-v1-cloudflare.outputs.deployment-url }} + BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} + SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'customers-v1' - visual-testing-customers-v2: + ARGOS_BUILD_NAME: 'customers-v2' + visual-testing-customers-v2-cloudflare: runs-on: ubuntu-latest - name: Visual Testing Customers v2 - needs: deploy-v2-vercel - timeout-minutes: 6 + name: Visual Testing Customers v2 (Cloudflare) + needs: deploy-v2-cloudflare + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 @@ -221,14 +178,14 @@ jobs: - name: Run Playwright tests run: bun e2e-customers env: - BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} - SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ + BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }} + SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} ARGOS_BUILD_NAME: 'customers-v2' - pagespeed-testing-v1: + pagespeed-testing-v2: runs-on: ubuntu-latest name: PageSpeed Testing v1 - needs: deploy-v1-cloudflare + needs: deploy-v2-vercel steps: - name: Checkout uses: actions/checkout@v4 @@ -241,5 +198,5 @@ jobs: - name: Run pagespeed tests run: bun ./packages/gitbook/tests/pagespeed-testing.ts env: - BASE_URL: ${{needs.deploy-v1-cloudflare.outputs.deployment-url}} + BASE_URL: ${{needs.deploy-v2-vercel.outputs.deployment-url}} PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }} diff --git a/.github/workflows/deploy-production.yaml b/.github/workflows/deploy-production.yaml index 42bae0a3b3..9d5bb3abb9 100644 --- a/.github/workflows/deploy-production.yaml +++ b/.github/workflows/deploy-production.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Deploy staging + - name: Deploy id: deploy uses: ./.github/composite/deploy-vercel with: @@ -48,6 +48,8 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} opItem: op://gitbook-open/2c-production opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + commitTag: main + commitMessage: ${{ github.sha }} - name: Outputs run: | echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index a1e1df62ed..ed2f290c72 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Deploy staging + - name: Deploy id: deploy uses: ./.github/composite/deploy-vercel with: @@ -48,6 +48,8 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} opItem: op://gitbook-open/2c-staging opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + commitTag: main + commitMessage: ${{ github.sha }} - name: Outputs run: | echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 25d6ab94cd..d7644620f8 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -39,22 +39,4 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - release-preview: - # For now it releases the cache-do to both preview and production - # Once we changed to deploy the app only on release, we should change `release:preview` in `cache-do` - name: Release Preview - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - name: Setup Bun - uses: ./.github/composite/setup-bun - - name: Install dependencies - run: bun install --frozen-lockfile - env: - PUPPETEER_SKIP_DOWNLOAD: 1 - - name: Release preview packages - run: bun run release:preview - env: - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + diff --git a/.vscode/settings.json b/.vscode/settings.json index b9d15df191..8a86e5d5d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,7 @@ ], "tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"], "prettier.enable": false, + "editor.formatOnSave": true, "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit", diff --git a/README.md b/README.md index f8030c085c..6772abe21a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
- Docs - Community - Developer Docs - Changelog - Bug reports + Docs - Community - Developer Docs - Changelog - Bug reports - Feature requests
@@ -35,10 +35,10 @@ To run a local version of this project, please follow these simple steps. ### Prerequisites -- Node.js (Version: >=20.6) - - Use nvm for easy Node management -- Bun (Version: >=1.2.1) - - We use a text-based lockfile which isn't supported below 1.2.1 +- Node.js (Version: >=20.6) + - Use nvm for easy Node management +- [Bun](https://bun.sh/) (Version: >=1.2.15) + - We use a text-based lockfile which isn't supported below 1.2.15 ### Set up @@ -62,20 +62,15 @@ bun install bun dev ``` -5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`. +6. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/url`. examples: -- http://localhost:3000/docs.gitbook.com -- http://localhost:3000/open-source.gitbook.io/midjourney +- http://localhost:3000/url/gitbook.com/docs +- http://localhost:3000/url/open-source.gitbook.io/midjourney Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser. -### Other development commands - -- `bun format`: format the code -- `bun lint`: lint the code - ### CI and testing All pull-requests will be tested against both visual and performances testing to prevent regressions. @@ -150,11 +145,11 @@ See `LICENSE` for more information.
```md -[](https://gitbook.com/) +[](https://www.gitbook.com/preview?utm_source=gitbook_readme_badge&utm_medium=organic&utm_campaign=preview_documentation&utm_content=link) ``` ```html - +{t(language, 'unexpected_error')}
-
+ {t(
+ language,
+ 'ai_chat_tools_read_page',
+ <>
+
+ {t( + language, + 'ai_chat_tools_mcp_tool', + {toolCall.mcpToolTitle ?? toolCall.mcpToolName} + )} +
+ ); +} + +async function DescriptionForSearchToolCall(props: { + toolCall: AIToolCallSearch; + context: GitBookSiteContext; +}) { + const { toolCall, context } = props; + + const language = getSpaceLanguage(context.customization); + + // Resolve all hrefs for search results in parallel + const searchResultsWithHrefs = await Promise.all( + toolCall.results.map(async (result) => { + const resolved = await resolveContentRef( + result.anchor + ? { + kind: 'anchor', + page: result.pageId, + space: result.spaceId, + anchor: result.anchor, + } + : { + kind: 'page', + page: result.pageId, + space: result.spaceId, + }, + context + ); + return { + ...result, + href: resolved?.href || '#', + }; + }) + ); + + return ( +{t(language, 'searched_for', {toolCall.query})}
++ {toolCall.results.length + ? t(language, 'search_results_count', toolCall.results.length) + : t(language, 'search_no_results')} +
+
+
+ {t(language, 'ai_chat_tools_listed_pages')}
+
+ {t(language, 'ai_chat_assistant_description')} +
+
@@ -72,7 +78,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/highlight.ts b/packages/gitbook/src/components/DocumentView/CodeBlock/highlight.ts
index eb02b5b928..c9f596034a 100644
--- a/packages/gitbook/src/components/DocumentView/CodeBlock/highlight.ts
+++ b/packages/gitbook/src/components/DocumentView/CodeBlock/highlight.ts
@@ -12,6 +12,7 @@ import {
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
import { type BundledLanguage, bundledLanguages } from 'shiki/langs';
+import { nullIfNever } from '@/lib/typescript';
import { plainHighlight } from './plain-highlight';
export type HighlightLine = {
@@ -76,10 +77,17 @@ export async function highlight(
themes: [theme],
});
+ let tokenizeMaxLineLength = 400;
+ // In some cases, people will use unindented code blocks with a single line.
+ // In this case, we can safely increase the max line length to avoid not highlighting the code.
+ if (block.nodes.length === 1) {
+ tokenizeMaxLineLength = 5000;
+ }
+
const lines = highlighter.codeToTokensBase(code, {
lang: langName,
theme,
- tokenizeMaxLineLength: 400,
+ tokenizeMaxLineLength,
});
let currentIndex = 0;
@@ -263,16 +271,28 @@ function getPlainCodeBlockLine(
if (node.object === 'text') {
content += cleanupLine(node.leaves.map((leaf) => leaf.text).join(''));
} else {
- const start = index + content.length;
- content += getPlainCodeBlockLine(node, index + content.length, inlines);
- const end = index + content.length;
-
- if (inlines) {
- inlines.push({
- inline: node,
- start,
- end,
- });
+ switch (node.type) {
+ case 'annotation': {
+ const start = index + content.length;
+ content += getPlainCodeBlockLine(node, index + content.length, inlines);
+ const end = index + content.length;
+
+ if (inlines) {
+ inlines.push({
+ inline: node,
+ start,
+ end,
+ });
+ }
+ break;
+ }
+ case 'expression': {
+ break;
+ }
+ default: {
+ nullIfNever(node);
+ break;
+ }
}
}
}
diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css b/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css
deleted file mode 100644
index 22ce3b60e3..0000000000
--- a/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css
+++ /dev/null
@@ -1,31 +0,0 @@
-:root {
- --shiki-color-text: theme("colors.tint.11");
- --shiki-token-constant: #0a6355;
- --shiki-token-string: #8b6d32;
- --shiki-token-comment: theme("colors.teal.700/.64");
- --shiki-token-keyword: theme("colors.pomegranate.600");
- --shiki-token-parameter: #0a3069;
- --shiki-token-function: #8250df;
- --shiki-token-string-expression: #6a4906;
- --shiki-token-punctuation: theme("colors.pomegranate.700/.92");
- --shiki-token-link: theme("colors.tint.12");
- --shiki-token-inserted: #22863a;
- --shiki-token-deleted: #b31d28;
- --shiki-token-changed: #8250df;
-}
-
-html.dark {
- --shiki-color-text: theme("colors.tint.11");
- --shiki-token-constant: #d19a66;
- --shiki-token-string: theme("colors.pomegranate.300");
- --shiki-token-comment: theme("colors.teal.300/.64");
- --shiki-token-keyword: theme("colors.pomegranate.400");
- --shiki-token-parameter: theme("colors.yellow.500");
- --shiki-token-function: #56b6c2;
- --shiki-token-string-expression: theme("colors.tint.11");
- --shiki-token-punctuation: #acc6ee;
- --shiki-token-link: theme("colors.pomegranate.400");
- --shiki-token-inserted: #85e89d;
- --shiki-token-deleted: #fdaeb7;
- --shiki-token-changed: #56b6c2;
-}
diff --git a/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx b/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx
new file mode 100644
index 0000000000..4a713538fc
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx
@@ -0,0 +1,79 @@
+import { type ClassValue, tcls } from '@/lib/tailwind';
+import type { DocumentBlockColumns, Length } from '@gitbook/api';
+import type { BlockProps } from '../Block';
+import { Blocks } from '../Blocks';
+
+export function Columns(props: BlockProps) {
+ const { block, style, ancestorBlocks, document, context } = props;
+ return (
+
+ {block.nodes.map((columnBlock) => {
+ const width = columnBlock.data.width;
+ const { className, style } = transformLengthToCSS(width);
+ return (
+
+
+
+ );
+ })}
+
+ );
+}
+
+export function Column(props: {
+ children?: React.ReactNode;
+ className?: ClassValue;
+ style?: React.CSSProperties;
+}) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+function transformLengthToCSS(length: Length | undefined) {
+ if (!length) {
+ return { className: ['md:w-full'] }; // default to full width if no length is specified
+ }
+
+ if (typeof length === 'number') {
+ return { style: undefined }; // not implemented yet with non-percentage lengths
+ }
+
+ if (length.unit === '%') {
+ return {
+ className: [
+ 'md:flex-shrink-0',
+ COLUMN_WIDTHS[Math.round(length.value * 0.01 * (COLUMN_WIDTHS.length - 1))],
+ ],
+ };
+ }
+
+ return { style: undefined }; // not implemented yet with non-percentage lengths
+}
+
+// Tailwind CSS classes for column widths.
+// The index of the array corresponds to the percentage width of the column.
+const COLUMN_WIDTHS = [
+ 'md:w-0',
+ 'md:w-1/12',
+ 'md:w-2/12',
+ 'md:w-3/12',
+ 'md:w-4/12',
+ 'md:w-5/12',
+ 'md:w-6/12',
+ 'md:w-7/12',
+ 'md:w-8/12',
+ 'md:w-9/12',
+ 'md:w-10/12',
+ 'md:w-11/12',
+ 'md:w-full',
+];
diff --git a/packages/gitbook/src/components/DocumentView/Columns/index.ts b/packages/gitbook/src/components/DocumentView/Columns/index.ts
new file mode 100644
index 0000000000..a8b4f25b41
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/Columns/index.ts
@@ -0,0 +1 @@
+export * from './Columns';
diff --git a/packages/gitbook/src/components/DocumentView/Divider.tsx b/packages/gitbook/src/components/DocumentView/Divider.tsx
index 8f18593e2f..f787d044b5 100644
--- a/packages/gitbook/src/components/DocumentView/Divider.tsx
+++ b/packages/gitbook/src/components/DocumentView/Divider.tsx
@@ -7,5 +7,5 @@ import type { BlockProps } from './Block';
export function Divider(props: BlockProps) {
const { style } = props;
- return
;
+ return
;
}
diff --git a/packages/gitbook/src/components/DocumentView/DocumentView.tsx b/packages/gitbook/src/components/DocumentView/DocumentView.tsx
index 724a7f2b05..71850fe20c 100644
--- a/packages/gitbook/src/components/DocumentView/DocumentView.tsx
+++ b/packages/gitbook/src/components/DocumentView/DocumentView.tsx
@@ -1,6 +1,6 @@
+import type { GitBookAnyContext } from '@/lib/context';
import type { ClassValue } from '@/lib/tailwind';
import type { JSONDocument } from '@gitbook/api';
-import type { GitBookAnyContext } from '@v2/lib/context';
import { BlockSkeleton } from './Block';
import { Blocks } from './Blocks';
@@ -28,6 +28,14 @@ export interface DocumentContext {
* @default true
*/
wrapBlocksInSuspense?: boolean;
+
+ /**
+ * True if link previews should be rendered.
+ * This is used to limit the number of link previews rendered in a document.
+ * If false, no link previews will be rendered.
+ * @default false
+ */
+ shouldRenderLinkPreviews?: boolean;
}
export interface DocumentContextProps {
diff --git a/packages/gitbook/src/components/DocumentView/Embed.tsx b/packages/gitbook/src/components/DocumentView/Embed.tsx
index 796cdaeee3..0b433b6892 100644
--- a/packages/gitbook/src/components/DocumentView/Embed.tsx
+++ b/packages/gitbook/src/components/DocumentView/Embed.tsx
@@ -5,7 +5,8 @@ import ReactDOM from 'react-dom';
import { Card } from '@/components/primitives';
import { tcls } from '@/lib/tailwind';
-import { getDataOrNull } from '@v2/lib/data';
+import { getDataOrNull } from '@/lib/data';
+import { Image } from '../utils';
import type { BlockProps } from './Block';
import { Caption } from './Caption';
import { IntegrationBlock } from './Integration';
@@ -52,7 +53,14 @@ export async function Embed(props: BlockProps) {
+
) : null
}
href={block.data.url}
diff --git a/packages/gitbook/src/components/DocumentView/Emoji.tsx b/packages/gitbook/src/components/DocumentView/Emoji.tsx
index f744053525..e076032469 100644
--- a/packages/gitbook/src/components/DocumentView/Emoji.tsx
+++ b/packages/gitbook/src/components/DocumentView/Emoji.tsx
@@ -4,7 +4,7 @@ import { Emoji as EmojiPrimitive } from '@/components/primitives';
import type { InlineProps } from './Inline';
-export async function Emoji(props: InlineProps) {
+export function Emoji(props: InlineProps) {
const { inline } = props;
return ;
diff --git a/packages/gitbook/src/components/DocumentView/HashLinkButton.tsx b/packages/gitbook/src/components/DocumentView/HashLinkButton.tsx
new file mode 100644
index 0000000000..02f528c459
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/HashLinkButton.tsx
@@ -0,0 +1,58 @@
+import { type ClassValue, tcls } from '@/lib/tailwind';
+import type { DocumentBlockHeading, DocumentBlockTabs } from '@gitbook/api';
+import { Icon } from '@gitbook/icons';
+import { getBlockTextStyle } from './spacing';
+
+/**
+ * A hash icon which adds the block or active block item's ID in the URL hash.
+ * The button needs to be wrapped in a container with `hashLinkButtonWrapperStyles`.
+ */
+export const hashLinkButtonWrapperStyles = tcls('relative', 'group/hash');
+
+export function HashLinkButton(props: {
+ id: string;
+ block: DocumentBlockTabs | DocumentBlockHeading;
+ label?: string;
+ className?: ClassValue;
+ iconClassName?: ClassValue;
+}) {
+ const { id, block, className, iconClassName, label = 'Direct link to block' } = props;
+ const textStyle = getBlockTextStyle(block);
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/Heading.tsx b/packages/gitbook/src/components/DocumentView/Heading.tsx
index 8016b0f1ba..9ac7476f69 100644
--- a/packages/gitbook/src/components/DocumentView/Heading.tsx
+++ b/packages/gitbook/src/components/DocumentView/Heading.tsx
@@ -1,11 +1,12 @@
import type { DocumentBlockHeading } from '@gitbook/api';
-import { Icon } from '@gitbook/icons';
import { tcls } from '@/lib/tailwind';
import type { BlockProps } from './Block';
+import { HashLinkButton, hashLinkButtonWrapperStyles } from './HashLinkButton';
import { Inlines } from './Inlines';
import { getBlockTextStyle } from './spacing';
+import { getTextAlignment } from './utils';
export function Heading(props: BlockProps) {
const { block, style, context, ...rest } = props;
@@ -20,51 +21,29 @@ export function Heading(props: BlockProps) {
return (
-
-
-
-
-
+
+
) {
'rounded-md',
hasHeading ? 'rounded-l' : null,
'straight-corners:rounded-none',
+ 'circular-corners:rounded-xl',
'overflow-hidden',
hasHeading ? ['border-l-2', hintStyle.containerWithHeader] : hintStyle.container,
diff --git a/packages/gitbook/src/components/DocumentView/Images.tsx b/packages/gitbook/src/components/DocumentView/Images.tsx
index baaab50bdc..91d869002e 100644
--- a/packages/gitbook/src/components/DocumentView/Images.tsx
+++ b/packages/gitbook/src/components/DocumentView/Images.tsx
@@ -1,9 +1,4 @@
-import type {
- DocumentBlockImage,
- DocumentBlockImageDimension,
- DocumentBlockImages,
- JSONDocument,
-} from '@gitbook/api';
+import type { DocumentBlockImage, DocumentBlockImages, JSONDocument, Length } from '@gitbook/api';
import { Image, type ImageResponsiveSize } from '@/components/utils';
import { resolveContentRef } from '@/lib/references';
@@ -29,7 +24,7 @@ export function Images(props: BlockProps) {
align === 'center' && 'justify-center',
align === 'right' && 'justify-end',
align === 'left' && 'justify-start',
- isMultipleImages && ['grid', 'grid-flow-col', 'max-w-none']
+ isMultipleImages && ['grid', 'grid-flow-col']
)}
>
{block.nodes.map((node: any, _i: number) => (
@@ -119,7 +114,7 @@ async function ImageBlock(props: {
* When using relative values, the converted dimension will be relative to the parent element's size.
*/
function getImageDimension(
- dimension: DocumentBlockImageDimension | undefined,
+ dimension: Length | undefined,
defaultValue: DefaultValue
): string | DefaultValue {
if (typeof dimension === 'number') {
diff --git a/packages/gitbook/src/components/DocumentView/Inline.tsx b/packages/gitbook/src/components/DocumentView/Inline.tsx
index 7330b9e6b5..84b56decfc 100644
--- a/packages/gitbook/src/components/DocumentView/Inline.tsx
+++ b/packages/gitbook/src/components/DocumentView/Inline.tsx
@@ -1,18 +1,11 @@
-import type {
- DocumentInline,
- DocumentInlineAnnotation,
- DocumentInlineEmoji,
- DocumentInlineImage,
- DocumentInlineLink,
- DocumentInlineMath,
- DocumentInlineMention,
- JSONDocument,
-} from '@gitbook/api';
-import assertNever from 'assert-never';
+import type { DocumentInline, JSONDocument } from '@gitbook/api';
+import { nullIfNever } from '@/lib/typescript';
import { Annotation } from './Annotation/Annotation';
import type { DocumentContextProps } from './DocumentView';
import { Emoji } from './Emoji';
+import { InlineButton } from './InlineButton';
+import { InlineIcon } from './InlineIcon';
import { InlineImage } from './InlineImage';
import { InlineLink } from './InlineLink';
import { InlineMath } from './Math';
@@ -37,15 +30,7 @@ export interface InlineProps extends DocumentContextPr
children?: React.ReactNode;
}
-export function Inline<
- T extends
- | DocumentInlineImage
- | DocumentInlineAnnotation
- | DocumentInlineEmoji
- | DocumentInlineLink
- | DocumentInlineMath
- | DocumentInlineMention,
->(props: InlineProps) {
+export function Inline(props: InlineProps) {
const { inline, ...contextProps } = props;
switch (inline.type) {
@@ -61,7 +46,15 @@ export function Inline<
return ;
case 'inline-image':
return ;
+ case 'button':
+ return ;
+ case 'icon':
+ return ;
+ case 'expression':
+ // The GitBook API should take care of evaluating expressions.
+ // We should never need to render them.
+ return null;
default:
- assertNever(inline);
+ return nullIfNever(inline);
}
}
diff --git a/packages/gitbook/src/components/DocumentView/InlineButton.tsx b/packages/gitbook/src/components/DocumentView/InlineButton.tsx
new file mode 100644
index 0000000000..fad6d589ae
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineButton.tsx
@@ -0,0 +1,40 @@
+import { resolveContentRef } from '@/lib/references';
+import * as api from '@gitbook/api';
+import type { IconName } from '@gitbook/icons';
+import { Button } from '../primitives';
+import type { InlineProps } from './Inline';
+
+export async function InlineButton(props: InlineProps) {
+ const { inline, context } = props;
+
+ if (!context.contentContext) {
+ throw new Error('InlineButton requires a contentContext');
+ }
+
+ const resolved = await resolveContentRef(inline.data.ref, context.contentContext);
+
+ if (!resolved) {
+ return null;
+ }
+
+ return (
+ // Set the leading to have some vertical space between adjacent buttons
+
+
+
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineIcon.tsx b/packages/gitbook/src/components/DocumentView/InlineIcon.tsx
new file mode 100644
index 0000000000..0eca373f69
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineIcon.tsx
@@ -0,0 +1,10 @@
+import type { DocumentInlineIcon } from '@gitbook/api';
+
+import { Icon, type IconName } from '@gitbook/icons';
+import type { InlineProps } from './Inline';
+
+export async function InlineIcon(props: InlineProps) {
+ const { inline } = props;
+
+ return ;
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineImage.tsx b/packages/gitbook/src/components/DocumentView/InlineImage.tsx
index 4a3663b823..b5797e6513 100644
--- a/packages/gitbook/src/components/DocumentView/InlineImage.tsx
+++ b/packages/gitbook/src/components/DocumentView/InlineImage.tsx
@@ -1,5 +1,5 @@
+import type { GitBookBaseContext } from '@/lib/context';
import type { DocumentInlineImage } from '@gitbook/api';
-import type { GitBookBaseContext } from '@v2/lib/context';
import assertNever from 'assert-never';
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink.tsx b/packages/gitbook/src/components/DocumentView/InlineLink.tsx
deleted file mode 100644
index a12c59e087..0000000000
--- a/packages/gitbook/src/components/DocumentView/InlineLink.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
-
-import { resolveContentRef } from '@/lib/references';
-
-import { StyledLink } from '../primitives';
-import type { InlineProps } from './Inline';
-import { Inlines } from './Inlines';
-
-export async function InlineLink(props: InlineProps) {
- const { inline, document, context, ancestorInlines } = props;
-
- const resolved = context.contentContext
- ? await resolveContentRef(inline.data.ref, context.contentContext)
- : null;
-
- if (!resolved) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- );
-}
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx
new file mode 100644
index 0000000000..7a9645883e
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx
@@ -0,0 +1,127 @@
+import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
+
+import { getSpaceLanguage, tString } from '@/intl/server';
+import { languages } from '@/intl/translations';
+import type { GitBookAnyContext } from '@/lib/context';
+import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
+import { Icon } from '@gitbook/icons';
+import { StyledLink } from '../../primitives';
+import type { InlineProps } from '../Inline';
+import { Inlines } from '../Inlines';
+import { InlineLinkTooltip } from './InlineLinkTooltip';
+
+export async function InlineLink(props: InlineProps) {
+ const { inline, document, context, ancestorInlines } = props;
+
+ const resolved = context.contentContext
+ ? await resolveContentRef(inline.data.ref, context.contentContext, {
+ // We don't want to resolve the anchor text here, as it can be very expensive and will block rendering if there is a lot of anchors link.
+ resolveAnchorText: false,
+ })
+ : null;
+
+ if (!context.contentContext || !resolved) {
+ return (
+
+
+
+ );
+ }
+ const isExternal = inline.data.ref.kind === 'url';
+ const content = (
+
+
+ {isExternal ? (
+
+ ) : null}
+
+ );
+
+ if (context.shouldRenderLinkPreviews) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return content;
+}
+
+/**
+ * An SSR component that renders a link with a tooltip.
+ * Essentially it pulls the minimum amount of props from the context to render the tooltip.
+ */
+function InlineLinkTooltipWrapper(props: {
+ inline: DocumentInlineLink;
+ context: GitBookAnyContext;
+ children: React.ReactNode;
+ resolved: ResolvedContentRef;
+}) {
+ const { inline, context, resolved, children } = props;
+
+ let breadcrumbs = resolved.ancestors ?? [];
+ const language =
+ 'customization' in context ? getSpaceLanguage(context.customization) : languages.en;
+ const isExternal = inline.data.ref.kind === 'url';
+ const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined;
+ if (isExternal) {
+ breadcrumbs = [
+ {
+ label: tString(language, 'link_tooltip_external_link'),
+ },
+ ];
+ }
+ if (isSamePage) {
+ breadcrumbs = [
+ {
+ label: tString(language, 'link_tooltip_page_anchor'),
+ icon: ,
+ },
+ ];
+ resolved.subText = undefined;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltip.tsx b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltip.tsx
new file mode 100644
index 0000000000..15ec1c514a
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltip.tsx
@@ -0,0 +1,72 @@
+'use client';
+import dynamic from 'next/dynamic';
+import React from 'react';
+
+const LoadingValueContext = React.createContext(null);
+
+// To avoid polluting the RSC payload with the tooltip implementation,
+// we lazily load it on the client side. This way, the tooltip is only loaded
+// when the user interacts with the link, and it doesn't block the initial render.
+
+const InlineLinkTooltipImpl = dynamic(
+ () => import('./InlineLinkTooltipImpl').then((mod) => mod.InlineLinkTooltipImpl),
+ {
+ // Disable server-side rendering for this component, it's only
+ // visible on user interaction.
+ ssr: false,
+ loading: () => {
+ // The fallback should be the children (the content of the link),
+ // but as next/dynamic is aiming for feature parity with React.lazy,
+ // it doesn't support passing children to the loading component.
+ // https://github.com/vercel/next.js/issues/7906
+ const children = React.useContext(LoadingValueContext);
+ return <>{children}>;
+ },
+ }
+);
+
+/**
+ * Tooltip for inline links. It's lazily loaded to avoid blocking the initial render
+ * and polluting the RSC payload.
+ *
+ * The link text and href have already been rendered on the server for good SEO,
+ * so we can be as lazy as possible with the tooltip.
+ */
+export function InlineLinkTooltip(props: {
+ isSamePage: boolean;
+ isExternal: boolean;
+ breadcrumbs: Array<{ href?: string; label: string; icon?: React.ReactNode }>;
+ target: {
+ href: string;
+ text: string;
+ subText?: string;
+ icon?: React.ReactNode;
+ };
+ openInNewTabLabel: string;
+ children: React.ReactNode;
+}) {
+ const { children, ...rest } = props;
+ const [shouldLoad, setShouldLoad] = React.useState(false);
+
+ // Once the browser is idle, we set shouldLoad to true.
+ // NOTE: to be slightly more performant, we could load when a link is hovered.
+ // But I found this was too much of a delay for the tooltip to appear.
+ // Loading on idle is a good compromise, as it allows the initial render to be fast,
+ // while still loading the tooltip in the background and not polluting the RSC payload.
+ React.useEffect(() => {
+ if ('requestIdleCallback' in window) {
+ (window as globalThis.Window).requestIdleCallback(() => setShouldLoad(true));
+ } else {
+ // fallback for old browsers
+ setTimeout(() => setShouldLoad(true), 2000);
+ }
+ }, []);
+
+ return shouldLoad ? (
+
+ {children}
+
+ ) : (
+ children
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltipImpl.tsx b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltipImpl.tsx
new file mode 100644
index 0000000000..22547cefc7
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLinkTooltipImpl.tsx
@@ -0,0 +1,109 @@
+'use client';
+import { tcls } from '@/lib/tailwind';
+import { Icon } from '@gitbook/icons';
+import * as Tooltip from '@radix-ui/react-tooltip';
+import { Fragment } from 'react';
+import { Button, StyledLink } from '../../primitives';
+
+export function InlineLinkTooltipImpl(props: {
+ isSamePage: boolean;
+ isExternal: boolean;
+ breadcrumbs: Array<{ href?: string; label: string; icon?: React.ReactNode }>;
+ target: {
+ href: string;
+ text: string;
+ subText?: string;
+ icon?: React.ReactNode;
+ };
+ openInNewTabLabel: string;
+ children: React.ReactNode;
+}) {
+ const { isSamePage, isExternal, openInNewTabLabel, target, breadcrumbs, children } = props;
+
+ return (
+
+
+ {children}
+
+
+
+
+
+
+ {breadcrumbs && breadcrumbs.length > 0 ? (
+
+ {breadcrumbs.map((crumb, index) => {
+ const Tag = crumb.href ? StyledLink : 'div';
+
+ return (
+
+ {index !== 0 ? (
+
+ ) : null}
+
+ {crumb.icon ? (
+
+ {crumb.icon}
+
+ ) : null}
+ {crumb.label}
+
+
+ );
+ })}
+
+ ) : null}
+
+ {target.icon ? (
+
+ {target.icon}
+
+ ) : null}
+ {target.text}
+
+
+ {!isSamePage && target.href ? (
+
+ ) : null}
+
+ {target.subText ? (
+ {target.subText}
+ ) : null}
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink/index.ts b/packages/gitbook/src/components/DocumentView/InlineLink/index.ts
new file mode 100644
index 0000000000..1d7f30120b
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/InlineLink/index.ts
@@ -0,0 +1 @@
+export * from './InlineLink';
diff --git a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
index 6ebec6f030..8e44f2b1ff 100644
--- a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
+++ b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
@@ -1,12 +1,12 @@
+import { GITBOOK_INTEGRATIONS_HOST } from '@/lib/env';
import { tcls } from '@/lib/tailwind';
import type { DocumentBlockIntegration, RenderIntegrationUI } from '@gitbook/api';
import { ContentKit, ContentKitOutput } from '@gitbook/react-contentkit';
-import { GITBOOK_INTEGRATIONS_HOST } from '@v2/lib/env';
import type { BlockProps } from '../Block';
import './contentkit.css';
-import { getDataOrNull } from '@v2/lib/data';
import { contentKitServerContext } from './contentkit';
+import { fetchSafeIntegrationUI } from './render';
import { renderIntegrationUi } from './server-actions';
export async function IntegrationBlock(props: BlockProps) {
@@ -16,8 +16,6 @@ export async function IntegrationBlock(props: BlockProps
+
+ Unexpected error with integration {block.data.integration}:{' '}
+ {initialResponse.error.message}
+
+
+ );
+ }
+ const initialOutput = initialResponse.data;
+ if (initialOutput.type === 'complete') {
return null;
}
diff --git a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx
index fb692c6824..3d1f691116 100644
--- a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx
+++ b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx
@@ -20,6 +20,9 @@ export const contentKitServerContext: ContentKitServerContext = {
'link-external': (props) => ,
eye: (props) => ,
lock: (props) => ,
+ check: (props) => ,
+ 'check-circle': (props) => ,
+ 'eye-off': (props) => ,
},
codeBlock: (props) => {
return ;
diff --git a/packages/gitbook/src/components/DocumentView/Integration/render.ts b/packages/gitbook/src/components/DocumentView/Integration/render.ts
new file mode 100644
index 0000000000..86c42d1a6a
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/Integration/render.ts
@@ -0,0 +1,34 @@
+import type { GitBookBaseContext } from '@/lib/context';
+import { ignoreDataFetcherErrors } from '@/lib/data';
+import type { RenderIntegrationUI } from '@gitbook/api';
+
+/**
+ * Render an integration UI while ignoring some errors.
+ */
+export async function fetchSafeIntegrationUI(
+ context: GitBookBaseContext,
+ {
+ integrationName,
+ request,
+ }: {
+ integrationName: string;
+ request: RenderIntegrationUI;
+ }
+) {
+ const output = await ignoreDataFetcherErrors(
+ context.dataFetcher.renderIntegrationUi({
+ integrationName,
+ request,
+ }),
+
+ // The API can respond with certain errors that are expected to happen.
+ [
+ 404, // Integration has been uninstalled
+ 400, // Integration is rejecting its own request
+ 422, // Integration is triggering an invalid request, failing at the validation step
+ 502, // Integration is failing in an unexpected way
+ ]
+ );
+
+ return output;
+}
diff --git a/packages/gitbook/src/components/DocumentView/Integration/server-actions.tsx b/packages/gitbook/src/components/DocumentView/Integration/server-actions.tsx
index 54180e5c89..6487ec68c4 100644
--- a/packages/gitbook/src/components/DocumentView/Integration/server-actions.tsx
+++ b/packages/gitbook/src/components/DocumentView/Integration/server-actions.tsx
@@ -1,12 +1,10 @@
'use server';
-import { getV1BaseContext } from '@/lib/v1';
-import { isV2 } from '@/lib/v2';
+import { getServerActionBaseContext } from '@/lib/server-actions';
import type { RenderIntegrationUI } from '@gitbook/api';
import { ContentKitOutput } from '@gitbook/react-contentkit';
-import { throwIfDataError } from '@v2/lib/data';
-import { getServerActionBaseContext } from '@v2/lib/server-actions';
import { contentKitServerContext } from './contentkit';
+import { fetchSafeIntegrationUI } from './render';
/**
* Server action to render an integration UI request from .
@@ -21,17 +19,20 @@ export async function renderIntegrationUi({
};
request: RenderIntegrationUI;
}) {
- const serverAction = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
+ const serverAction = await getServerActionBaseContext();
+ const output = await fetchSafeIntegrationUI(serverAction, {
+ integrationName: renderContext.integrationName,
+ request,
+ });
- const output = await throwIfDataError(
- serverAction.dataFetcher.renderIntegrationUi({
- integrationName: renderContext.integrationName,
- request,
- })
- );
+ if (output.error) {
+ return {
+ error: output.error.message,
+ };
+ }
return {
- children: ,
- output: output,
+ children: ,
+ output: output.data,
};
}
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIOperation.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIOperation.tsx
index 294dd876cd..539bbba2cf 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIOperation.tsx
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIOperation.tsx
@@ -1,18 +1,14 @@
-import type { JSONDocument } from '@gitbook/api';
-import { Icon } from '@gitbook/icons';
import { OpenAPIOperation as BaseOpenAPIOperation } from '@gitbook/react-openapi';
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
import { tcls } from '@/lib/tailwind';
import type { BlockProps } from '../Block';
-import { PlainCodeBlock } from '../CodeBlock';
-import { DocumentView } from '../DocumentView';
-import { Heading } from '../Heading';
import './scalar.css';
import './style.css';
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
+import { getOpenAPIContext } from './context';
/**
* Render an openapi block or an openapi-operation block.
@@ -27,7 +23,7 @@ export async function OpenAPIOperation(props: BlockProps) {
- const { block, context } = props;
+ const { block, context, style } = props;
if (!context.contentContext) {
return null;
@@ -40,11 +36,9 @@ async function OpenAPIOperationBody(props: BlockProps
if (error) {
return (
-
-
- Error with {specUrl}: {error.message}
-
-
+
+ Error while loading OpenAPI operation — {error.message}
+
);
}
@@ -55,56 +49,7 @@ async function OpenAPIOperationBody(props: BlockProps
return (
,
- chevronRight: ,
- plus: ,
- },
- renderCodeBlock: (codeProps) => ,
- renderDocument: (documentProps) => (
-
- ),
- renderHeading: (headingProps) => (
- div]:mt-0'
- : undefined,
- ])}
- block={{
- object: 'block',
- key: `${block.key}-heading`,
- meta: block.meta,
- data: {},
- type: 'heading-2',
- nodes: [
- {
- key: `${block.key}-heading-text`,
- object: 'text',
- leaves: [
- { text: headingProps.title, object: 'leaf', marks: [] },
- ],
- },
- ],
- }}
- />
- ),
- defaultInteractiveOpened: context.mode === 'print',
- id: block.meta?.id,
- blockKey: block.key,
- }}
+ context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
className="openapi-block"
/>
);
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx
index b2a8415e17..06023a89ce 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx
@@ -1,6 +1,5 @@
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
import { tcls } from '@/lib/tailwind';
-import { Icon } from '@gitbook/icons';
import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
import type { BlockProps } from '../Block';
@@ -8,6 +7,7 @@ import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
+import { getOpenAPIContext } from './context';
/**
* Render an openapi-schemas block.
@@ -49,19 +49,9 @@ async function OpenAPISchemasBody(props: BlockProps) {
return (
,
- chevronRight: ,
- plus: ,
- },
- defaultInteractiveOpened: context.mode === 'print',
- id: block.meta?.id,
- blockKey: block.key,
- }}
+ context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
className="openapi-block"
/>
);
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx
new file mode 100644
index 0000000000..3453cf8d77
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx
@@ -0,0 +1,58 @@
+import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
+
+import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
+import { tcls } from '@/lib/tailwind';
+
+import type { BlockProps } from '../Block';
+
+import './scalar.css';
+import './style.css';
+import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
+import { getOpenAPIContext } from './context';
+
+/**
+ * Render an openapi block or an openapi-webhook block.
+ */
+export async function OpenAPIWebhook(props: BlockProps) {
+ const { style } = props;
+ return (
+
+
+
+ );
+}
+
+async function OpenAPIWebhookBody(props: BlockProps) {
+ const { block, context } = props;
+
+ if (!context.contentContext) {
+ return null;
+ }
+
+ const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
+ block,
+ context: context.contentContext,
+ });
+
+ if (error) {
+ return (
+
+
+ Error with {specUrl}: {error.message}
+
+
+ );
+ }
+
+ if (!data || !specUrl) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx
new file mode 100644
index 0000000000..8acb3a9047
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx
@@ -0,0 +1,88 @@
+import type { JSONDocument } from '@gitbook/api';
+import { Icon } from '@gitbook/icons';
+import { type OpenAPIContextInput, checkIsValidLocale } from '@gitbook/react-openapi';
+
+import { tcls } from '@/lib/tailwind';
+
+import type { BlockProps } from '../Block';
+import { PlainCodeBlock } from '../CodeBlock';
+import { DocumentView } from '../DocumentView';
+import { Heading } from '../Heading';
+
+import './scalar.css';
+import './style.css';
+import { DEFAULT_LOCALE, getCustomizationLocale } from '@/intl/server';
+import type { GitBookAnyContext } from '@/lib/context';
+import type {
+ AnyOpenAPIOperationsBlock,
+ OpenAPISchemasBlock,
+ OpenAPIWebhookBlock,
+} from '@/lib/openapi/types';
+
+/**
+ * Get the OpenAPI context to render a block.
+ */
+export function getOpenAPIContext(args: {
+ props: BlockProps;
+ specUrl: string;
+ context: GitBookAnyContext | undefined;
+}): OpenAPIContextInput {
+ const { props, specUrl, context } = args;
+ const { block } = props;
+
+ const customization = context && 'customization' in context ? context.customization : null;
+ const customizationLocale = customization
+ ? getCustomizationLocale(customization)
+ : DEFAULT_LOCALE;
+ const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE;
+
+ return {
+ specUrl,
+ icons: {
+ chevronDown: ,
+ chevronRight: ,
+ plus: ,
+ },
+ renderCodeBlock: (codeProps) => ,
+ renderDocument: (documentProps) => (
+
+ ),
+ renderHeading: (headingProps) => (
+ div]:mt-0'
+ : undefined,
+ ])}
+ block={{
+ object: 'block',
+ key: `${block.key}-heading`,
+ meta: block.meta,
+ data: {},
+ type: 'heading-2',
+ nodes: [
+ {
+ key: `${block.key}-heading-text`,
+ object: 'text',
+ leaves: [{ text: headingProps.title, object: 'leaf', marks: [] }],
+ },
+ ],
+ }}
+ />
+ ),
+ defaultInteractiveOpened: props.context.mode === 'print',
+ id: block.meta?.id,
+ blockKey: block.key,
+ locale,
+ };
+}
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/index.ts b/packages/gitbook/src/components/DocumentView/OpenAPI/index.ts
index 25daa70d30..a0b24ee1a6 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/index.ts
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/index.ts
@@ -1,2 +1,3 @@
export * from './OpenAPIOperation';
export * from './OpenAPISchemas';
+export * from './OpenAPIWebhook';
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/scalar.css b/packages/gitbook/src/components/DocumentView/OpenAPI/scalar.css
index 3724dfb951..b7eec226f9 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/scalar.css
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/scalar.css
@@ -1,5 +1,11 @@
@import "@scalar/api-client-react/style.css";
+html,
+body {
+ /** Override Scalar's overscroll-behavior */
+ @apply !overscroll-auto;
+}
+
.light .scalar-modal-layout,
.light .scalar-app,
.light .scalar {
@@ -265,7 +271,7 @@
}
.scalar-activate-button {
@apply flex gap-2 items-center;
- @apply bg-primary-solid text-contrast-primary-solid hover:bg-primary-solid-hover hover:text-contrast-primary-solid-hover contrast-more:ring-1 rounded-md straight-corners:rounded-none place-self-start;
+ @apply bg-primary-solid text-contrast-primary-solid hover:bg-primary-solid-hover hover:text-contrast-primary-solid-hover contrast-more:ring-1 rounded-md straight-corners:rounded-none circular-corners:rounded-full circular-corners:px-3 place-self-start;
@apply ring-1 ring-tint hover:ring-tint-hover;
@apply shadow-sm shadow-tint dark:shadow-tint-1 hover:shadow-md active:shadow-none;
@apply contrast-more:ring-tint-12 contrast-more:hover:ring-2 contrast-more:hover:ring-tint-12;
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
index c0bf8d13c7..5451908884 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
@@ -1,10 +1,16 @@
/* Layout Components */
-.openapi-operation {
+.openapi-operation,
+.openapi-schemas,
+.openapi-webhook {
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
}
.openapi-schemas {
- @apply flex flex-col mb-14 flex-1;
+ @apply flex flex-col mb-14 gap-0 flex-1;
+}
+
+.openapi-schemas-title {
+ @apply tabular-nums text-[0.813rem] leading-4 font-mono shrink-0 font-medium text-tint-strong;
}
.openapi-columns {
@@ -17,7 +23,7 @@
}
.openapi-summary {
- @apply flex flex-col items-start justify-start gap-3;
+ @apply flex flex-col items-start justify-start gap-3 scroll-m-12;
}
.openapi-summary-tags {
@@ -29,10 +35,6 @@
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
}
-.openapi-stability-stable {
- @apply text-green-600 dark:text-green-300 bg-green-50 dark:bg-green-900/6 ring-green-500/5;
-}
-
.openapi-stability-alpha {
@apply text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-900/6 ring-amber-500/5;
}
@@ -49,10 +51,7 @@
@apply font-semibold font-mono truncate;
}
-.openapi-description.openapi-markdown {
- @apply prose-sm text-[0.938rem];
-}
-
+.openapi-description.openapi-markdown,
.openapi-description.openapi-markdown code {
@apply prose-sm;
}
@@ -92,21 +91,23 @@
}
/* Method Tags */
-.openapi-method {
- @apply rounded uppercase font-mono font-bold text-xs px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap;
+.openapi-method,
+.openapi-statuscode {
+ @apply rounded uppercase font-mono items-center shrink-0 font-semibold text-[0.813rem] px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap;
}
-.openapi-method-get {
- /* @apply bg-[hsl(215,54%,86%)] dark:bg-[hsla(215,54%,45%,0.24)] dark:text-[hsl(215,54%,86%)]; */
+.openapi-method-get,
+.openapi-statuscode-success {
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100;
}
-.openapi-method-post {
- /* @apply bg-[hsl(120,25%,80%)] dark:bg-[hsla(120,54%,32%,0.24)] dark:text-[hsl(120,25%,80%)]; */
+.openapi-method-post,
+.openapi-statuscode-redirection {
@apply bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100;
}
-.openapi-method-put {
+.openapi-method-put,
+.openapi-statuscode-informational {
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100;
}
@@ -114,8 +115,9 @@
@apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100;
}
-.openapi-method-delete {
- @apply bg-pomegranate-100 text-pomegranate-800 dark:bg-pomegranate-900 dark:text-pomegranate-100;
+.openapi-method-delete,
+.openapi-statuscode-error {
+ @apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100;
}
.openapi-method-head,
@@ -143,7 +145,7 @@
}
.openapi-column-preview-body {
- @apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header-sections:page-has-toc:top-32 page-api-block:xl:max-2xl:top-32 print-mode:static;
+ @apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header:xl:max-2xl:top-32 site-header-sections:top-32 site-header-sections:xl:max-2xl:top-44 print-mode:static;
}
.openapi-column-preview pre {
@@ -155,45 +157,30 @@
/* unstyled */
}
-.openapi-schema-properties {
- @apply flex flex-col;
-}
-
-.openapi-schema {
- @apply py-2.5 flex flex-col gap-2;
-}
-
-.openapi-section-body .openapi-schema-properties {
- @apply divide-y divide-tint-subtle;
+.openapi-schema-root-description.openapi-markdown {
+ @apply prose-sm text-balance mt-1.5 !text-[0.813rem] text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
}
-.openapi-disclosure-group-panel > .openapi-schema-properties > *:first-child > .openapi-schema {
- @apply pt-0;
+.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description {
+ @apply px-2.5 pt-2.5 mt-0 !text-sm;
}
-.openapi-responsebody > .openapi-schema-properties > .openapi-schema:last-child {
- @apply pb-0;
+.openapi-schema-properties {
+ @apply flex flex-col;
}
-.openapi-responsebody > .openapi-schema-properties > .openapi-schema:only-child {
- @apply py-0;
+.openapi-schema,
+.openapi-disclosure {
+ @apply py-2.5 flex flex-col gap-2;
}
.openapi-schema-properties .openapi-schema:last-child {
@apply border-b-0;
}
-.openapi-schema-properties .openapi-schema-opened {
- @apply pb-3;
-}
-
-.openapi-schema > .openapi-schema-properties {
- @apply mt-3;
-}
-
/* Schema Presentation */
.openapi-schema-presentation {
- @apply flex flex-col gap-1.5 font-normal;
+ @apply flex flex-col gap-1 font-normal;
}
.openapi-schema-properties:last-child {
@@ -203,7 +190,7 @@
.openapi-schema-name {
/* To make double click on the property name select only the name,
we disable selection on the parent and re-enable it on the children. */
- @apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap;
+ @apply select-none text-sm text-balance *:whitespace-nowrap flex flex-wrap gap-y-1.5 gap-x-2.5;
}
.openapi-schema-name .openapi-deprecated {
@@ -211,7 +198,7 @@
}
.openapi-schema-propertyname {
- @apply select-all font-mono font-normal text-tint-strong;
+ @apply select-all font-mono font-semibold text-tint-strong;
}
.openapi-schema-propertyname[data-deprecated="true"] {
@@ -219,19 +206,23 @@
}
.openapi-schema-required {
- @apply text-warning-subtle text-[0.813rem];
+ @apply text-warning-subtle text-[0.813rem] lowercase;
}
.openapi-schema-optional {
- @apply text-info-subtle text-[0.813rem];
+ @apply text-tint-subtle text-[0.813rem] lowercase;
}
.openapi-schema-readonly {
- @apply text-primary-subtle/9 text-[0.813rem];
+ @apply text-primary-subtle/9 text-[0.813rem] lowercase;
}
.openapi-schema-writeonly {
- @apply text-success dark:text-success-subtle/9 text-[0.813rem];
+ @apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase;
+}
+
+.openapi-schema-types {
+ @apply flex items-baseline flex-wrap gap-1;
}
.openapi-schema-type {
@@ -265,15 +256,11 @@
/* Schema Enum */
.openapi-schema-enum {
- @apply flex flex-row text-sm leading-relaxed gap-2 flex-wrap text-tint;
-}
-
-.openapi-schema-enum-list {
- @apply flex flex-row gap-1.5 items-center;
+ @apply text-sm leading-relaxed max-w-full text-tint;
}
.openapi-schema-enum-value {
- @apply text-sm;
+ @apply text-sm mr-1.5;
}
.openapi-schema-enum-value:first-child {
@@ -286,7 +273,7 @@
/* Schema Description */
.openapi-schema-description.openapi-markdown {
- @apply prose-sm text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
+ @apply prose-sm text-tint overflow-hidden text-pretty !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-schema-description.openapi-markdown pre:has(code) {
@@ -306,14 +293,16 @@
/* Schema Examples */
.openapi-schema-example,
-.openapi-schema-pattern {
+.openapi-schema-pattern,
+.openapi-schema-default {
@apply prose-sm text-tint;
}
.openapi-schema-example code,
.openapi-schema-pattern code,
-.openapi-schema-enum-value code {
- @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
+.openapi-schema-enum-value code,
+.openapi-schema-default code {
+ @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
}
/* Authentication */
@@ -321,12 +310,32 @@
@apply py-2 border-b border-tint-subtle max-w-full flex-1;
}
+.openapi-securities-oauth-flows {
+ @apply flex flex-col gap-2 divide-y divide-tint-subtle;
+}
+
+.openapi-securities-oauth-content {
+ @apply prose *:!prose-sm *:text-tint;
+}
+
+.openapi-securities-oauth-content.openapi-markdown code {
+ @apply text-xs;
+}
+
+.openapi-securities-oauth-content ul {
+ @apply !my-0;
+}
+
+.openapi-securities-url {
+ @apply ml-0.5 px-0.5 rounded hover:bg-tint transition-colors;
+}
+
.openapi-securities-body {
@apply flex flex-col gap-2;
}
.openapi-securities-description.openapi-markdown {
- @apply prose-sm text-tint !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
+ @apply prose-sm text-tint !font-normal select-text text-pretty prose-strong:font-semibold prose-strong:text-inherit;
}
.openapi-securities-label {
@@ -349,15 +358,20 @@
.openapi-requestbody-header-content {
/* unstyled */
+ @apply flex flex-row items-center gap-2.5;
+}
+
+.openapi-requestbody-header-type {
+ @apply text-tint select-text text-[0.813rem] font-mono font-normal [word-spacing:-0.25rem];
}
.openapi-requestbody-description.openapi-markdown {
- @apply prose-sm text-tint !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit;
+ @apply prose-sm text-tint !font-normal text-pretty select-text prose-strong:font-semibold prose-strong:text-inherit;
}
/* Responses */
.openapi-responses-header {
- @apply py-2 border-b border-tint-subtle max-w-full flex-1;
+ @apply py-2 max-w-full flex-1;
}
.openapi-responses-header-content {
@@ -365,31 +379,25 @@
}
.openapi-response-tab-content {
- @apply overflow-hidden max-w-full flex items-baseline gap-2;
+ @apply flex items-baseline truncate grow shrink max-w-max basis-[60%] mr-auto;
+ @apply text-left text-pretty relative leading-tight text-tint select-text;
}
.openapi-response-description.openapi-markdown {
- @apply text-left prose-sm text-[0.813rem] h-auto relative leading-[1.125rem] text-tint !font-normal truncate select-text prose-strong:font-semibold prose-strong:text-inherit;
-}
-
-.openapi-response-description.openapi-markdown::-webkit-scrollbar {
- display: none;
+ @apply text-left truncate prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
}
-.openapi-response-description p {
- @apply truncate max-w-full inline pr-1;
+.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-tab-content {
+ @apply basis-full;
}
-.openapi-response-statuscode {
- @apply tabular-nums text-sm font-normal font-mono shrink-0;
-}
-
-.openapi-response-content-type {
- @apply text-xs text-tint-8 ml-auto shrink-0;
+.openapi-disclosure-group-trigger[aria-expanded="true"]
+ .openapi-response-description.openapi-markdown {
+ @apply whitespace-normal;
}
.openapi-response-body {
- @apply flex flex-col gap-3;
+ @apply flex flex-col;
}
/* Response Body and Headers */
@@ -406,22 +414,39 @@
@apply px-3 py-1;
}
-.openapi-responsebody-header-content,
-.openapi-responseheaders-header-content {
- /* unstyled */
+/* Code Sample */
+.openapi-codesample-header {
+ @apply flex flex-row items-center;
}
-/* Code Sample */
-.openapi-codesample {
- @apply border rounded bg-tint border-tint-subtle;
+.openapi-response-media-types-examples-footer-content {
+ @apply flex flex-row items-center gap-2.5;
}
-.openapi-codesample-header {
- @apply flex flex-row items-center;
+.openapi-panel-heading,
+.openapi-codesample-header,
+.openapi-response-examples-header {
+ @apply border-b border-tint-subtle;
+}
+
+.openapi-response-examples-header .openapi-select > button {
+ @apply max-w-full overflow-hidden shrink pl-0.5 py-0.5;
+}
+
+.openapi-response-examples-header .openapi-select > button .openapi-statuscode {
+ @apply h-full;
}
.openapi-codesample-header-content {
- @apply flex flex-row items-center h-fit;
+ @apply flex flex-row items-center justify-between h-fit p-2.5;
+}
+
+.openapi-codesample-header-content .openapi-path {
+ @apply flex items-center font-mono text-[0.813rem] gap-1 h-fit *:truncate overflow-x-auto min-w-0 max-w-full font-normal text-tint-strong;
+}
+
+.openapi-codesample-header-content .openapi-path .openapi-path-variable {
+ @apply text-[0.813rem];
}
.openapi-codesample-footer {
@@ -434,7 +459,7 @@
/* Path */
.openapi-path {
- @apply flex items-start text-sm gap-2 h-fit overflow-x-auto min-w-0 max-w-full;
+ @apply flex items-center text-sm gap-2 h-fit overflow-x-auto min-w-0 max-w-full;
scrollbar-width: none;
-ms-overflow-style: none;
}
@@ -448,12 +473,12 @@
}
.openapi-path .openapi-method {
- @apply text-[0.813rem] m-0 mt-0.5 items-center flex px-2;
+ @apply m-0 mt-0.5 items-center flex px-1;
}
.openapi-path-title {
@apply flex-1 relative font-normal text-left font-mono text-tint-strong/10;
- @apply py-0.5 px-1 rounded hover:bg-tint cursor-pointer transition-colors;
+ @apply py-0.5 px-1 rounded hover:bg-tint transition-colors;
@apply whitespace-nowrap md:whitespace-normal;
}
@@ -465,14 +490,6 @@
display: none;
}
-/* .openapi-path-copy {
- @apply absolute opacity-0 h-fit right-0 top-1/2 -translate-y-1/2 bg-light dark:bg-dark border rounded-md border-tint-subtle px-1.5 py-0;
-}
-
-.openapi-path-title:hover .openapi-path-copy {
- @apply opacity-11;
-} */
-
.openapi-path-title em {
@apply not-italic text-primary font-medium;
}
@@ -485,18 +502,131 @@
@apply flex flex-row items-center py-2 px-3 justify-end border-t border-tint-subtle;
}
-/* Response Example */
-.openapi-response-example {
- @apply border rounded bg-tint border-tint-subtle;
+/* Panel */
+.openapi-panel,
+.openapi-codesample,
+.openapi-response-examples {
+ @apply border rounded-md straight-corners:rounded-none circular-corners:rounded-xl bg-tint-subtle border-tint-subtle shadow-sm;
+}
+
+.openapi-panel pre,
+.openapi-codesample pre,
+.openapi-response-examples pre {
+ @apply bg-transparent border-none rounded-none shadow-none;
+}
+
+.openapi-panel-heading {
+ @apply font-medium px-4 py-2 text-xs uppercase;
+}
+
+.openapi-panel-body {
+ @apply relative;
+}
+
+.openapi-panel-footer,
+.openapi-codesample-footer {
+ @apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint empty:hidden;
+}
+
+.openapi-panel-footer .openapi-markdown {
+ @apply text-[0.813rem] text-tint;
+}
+
+/* Example */
+.openapi-response-examples-header {
+ @apply flex flex-row items-center p-2.5;
+}
+
+.openapi-response-examples-header-content {
+ @apply max-w-full overflow-hidden truncate;
+}
+
+.openapi-response-examples-statuscode-title {
+ @apply flex items-center;
+}
+
+.openapi-response-examples-header .openapi-select > button,
+.openapi-response-examples-header .openapi-markdown,
+.openapi-response-examples-statuscode-title {
+ @apply text-[0.813rem] truncate text-tint font-normal;
+}
+
+.openapi-response-examples-panel,
+.openapi-codesample-panel {
+ @apply flex-1 text-sm relative focus-visible:outline-none;
}
-.openapi-response-example-empty {
+.openapi-example-empty {
@apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center;
}
/* Common Elements */
.openapi-select {
- @apply max-w-60 rounded font-mono text-xs leading-6 px-1 py-0.5 truncate border border-tint-subtle bg-tint;
+ @apply w-auto max-w-full;
+}
+
+/* Prevent react-aria popover from setting overflow:auto on body */
+body:has(.openapi-select-popover) {
+ overflow: unset !important;
+}
+
+.openapi-select > button {
+ @apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 text-tint-strong max-w-32 rounded text-xs p-1.5 leading-none border border-tint-subtle bg-tint;
+ @apply hover:bg-tint-hover transition-all;
+}
+
+.openapi-select > button[data-focused="true"] {
+ @apply outline-primary -outline-offset-1 outline outline-1;
+}
+
+.openapi-select > button > span.react-aria-SelectValue {
+ @apply shrink truncate flex items-center;
+}
+
+.openapi-select > button > span.react-aria-SelectValue span:not(.openapi-statuscode) {
+ @apply truncate;
+}
+
+.openapi-select > button .openapi-markdown {
+ @apply *:leading-none;
+}
+
+.openapi-select > button .gb-icon {
+ @apply size-2.5 shrink-0;
+}
+
+.openapi-select-popover {
+ @apply min-w-32 z-10 max-w-[max(20rem,var(--trigger-width))] overflow-x-hidden max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md circular-corners:rounded-xl straight-corners:rounded-none;
+ @apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1;
+}
+
+.openapi-select-popover[data-entering] {
+ animation: popover-enter 0.2s ease-in-out;
+}
+
+.openapi-select-popover[data-exiting] {
+ animation: popover-leave 0.2s ease-in-out;
+}
+
+.openapi-select-item {
+ @apply text-sm flex items-center cursor-pointer px-1.5 overflow-hidden py-1 *:truncate text-tint ring-0 border-none rounded !outline-none;
+ @apply hover:bg-tint-hover theme-gradient:hover:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
+}
+
+.openapi-select button .openapi-markdown,
+.openapi-select-item .openapi-markdown {
+ @apply text-[0.813rem] *:truncate;
+}
+
+.openapi-select-item-selected,
+.openapi-select-item-selected .openapi-markdown {
+ @apply text-primary-subtle hover:text-primary hover:bg-primary-hover;
+ @apply theme-muted:hover:bg-primary-active theme-gradient:hover:bg-primary-active tint:font-semibold;
+ @apply contrast-more:text-primary contrast-more:hover:text-primary-strong contrast-more:font-semibold;
+}
+
+.openapi-select-listbox {
+ @apply flex flex-col gap-1 focus:ring-0 focus:outline-none;
}
.openapi-select:focus {
@@ -524,6 +654,14 @@
@apply text-tint;
}
+.openapi-section-footer {
+ @apply flex flex-row items-center p-2.5 gap-2.5 text-sm text-tint-strong border-t border-tint-subtle;
+}
+
+.openapi-section-footer-content {
+ @apply text-sm text-tint-strong;
+}
+
.openapi-section-toggle {
@apply text-tint-subtle contrast-more:text-tint-strong;
}
@@ -545,10 +683,9 @@
}
/* Tabs */
+.openapi-panel-header,
.openapi-tabs-list {
- @apply flex flex-row gap-1.5 py-1.5 px-2.5 w-full overflow-x-scroll;
- scrollbar-width: none;
- -ms-overflow-style: none;
+ @apply flex flex-row gap-1.5 py-1.5 px-2.5 w-full overflow-x-auto no-scrollbar;
}
.openapi-tabs-tab {
@@ -556,7 +693,7 @@
}
.openapi-tabs-tab[aria-selected="true"] {
- @apply text-primary after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
+ @apply !text-primary-subtle after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
}
.openapi-tabs-panel {
@@ -564,42 +701,33 @@
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
}
-.openapi-tabs-footer {
- @apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
-}
-
-.openapi-tabs-footer .openapi-markdown {
- @apply text-[0.813rem] text-tint;
-}
-
/* Disclosure group */
.openapi-disclosure-group {
- @apply border-b border-tint-subtle relative;
+ @apply border-tint-subtle transition-all border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none circular-corners:last:rounded-b-xl first:rounded-t-md straight-corners:first:rounded-none circular-corners:first:rounded-t-xl first:border-t relative;
}
-.openapi-disclosure-group-header {
- @apply flex flex-row items-baseline justify-between gap-3 relative;
+.openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover) {
+ @apply bg-tint-subtle;
}
-.openapi-disclosure-group-trigger {
- @apply flex items-baseline relative flex-1 gap-2.5 py-2 truncate -outline-offset-1;
+.openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover):has(.openapi-select:hover) {
+ @apply !bg-transparent;
}
-.openapi-disclosure-group-trigger:disabled {
- @apply cursor-default;
+.openapi-disclosure-group-trigger {
+ @apply flex w-full cursor-pointer items-baseline gap-3 transition-all relative flex-1 p-3 -outline-offset-1;
}
-.openapi-disclosure-group:only-child,
-.openapi-disclosure-group:last-child {
- @apply border-b-0;
+.openapi-disclosure-group-label {
+ @apply flex flex-wrap items-baseline gap-x-3 gap-y-1 flex-1 truncate;
}
-.openapi-disclosure-group-trigger:disabled .openapi-disclosure-group-icon {
- @apply invisible;
+.openapi-disclosure-group-trigger[aria-disabled="true"] {
+ @apply cursor-default hover:bg-inherit;
}
-.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-description {
- @apply whitespace-normal;
+.openapi-disclosure-group-trigger[aria-disabled="true"] .openapi-disclosure-group-icon {
+ @apply invisible;
}
.openapi-disclosure-group-icon > svg {
@@ -611,73 +739,123 @@
}
.openapi-disclosure-group-panel {
- @apply pb-2.5;
+ @apply px-3 transition-all;
}
.openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg {
@apply rotate-90;
}
-.openapi-disclosure-group:hover .openapi-disclosure-group-mediatype {
- @apply opacity-11 flex;
+.openapi-disclosure-group-mediatype:not(:has(.openapi-select)) {
+ @apply text-[0.625rem] font-mono shrink-0 grow-0 text-tint-subtle contrast-more:text-tint;
+}
+
+/* Disclosure */
+.openapi-schemas-disclosure {
+ @apply border-t border-x last:border-b border-tint-subtle !ring-0 first:!rounded-t-xl last:!rounded-b-xl !rounded-none;
}
-.openapi-disclosure-group-mediatype {
- @apply opacity-0 hidden text-xs transition-all duration-200 shrink-0 absolute right-0 top-2.5;
+.openapi-schemas-disclosure > .openapi-disclosure-trigger {
+ @apply flex items-center font-mono transition-all font-normal text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-5 truncate -outline-offset-1;
}
-.openapi-disclosure-group-mediatype > span {
- @apply px-1 bg-tint-6 text-tint-12 rounded-full;
+.openapi-schemas-disclosure > .openapi-disclosure-trigger,
+.openapi-schemas-disclosure .openapi-disclosure-panel {
+ @apply straight-corners:!rounded-none;
+}
+
+.openapi-disclosure-panel {
+ @apply ml-1.5 pl-3 border-l border-tint-subtle;
+}
+
+.openapi-schema .openapi-schema-properties .openapi-schema {
+ @apply animate-fadeIn [animation-fill-mode:both];
+}
+
+.openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg {
+ @apply rotate-90;
}
-/* Disclosure */
.openapi-disclosure-trigger {
- @apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
+ @apply flex flex-row justify-between flex-wrap relative items-start gap-2 text-left -mx-3 px-3 -my-2.5 py-2.5 pr-10;
}
-.openapi-disclosure-trigger span {
- @apply truncate;
+.openapi-disclosure {
+ @apply -mx-3 px-3 py-2.5 transition-all flex flex-col ring-tint-subtle;
}
-.openapi-disclosure svg {
- @apply size-3 shrink-0 transition-transform duration-300;
+.openapi-disclosure:not(
+ .openapi-disclosure-group .openapi-disclosure,
+ .openapi-schema-alternatives .openapi-disclosure,
+ .openapi-schemas-disclosure .openapi-schema.openapi-disclosure
+ ) {
+ @apply rounded-xl;
}
-.openapi-disclosure-trigger[aria-expanded="true"] svg {
- @apply rotate-45;
+.openapi-disclosure .openapi-schemas-disclosure .openapi-schema.openapi-disclosure {
+ @apply !rounded-none;
}
-.openapi-disclosure-trigger[aria-expanded="true"] {
- @apply w-full rounded-lg border-b rounded-b-none;
+.openapi-disclosure:has(> .openapi-disclosure-trigger:hover) {
+ @apply bg-tint-subtle overflow-hidden;
}
-.openapi-disclosure-trigger[aria-expanded="false"] {
- @apply w-auto;
+.openapi-disclosure[data-expanded="true"] {
+ @apply ring-1 shadow-sm;
}
-.openapi-disclosure-panel[aria-hidden="false"] {
- @apply border-b border-x border-tint-subtle rounded-b-lg;
+.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:first-child) {
+ @apply mt-2;
+}
+.openapi-disclosure[data-expanded="true"]:not(.openapi-schemas-disclosure):not(:last-child) {
+ @apply mb-2;
}
-.openapi-disclosure-panel .openapi-schema {
- @apply p-2.5;
+.openapi-disclosure-trigger-label {
+ @apply absolute right-3 px-2 h-5 justify-end shrink-0 ring-tint-subtle truncate text-tint duration-300 transition-all rounded straight-corners:rounded-none circular-corners:rounded-xl flex flex-row gap-1 items-center text-xs;
}
-.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child,
-.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child .openapi-schema-name {
- @apply !m-0;
+.openapi-disclosure-trigger-label span {
+ @apply hidden;
}
-.openapi-disclosure .openapi-schema-properties .openapi-schema-enum {
- @apply pt-0 mt-0;
+.openapi-disclosure-trigger-label svg {
+ @apply size-3 shrink-0 transition-transform duration-300 text-tint-subtle;
}
-.openapi-section-body.openapi-schema.openapi-schema-root {
- @apply space-y-2.5;
+.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label,
+.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label {
+ @apply shadow ring-1 bg-tint-base;
+}
+
+.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label span,
+.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label span {
+ @apply block animate-fadeIn;
+}
+
+@media (hover: none) {
+ /* Make button label always visible on non-hover devices like phones */
+ .openapi-disclosure-trigger-label {
+ @apply relative ring-1 bg-tint-base right-0;
+ }
+ .openapi-disclosure-trigger-label span {
+ @apply block;
+ }
+ .openapi-disclosure-trigger {
+ @apply pr-3;
+ }
+}
+
+.openapi-disclosure-trigger[aria-expanded="true"] svg {
+ @apply rotate-45;
+}
+
+.openapi-disclosure-trigger[aria-expanded="false"] {
+ @apply w-auto;
}
-.openapi-section-schemas {
- @apply border border-tint-subtle rounded-lg;
+.openapi-section-body.openapi-schema.openapi-schema-root {
+ @apply space-y-2.5;
}
.openapi-section-schemas > .openapi-section-body > .openapi-schema-properties > .openapi-schema,
@@ -685,8 +863,18 @@
@apply p-2.5;
}
+.openapi-schema-alternatives {
+ @apply ml-1.5 pl-3 border-l border-tint-subtle;
+}
+.openapi-schema-alternative {
+ @apply relative;
+}
+.openapi-schema-alternative-separator {
+ @apply p-0.5 tracking-wide leading-none uppercase text-[0.625rem] text-tint-subtle whitespace-nowrap absolute -left-3 -bottom-2.5 -translate-x-1/2 z-10 bg-tint-base border-y border-tint-subtle -rotate-6;
+}
+
.openapi-tooltip {
- @apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md font-medium px-1.5 py-0.5 shadow-sm text-[13px];
+ @apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none circular-corners:rounded-lg font-medium px-1.5 py-0.5 shadow-sm text-[13px];
}
.openapi-tooltip svg {
@@ -719,6 +907,32 @@
}
}
+@keyframes popover-enter {
+ 0% {
+ opacity: 0;
+ transform: translateY(4px) scale(0.95);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes popover-leave {
+ from {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(4px) scale(0.95);
+ }
+}
+
.openapi-copy-button {
- @apply hover:brightness-95;
+ @apply hover:brightness-95 cursor-pointer;
+}
+
+.openapi-copy-button[data-disabled="true"] {
+ @apply cursor-default;
}
diff --git a/packages/gitbook/src/components/DocumentView/Paragraph.tsx b/packages/gitbook/src/components/DocumentView/Paragraph.tsx
index d407f74060..e96a8aa7b0 100644
--- a/packages/gitbook/src/components/DocumentView/Paragraph.tsx
+++ b/packages/gitbook/src/components/DocumentView/Paragraph.tsx
@@ -4,12 +4,13 @@ import { tcls } from '@/lib/tailwind';
import type { BlockProps } from './Block';
import { Inlines } from './Inlines';
+import { getTextAlignment } from './utils';
export function Paragraph(props: BlockProps) {
const { block, style, ...contextProps } = props;
return (
-
+
);
diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
index e95ffc7545..8f7a5d8e1e 100644
--- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
+++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
@@ -1,8 +1,7 @@
import type { DocumentBlockReusableContent } from '@gitbook/api';
+import { getDataOrNull } from '@/lib/data';
import { resolveContentRef } from '@/lib/references';
-
-import { getDataOrNull } from '@v2/lib/data';
import type { BlockProps } from './Block';
import { UnwrappedBlocks } from './Blocks';
@@ -13,15 +12,28 @@ export async function ReusableContent(props: BlockProps
);
}
diff --git a/packages/gitbook/src/components/DocumentView/StepperStep.tsx b/packages/gitbook/src/components/DocumentView/StepperStep.tsx
index 46e70ed603..5c7456ad60 100644
--- a/packages/gitbook/src/components/DocumentView/StepperStep.tsx
+++ b/packages/gitbook/src/components/DocumentView/StepperStep.tsx
@@ -34,13 +34,13 @@ export function StepperStep(props: BlockProps) {
{index + 1}
-
+
(record[1], view.coverDefinition)?.[0]
: null;
- const cover =
- coverFile && context.contentContext
- ? await resolveContentRef({ kind: 'file', file: coverFile }, context.contentContext)
- : null;
-
const targetRef = view.targetDefinition
? (record[1].values[view.targetDefinition] as ContentRef)
: null;
- const target =
+
+ const [cover, target] = await Promise.all([
+ coverFile && context.contentContext
+ ? resolveContentRef({ kind: 'file', file: coverFile }, context.contentContext)
+ : null,
targetRef && context.contentContext
- ? await resolveContentRef(targetRef, context.contentContext)
- : null;
+ ? resolveContentRef(targetRef, context.contentContext)
+ : null,
+ ]);
const coverIsSquareOrPortrait =
cover?.file?.dimensions &&
@@ -44,15 +45,15 @@ export async function RecordCard(
div:first-child]:hidden',
'[&_.heading>div]:text-[.8em]',
@@ -143,45 +144,26 @@ export async function RecordCard(
);
- const style = [
- 'group',
- 'grid',
- 'shadow-1xs',
- 'shadow-tint-9/1',
- 'rounded-md',
- 'straight-corners:rounded-none',
- 'dark:shadow-transparent',
- 'z-0',
-
- 'before:pointer-events-none',
- 'before:grid-area-1-1',
- 'before:transition-shadow',
- 'before:w-full',
- 'before:h-full',
- 'before:rounded-[inherit]',
- 'before:ring-1',
- 'before:ring-tint-12/2',
- 'before:z-10',
- 'before:relative',
- ] as ClassValue;
-
if (target && targetRef) {
return (
-
+ // We don't use `Link` directly here because we could end up in a situation where
+ // a link is rendered inside a link, which is not allowed in HTML.
+ // It causes an hydration error in React.
+
+
{body}
-
+
);
}
- return {body};
+ return {body};
}
diff --git a/packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx b/packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx
index 7c1ecb9958..710746a92b 100644
--- a/packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx
+++ b/packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx
@@ -22,6 +22,12 @@ import { FileIcon } from '../FileIcon';
import type { TableRecordKV } from './Table';
import { type VerticalAlignment, getColumnAlignment } from './utils';
+const alignmentMap: Record<'text-left' | 'text-center' | 'text-right', string> = {
+ 'text-left': '[&_*]:text-left text-left',
+ 'text-center': '[&_*]:text-center text-center',
+ 'text-right': '[&_*]:text-right text-right',
+};
+
/**
* Render the value for a column in a record.
*/
@@ -115,7 +121,8 @@ export async function RecordColumnValue(
return {''} ;
}
- const alignment = getColumnAlignment(definition);
+ const horizontalAlignment = getColumnAlignment(definition);
+ const horizontalClasses = alignmentMap[horizontalAlignment];
return (
(
'lg:space-y-3',
'leading-normal',
verticalAlignment,
- alignment === 'right' ? 'text-right' : null,
- alignment === 'center' ? 'text-center' : null,
+ horizontalClasses,
]}
context={context}
blockStyle={['w-full', 'max-w-[unset]']}
@@ -168,7 +174,7 @@ export async function RecordColumnValue(
key={index}
href={ref.href}
target="_blank"
- style={['flex', 'flex-row', 'items-center', 'gap-2']}
+ className="flex flex-row items-center gap-2"
insights={
ref.file
? {
diff --git a/packages/gitbook/src/components/DocumentView/Table/RecordRow.tsx b/packages/gitbook/src/components/DocumentView/Table/RecordRow.tsx
index e39f386cc5..b09760ceb2 100644
--- a/packages/gitbook/src/components/DocumentView/Table/RecordRow.tsx
+++ b/packages/gitbook/src/components/DocumentView/Table/RecordRow.tsx
@@ -15,14 +15,14 @@ export function RecordRow(
fixedColumns: string[];
}
) {
- const { view, autoSizedColumns, fixedColumns, block } = props;
+ const { view, autoSizedColumns, fixedColumns, block, context } = props;
return (
{view.columns.map((column) => {
const columnWidth = getColumnWidth({
column,
- columnWidths: view.columnWidths,
+ columnWidths: context.mode === 'print' ? undefined : view.columnWidths,
autoSizedColumns,
fixedColumns,
});
diff --git a/packages/gitbook/src/components/DocumentView/Table/ViewGrid.tsx b/packages/gitbook/src/components/DocumentView/Table/ViewGrid.tsx
index 65bae8e0b8..606b2305b5 100644
--- a/packages/gitbook/src/components/DocumentView/Table/ViewGrid.tsx
+++ b/packages/gitbook/src/components/DocumentView/Table/ViewGrid.tsx
@@ -13,10 +13,10 @@ import { getColumnAlignment } from './utils';
3. Auto-size is turned off without setting a width, we then default to a fixed width of 100px
*/
export function ViewGrid(props: TableViewProps) {
- const { block, view, records, style } = props;
+ const { block, view, records, style, context } = props;
/* Calculate how many columns are auto-sized vs fixed width */
- const columnWidths = view.columnWidths;
+ const columnWidths = context.mode === 'print' ? undefined : view.columnWidths;
const autoSizedColumns = view.columns.filter((column) => !columnWidths?.[column]);
const fixedColumns = view.columns.filter((column) => columnWidths?.[column]);
@@ -38,36 +38,33 @@ export function ViewGrid(props: TableViewProps) {
className={tcls(
tableWidth,
styles.rowGroup,
- 'straight-corners:rounded-none'
+ 'straight-corners:rounded-none',
+ 'circular-corners:rounded-xl'
)}
>
- {view.columns.map((column) => {
- const alignment = getColumnAlignment(block.data.definition[column]);
- return (
-
- {block.data.definition[column].title}
-
- );
- })}
+ {view.columns.map((column) => (
+
+ {block.data.definition[column].title}
+
+ ))}
)}
diff --git a/packages/gitbook/src/components/DocumentView/Table/styles.ts b/packages/gitbook/src/components/DocumentView/Table/styles.ts
new file mode 100644
index 0000000000..3e33b06837
--- /dev/null
+++ b/packages/gitbook/src/components/DocumentView/Table/styles.ts
@@ -0,0 +1,26 @@
+import type { ClassValue } from '@/lib/tailwind';
+
+export const RecordCardStyles = [
+ 'group',
+ 'grid',
+ 'shadow-1xs',
+ 'shadow-tint-9/1',
+ 'depth-flat:shadow-none',
+ 'rounded',
+ 'straight-corners:rounded-none',
+ 'circular-corners:rounded-xl',
+ 'dark:shadow-transparent',
+
+ 'before:pointer-events-none',
+ 'before:grid-area-1-1',
+ 'before:transition-shadow',
+ 'before:w-full',
+ 'before:h-full',
+ 'before:rounded-[inherit]',
+ 'before:ring-1',
+ 'before:ring-tint-12/2',
+ 'before:z-10',
+ 'before:relative',
+
+ 'hover:before:ring-tint-12/5',
+] as ClassValue;
diff --git a/packages/gitbook/src/components/DocumentView/Table/table.module.css b/packages/gitbook/src/components/DocumentView/Table/table.module.css
index 4b602a607c..c2838ed53a 100644
--- a/packages/gitbook/src/components/DocumentView/Table/table.module.css
+++ b/packages/gitbook/src/components/DocumentView/Table/table.module.css
@@ -23,7 +23,7 @@
}
.columnHeader {
- @apply text-sm font-medium py-2 px-4 text-tint-strong;
+ @apply text-sm font-medium py-2 px-3 text-tint-strong;
}
.row {
@@ -35,7 +35,7 @@
}
.cell {
- @apply flex-1 flex align-middle border-tint-subtle py-2 px-3 h-full text-sm relative;
+ @apply flex-1 flex align-middle border-tint-subtle py-2 px-3 text-sm relative;
}
.cell:not(:last-child) {
diff --git a/packages/gitbook/src/components/DocumentView/Table/utils.ts b/packages/gitbook/src/components/DocumentView/Table/utils.ts
index 01bd82bcd6..1fc95b357e 100644
--- a/packages/gitbook/src/components/DocumentView/Table/utils.ts
+++ b/packages/gitbook/src/components/DocumentView/Table/utils.ts
@@ -1,4 +1,5 @@
import type { ContentRef, DocumentTableDefinition, DocumentTableRecord } from '@gitbook/api';
+import assertNever from 'assert-never';
/**
* Get the value for a column in a record.
@@ -14,11 +15,24 @@ export function getRecordValue void>();
function useTabsState() {
@@ -31,7 +33,7 @@ function useTabsState() {
const setTabsState = useCallback((updater: (previous: TabsState) => TabsState) => {
globalTabsState = updater(globalTabsState);
- storage.setItem('@gitbook/tabsState', globalTabsState);
+ setLocalStorageItem('@gitbook/tabsState', globalTabsState);
listeners.forEach((listener) => listener());
}, []);
const state = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
@@ -68,9 +70,10 @@ export function DynamicTabs(
props: TabsInput & {
tabsBody: React.ReactNode[];
style: ClassValue;
+ block: DocumentBlockTabs;
}
) {
- const { id, tabs, tabsBody, style } = props;
+ const { id, block, tabs, tabsBody, style } = props;
const hash = useHash();
const [tabsState, setTabsState] = useTabsState();
@@ -146,8 +149,8 @@ export function DynamicTabs(
'ring-inset',
'ring-tint-subtle',
'flex',
- 'overflow-hidden',
'flex-col',
+ 'overflow-hidden',
style
)}
>
@@ -165,16 +168,14 @@ export function DynamicTabs(
)}
>
{tabs.map((tab) => (
- {
- onSelectTab(tab);
- }}
className={tcls(
+ hashLinkButtonWrapperStyles,
+ 'flex',
+ 'items-center',
+ 'gap-3.5',
+
//prev from active-tab
'[&:has(+_.active-tab)]:rounded-br-md',
@@ -184,14 +185,6 @@ export function DynamicTabs(
//next from active-tab
'[.active-tab_+_:after]:rounded-br-md',
- 'inline-block',
- 'text-sm',
- 'px-3.5',
- 'py-2',
- 'transition-[color]',
- 'font-[500]',
- 'relative',
-
'after:transition-colors',
'after:border-r',
'after:absolute',
@@ -202,14 +195,16 @@ export function DynamicTabs(
'after:h-[70%]',
'after:w-[1px]',
+ 'px-3.5',
+ 'py-2',
+
'last:after:border-transparent',
'text-tint',
'bg-tint-12/1',
'hover:text-tint-strong',
-
- 'truncate',
'max-w-full',
+ 'truncate',
active.id === tab.id
? [
@@ -224,8 +219,34 @@ export function DynamicTabs(
: null
)}
>
- {tab.title}
-
+ {
+ onSelectTab(tab);
+ }}
+ className={tcls(
+ 'inline-block',
+ 'text-sm',
+ 'transition-[color]',
+ 'font-[500]',
+ 'relative',
+ 'max-w-full',
+ 'truncate'
+ )}
+ >
+ {tab.title}
+
+
+
+
0;
+ const hasCopyright = customization.footer.copyright;
+ const hasThemeToggle = customization.themes.toggeable;
+
+ const mobileOnly = !hasLogo && !hasGroups && !hasCopyright && hasThemeToggle;
+
return (
{t(language, 'page_last_modified',