Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] c734c5be63 Version Packages 2026-10-30 18:07:11 +00:00
42 changed files with 143 additions and 366 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Stop caching failed searches, so a rate-limited or failing search is retried on the next query instead of being served from cache for 15 minutes.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Declare the Tailwind layer order in each component stylesheet, so a route whose first stylesheet is one of them still puts `base` before `components` and preflight does not strip their padding and borders.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
@@ -1,6 +0,0 @@
---
"@gitbook/icons": minor
"gitbook": patch
---
Define a page's repeated icons once in an SVG sprite that each icon references, instead of repeating the full SVG at every heading anchor, expandable and embed placeholder, and pack the class lists every expandable repeats into shared CSS classes.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-contentkit": patch
---
Load a webframe only once it is near the viewport. A page can hold hundreds of them inside collapsed expandables, and loading every one on mount held up the first paint.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Pack the class lists that every block, heading anchor and table cell repeats into shared CSS classes, and carry a table cell's width once as a custom property instead of spelling it out as both width and min-width, so a large page carries far less markup.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Follow site and space redirects when the MCP getPage tool resolves a page URL.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Add a search input to filter models by name on OpenAPI models pages.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Let analytics tools like GA4 track outbound link clicks by using native link navigation.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Resolve the published content with the full URL in a single lookup and always send the visitor country.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Resolve dark-mode image URLs in published Markdown.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/openapi-parser": patch
---
Bump @scalar/openapi-parser to 0.29 so valid OpenAPI 3.2 documents no longer report false validation errors.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Only apply a site preview customization override to the section or variant it was made for.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Render soft line breaks in paragraphs that contain buttons.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix visitors being logged out right after signing in on sites with a logout link in the header.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
+4 -4
View File
@@ -110,7 +110,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.1",
"dependencies": {
"@base-ui/react": "catalog:",
"@cloudflare/workers-types": "^4.20251011.0",
@@ -249,7 +249,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "3.0.13",
"version": "3.0.14",
"dependencies": {
"@scalar/json-magic": "^0.17.0",
"@scalar/openapi-parser": "^0.29.19",
@@ -266,7 +266,7 @@
},
"packages/react-contentkit": {
"name": "@gitbook/react-contentkit",
"version": "0.7.17",
"version": "0.7.18",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:*",
@@ -309,7 +309,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "2.0.0",
"version": "2.0.1",
"dependencies": {
"@base-ui/react": "catalog:",
"@gitbook/expr": "workspace:*",
+30
View File
@@ -1,5 +1,35 @@
# gitbook
## 0.28.1
### Patch Changes
- 7e096d5: Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
- f61578f: Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
- 26ec43e: Stop caching failed searches, so a rate-limited or failing search is retried on the next query instead of being served from cache for 15 minutes.
- 04f39f1: Declare the Tailwind layer order in each component stylesheet, so a route whose first stylesheet is one of them still puts `base` before `components` and preflight does not strip their padding and borders.
- a512777: Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
- 508f1bc: Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
- 04f39f1: Pack the class lists that every block, heading anchor and table cell repeats into shared CSS classes, and carry a table cell's width once as a custom property instead of spelling it out as both width and min-width, so a large page carries far less markup.
- bd431f1: Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
- a5d58bc: Follow site and space redirects when the MCP getPage tool resolves a page URL.
- e745c88: Add a search input to filter models by name on OpenAPI models pages.
- 96c5683: Let analytics tools like GA4 track outbound link clicks by using native link navigation.
- 347a1cc: Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
- ea01850: Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
- c00a7b3: Resolve the published content with the full URL in a single lookup and always send the visitor country.
- 063e685: Resolve dark-mode image URLs in published Markdown.
- b7b8ae6: Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
- 4a72ead: Only apply a site preview customization override to the section or variant it was made for.
- 38f60f2: Render soft line breaks in paragraphs that contain buttons.
- 1383a79: Fix visitors being logged out right after signing in on sites with a logout link in the header.
- be658e1: Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
- Updated dependencies [04f39f1]
- Updated dependencies [4dfe630]
- @gitbook/react-contentkit@0.7.18
- @gitbook/openapi-parser@3.0.14
- @gitbook/react-openapi@2.0.1
## 0.28.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.1",
"private": true,
"dependencies": {
"@base-ui/react": "catalog:",
@@ -50,8 +50,27 @@ export function Details(props: {
id={id}
data-block-key={props.commentKey}
open={props.open || openFromHash}
// Styles live on `.expandable` in `Expandable.css`; the group name is matched literally.
className={tcls(className, 'expandable', 'group/expandable')}
className={tcls(
className,
'group/expandable',
'bg-tint-base',
'border',
'border-b-0',
'border-tint-subtle',
//all
'mt-0!',
'mb-0',
//select first child
'first:mt-5!',
'first:rounded-t-lg',
//select first in group
'[:not(&)_+_&]:mt-5!',
'[:not(&)_+_&]:rounded-t-lg',
//select last in group
'[&:not(:has(+_&))]:mb-5',
'[&:not(:has(+_&))]:rounded-b-lg',
'[&:not(:has(+_&))]:border-b'
)}
>
{children}
</details>
@@ -1,43 +0,0 @@
@reference "../../RootLayout/globals.css";
/* This file can be the first stylesheet a route loads, and layer order follows first
appearance. Without this, `components` would be created here and Tailwind's `base`
only later, letting preflight's `padding: 0` and `border: 0` win over the rules below. */
@layer theme, base, components, utilities;
/* A page can stack a hundred expandables, so their styles live here rather than in each one's
class attributes. They sit in `components` so the utilities passed as props still override. */
@layer components {
/* Consecutive expandables join into one bordered group: only the first of a run gets the top
margin and corners, and only the last the bottom border, margin and corners. */
.expandable {
@apply scroll-mt-(--content-scroll-margin) border border-b-0 border-tint-subtle bg-tint-base;
@apply mt-0! mb-0 first:mt-5! first:rounded-t-lg;
@apply [:not(&)_+_&]:mt-5! [:not(&)_+_&]:rounded-t-lg;
@apply [&:not(:has(+_&))]:mb-5 [&:not(:has(+_&))]:rounded-b-lg [&:not(:has(+_&))]:border-b;
}
.expandable-summary {
@apply relative cursor-pointer list-none select-none p-4 text-tint transition-colors;
@apply group-hover/expandable:text-tint-strong group-open/expandable:text-tint-strong;
@apply [&::-webkit-details-marker]:hidden;
}
/* Room for the anchor icon pinned to the right edge. */
.expandable-summary-anchored {
@apply pr-10;
}
.expandable-chevron {
@apply mr-3 mb-1 inline-block group-open/expandable:rotate-90;
}
.expandable-anchor {
@apply absolute top-2 right-4 bottom-2 flex items-center dark:shadow-none dark:ring-0;
}
.expandable-anchor-icon {
@apply inline-block size-3 text-transparent transition-colors;
@apply group-hover/expandable:text-tint-subtle contrast-more:group-hover/expandable:text-tint-strong;
}
}
@@ -7,7 +7,6 @@ import { shouldShowHashLinks } from '../HashLinkButton';
import { Inlines } from '../Inlines';
import { getCommentAnchorProps, getCommentKeyProp } from '../utils';
import { Details } from './Details';
import './Expandable.css';
import { ToggleChevron } from '@/components/primitives';
import { getSpaceLanguage, tString } from '@/intl/server';
import { defaultLanguage } from '@/intl/translations';
@@ -41,19 +40,37 @@ export async function Expandable(props: BlockProps<DocumentBlockExpandable>) {
id={id}
{...getCommentKeyProp(context, block.key)}
open={context.mode === 'print' || isDefaultExpanded}
className={style}
className={tcls('scroll-mt-(--content-scroll-margin)', style)}
>
<summary
{...getCommentAnchorProps(context, titleParagraph.key)}
className={tcls(
'expandable-summary',
showHashLink && 'expandable-summary-anchored'
'cursor-pointer',
'px-4',
// Reserve room for the anchor icon pinned to the right edge
showHashLink && 'pr-10',
'py-4',
'relative',
'list-none',
'select-none',
'transition-colors',
'text-tint',
'group-hover/expandable:text-tint-strong',
'group-open/expandable:text-tint-strong',
'[&::-webkit-details-marker]:hidden'
)}
>
<ToggleChevron
orientation="right-to-down"
open={context.mode === 'print'}
className="expandable-chevron"
className={tcls(
'inline-block',
'size-3',
'mr-3',
'mb-1',
'shrink-0',
'group-open/expandable:rotate-90'
)}
/>
<Inlines
nodes={titleParagraph.nodes}
@@ -65,9 +82,28 @@ export async function Expandable(props: BlockProps<DocumentBlockExpandable>) {
<a
href={`#${id}`}
aria-label={tString(language, 'direct_link_to_heading')}
className="expandable-anchor"
className={tcls(
'absolute',
'top-2',
'bottom-2',
'right-4',
'flex',
'items-center',
'dark:shadow-none',
'dark:ring-0'
)}
>
<Icon icon="hashtag" className="expandable-anchor-icon" />
<Icon
icon="hashtag"
className={tcls(
'inline-block',
'size-3',
'transition-colors',
'text-transparent',
'group-hover/expandable:text-tint-subtle',
'contrast-more:group-hover/expandable:text-tint-strong'
)}
/>
</a>
) : null}
</summary>
@@ -140,7 +140,7 @@ export async function SitePage(props: SitePageProps & { staticRoute: boolean })
);
return (
<IconsProvider iconSources={iconSources} sprite>
<IconsProvider iconSources={iconSources}>
<PageContextProvider pageId={page.id} spaceId={context.space.id} title={page.title}>
{filterableTagSlugs.length > 0 ? (
<UpdatesFilterProvider tagSlugs={filterableTagSlugs}>
@@ -132,27 +132,6 @@ describe('getContentInlineIconSourceRequests', () => {
]);
});
it('collects the icons that blocks repeat once per instance', () => {
const document = {
object: 'document',
nodes: [
{ object: 'block', type: 'heading-2', nodes: [] },
{ object: 'block', type: 'expandable', nodes: [] },
{ object: 'block', type: 'integration', nodes: [] },
{ object: 'block', type: 'paragraph', nodes: [] },
],
} as unknown as JSONDocument;
expect(
getContentInlineIconSourceRequests({ iconStyle: IconStyle.Regular, document })
).toEqual([
{ icon: 'hashtag', iconStyle: IconStyle.Regular },
{ icon: 'hashtag', iconStyle: IconStyle.Regular },
{ icon: 'chevron-right', iconStyle: IconStyle.Regular },
{ icon: 'spinner', iconStyle: IconStyle.Regular },
]);
});
it('ignores invalid, missing, and non-string icon values', () => {
const pages = [
{ icon: 'not-a-real-icon' },
-17
View File
@@ -138,17 +138,6 @@ const customIconSourceRequests = [
'vscode',
] satisfies IconName[];
/**
* Icons a block renders once per instance, so a document with many such blocks repeats them.
*/
const blockIconSourceRequests: Partial<Record<string, IconName[]>> = {
'heading-1': ['hashtag'],
'heading-2': ['hashtag'],
'heading-3': ['hashtag'],
expandable: ['hashtag', 'chevron-right'],
integration: ['spinner'],
};
const explicitStyleIconSourceRequests = [
{ icon: 'check', iconStyle: IconStyle.Solid },
{ icon: 'chevron-right', iconStyle: IconStyle.Solid },
@@ -391,12 +380,6 @@ function collectDocumentIconSourceRequests(
}
}
if (record.object === 'block' && typeof record.type === 'string') {
for (const icon of blockIconSourceRequests[record.type] ?? []) {
addIconSourceRequest(requests, icon, iconStyle);
}
}
for (const value of Object.values(record)) {
if (Array.isArray(value)) {
for (const item of value) {
@@ -1,57 +0,0 @@
import { describe, expect, it } from 'bun:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { Icon, IconStyle, IconsProvider } from '@gitbook/icons';
const hashtag = { viewBox: '0 0 448 512', markup: '<path d="M1 1h2"></path>' };
const star = { viewBox: '0 0 576 512', markup: '<path d="M3 3h4"></path>' };
describe('icon sprite', () => {
it('defines each source once and references it from every icon', () => {
const html = renderToStaticMarkup(
<IconsProvider
iconStyle={IconStyle.Regular}
iconSources={{ 'regular/hashtag': hashtag }}
sprite
>
<Icon icon="hashtag" />
<Icon icon="hashtag" className="size-4" />
</IconsProvider>
);
expect(html.split(hashtag.markup)).toHaveLength(2);
expect(html).toContain('<g id="gb-icon-regular-hashtag">');
expect(html.match(/<use href="#gb-icon-regular-hashtag"><\/use>/g)).toHaveLength(2);
expect(html).toContain('viewBox="0 0 448 512"');
});
it('inlines the markup without a sprite', () => {
const html = renderToStaticMarkup(
<IconsProvider
iconStyle={IconStyle.Regular}
iconSources={{ 'regular/hashtag': hashtag }}
>
<Icon icon="hashtag" />
</IconsProvider>
);
expect(html).not.toContain('<use');
expect(html).not.toContain('data-gb-icon-sprite');
expect(html).toContain(hashtag.markup);
});
it('inlines sources that only an outer provider defines', () => {
const html = renderToStaticMarkup(
<IconsProvider iconStyle={IconStyle.Regular} iconSources={{ 'regular/star': star }}>
<IconsProvider iconSources={{ 'regular/hashtag': hashtag }} sprite>
<Icon icon="star" />
<Icon icon="hashtag" />
</IconsProvider>
</IconsProvider>
);
expect(html).toContain(`>${star.markup}</svg>`);
expect(html).not.toContain('gb-icon-regular-star');
expect(html).toContain('<use href="#gb-icon-regular-hashtag"></use>');
});
});
+17 -26
View File
@@ -3,7 +3,7 @@
import * as React from 'react';
import { getIconStyle } from './getIconStyle';
import { getIconSpriteId, getInlineIconSourceKey } from './IconSources';
import { getInlineIconSourceKey } from './IconSources';
import { getIconAssetURL, useIcons } from './IconsProvider';
import type { IconName, IconStyle } from './types';
@@ -51,34 +51,25 @@ export const Icon = React.forwardRef(function Icon(
} = props;
const [iconStyle, icon] = getIconStyle(propIconStyle, propIcon);
const sourceKey = getInlineIconSourceKey(iconStyle, icon);
const source = context.iconSources?.[sourceKey];
const source = context.iconSources?.[getInlineIconSourceKey(iconStyle, icon)];
const maskId = React.useId();
if (source) {
const svgProps = {
ref,
...rest,
viewBox: source.viewBox,
fill: 'currentColor',
style: {
overflow: 'visible',
...(size ? { width: size, height: size } : {}),
...rest.style,
},
className: `gb-icon ${className}`,
};
// A page can repeat one icon hundreds of times (heading anchors), so reference the sprite.
if (context.spriteKeys?.has(sourceKey)) {
return (
<svg {...svgProps}>
<use href={`#${getIconSpriteId(sourceKey)}`} />
</svg>
);
}
return <svg {...svgProps} dangerouslySetInnerHTML={{ __html: source.markup }} />;
return (
<svg
ref={ref}
{...rest}
viewBox={source.viewBox}
fill="currentColor"
style={{
overflow: 'visible',
...(size ? { width: size, height: size } : {}),
...rest.style,
}}
className={`gb-icon ${className}`}
dangerouslySetInnerHTML={{ __html: source.markup }}
/>
);
}
const url = getIconAssetURL(context, iconStyle, icon);
-7
View File
@@ -11,10 +11,3 @@ export interface InlineIconSource {
export function getInlineIconSourceKey(style: string, icon: string): string {
return `${style}/${icon}`;
}
/**
* Get the element id an icon's sprite entry is defined under, from its source key.
*/
export function getIconSpriteId(key: string): string {
return `gb-icon-${key.replace(/[^a-z0-9-]/gi, '-')}`;
}
+4 -61
View File
@@ -2,7 +2,7 @@
import * as React from 'react';
import { type InlineIconSource, getIconSpriteId } from './IconSources';
import type { InlineIconSource } from './IconSources';
import { IconStyle } from './types';
import { GITBOOK_ICONS_ASSET_VERSION } from './version';
@@ -18,8 +18,6 @@ export type IconsContextType = Partial<IconsAssetsLocation> & {
assetsByStyles?: Record<string, IconsAssetsLocation>;
/** Server-resolved SVG sources keyed by style and icon. */
iconSources?: Record<string, InlineIconSource>;
/** Source keys defined in an on-page sprite, which icons reference instead of repeating the markup. */
spriteKeys?: ReadonlySet<string>;
/** Current default style for icons */
iconStyle: IconStyle;
};
@@ -31,17 +29,7 @@ const IconsContext = React.createContext<IconsContextType>({
/**
* Provider to control the loading of icons.
*/
export function IconsProvider(
props: React.PropsWithChildren<
Partial<Omit<IconsContextType, 'spriteKeys'>> & {
/**
* Define this provider's own `iconSources` once in a hidden sprite, so icons below
* reference them rather than each repeating the SVG markup.
*/
sprite?: boolean;
}
>
) {
export function IconsProvider(props: React.PropsWithChildren<Partial<IconsContextType>>) {
const parent = React.useContext(IconsContext);
const {
children,
@@ -50,9 +38,7 @@ export function IconsProvider(
iconStyle = parent.iconStyle,
assetsByStyles = parent.assetsByStyles,
iconSources = parent.iconSources,
sprite = false,
} = props;
const spriteSources = sprite ? props.iconSources : undefined;
const value = React.useMemo(() => {
return {
assetsURL,
@@ -63,53 +49,10 @@ export function IconsProvider(
parent.iconSources && iconSources
? { ...parent.iconSources, ...iconSources }
: (iconSources ?? parent.iconSources),
spriteKeys: spriteSources
? new Set([...(parent.spriteKeys ?? []), ...Object.keys(spriteSources)])
: parent.spriteKeys,
};
}, [
assetsURL,
assetsURLToken,
iconStyle,
assetsByStyles,
iconSources,
spriteSources,
parent.iconSources,
parent.spriteKeys,
]);
}, [assetsURL, assetsURLToken, iconStyle, assetsByStyles, iconSources, parent.iconSources]);
return (
<IconsContext.Provider value={value}>
{spriteSources ? <IconSprite sources={spriteSources} /> : null}
{children}
</IconsContext.Provider>
);
}
/**
* Hidden SVG holding one copy of each source, for `<use>` references from icons.
* Entries are groups rather than symbols so they draw in the referencing icon's own viewBox.
*/
function IconSprite(props: { sources: Record<string, InlineIconSource> }) {
return (
<svg
aria-hidden="true"
data-gb-icon-sprite=""
width="0"
height="0"
style={{ position: 'absolute' }}
>
<defs>
{Object.entries(props.sources).map(([key, source]) => (
<g
key={key}
id={getIconSpriteId(key)}
dangerouslySetInnerHTML={{ __html: source.markup }}
/>
))}
</defs>
</svg>
);
return <IconsContext.Provider value={value}>{children}</IconsContext.Provider>;
}
/**
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/openapi-parser
## 3.0.14
### Patch Changes
- 4dfe630: Bump @scalar/openapi-parser to 0.29 so valid OpenAPI 3.2 documents no longer report false validation errors.
## 3.0.13
### Patch Changes
+1 -1
View File
@@ -8,7 +8,7 @@
"default": "./dist/index.js"
}
},
"version": "3.0.13",
"version": "3.0.14",
"sideEffects": false,
"dependencies": {
"@scalar/json-magic": "^0.17.0",
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/react-contentkit
## 0.7.18
### Patch Changes
- 04f39f1: Load a webframe only once it is near the viewport. A page can hold hundreds of them inside collapsed expandables, and loading every one on mount held up the first paint.
## 0.7.17
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@gitbook/react-contentkit",
"version": "0.7.17",
"version": "0.7.18",
"type": "module",
"exports": {
".": {
+7
View File
@@ -1,5 +1,12 @@
# @gitbook/react-openapi
## 2.0.1
### Patch Changes
- Updated dependencies [4dfe630]
- @gitbook/openapi-parser@3.0.14
## 2.0.0
### Major Changes
+1 -1
View File
@@ -7,7 +7,7 @@
"default": "./dist/index.js"
}
},
"version": "2.0.0",
"version": "2.0.1",
"sideEffects": false,
"dependencies": {
"@base-ui/react": "catalog:",