Support for site customization option to change how external links open (#3372)

This commit is contained in:
Samy Pessé
2025-06-22 15:49:15 +02:00
committed by GitHub
parent 50c05164f7
commit f033734308
8 changed files with 45 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Add support for site customization option to change how external links open.
+2 -2
View File
@@ -286,7 +286,7 @@
"react-dom": "^19.0.0",
},
"catalog": {
"@gitbook/api": "^0.122.0",
"@gitbook/api": "^0.123.0",
},
"packages": {
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
@@ -651,7 +651,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.122.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-zZ40xlJsfh4aed4zkEAcLtuSNRbPmyG9HZ0zqVXqeUiqsm6gm2uhdY0VDL+zYxcDXDqbPcA1fiubfivdfe3LtA=="],
"@gitbook/api": ["@gitbook/api@0.123.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-BszoIk4H/wWb0jnGSJPSSXSAO8tV4B4/LHwl5ygfERHqk9rOGpM/U7+yOlmbX/tnm30vE+MQ3QQ5i8hN8wVk1w=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
+1 -1
View File
@@ -36,7 +36,7 @@
"workspaces": {
"packages": ["packages/*"],
"catalog": {
"@gitbook/api": "^0.122.0"
"@gitbook/api": "^0.123.0"
}
},
"patchedDependencies": {
+4
View File
@@ -16,6 +16,7 @@ import {
CustomizationThemeMode,
type CustomizationThemedColor,
type SiteCustomizationSettings,
SiteExternalLinksTarget,
} from '@gitbook/api';
import { type BrowserContext, type Page, type Response, expect, test } from '@playwright/test';
import deepMerge from 'deepmerge';
@@ -317,6 +318,9 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
aiSearch: {
enabled: true,
},
externalLinks: {
target: SiteExternalLinksTarget.Self,
},
advancedCustomization: {
enabled: true,
},
@@ -1,15 +1,17 @@
'use client';
import type { CustomizationThemeMode } from '@gitbook/api';
import type { CustomizationThemeMode, SiteExternalLinksTarget } from '@gitbook/api';
import { ThemeProvider } from 'next-themes';
import type React from 'react';
import { LinkSettingsContext } from '../primitives';
export function ClientContexts(props: {
nonce?: string;
forcedTheme: CustomizationThemeMode | undefined;
externalLinksTarget: SiteExternalLinksTarget;
children: React.ReactNode;
}) {
const { children, forcedTheme } = props;
const { children, forcedTheme, externalLinksTarget } = props;
/**
* A bug in ThemeProvider is causing the nonce to be included incorrectly
@@ -22,7 +24,9 @@ export function ClientContexts(props: {
return (
<ThemeProvider nonce={nonce} attribute="class" enableSystem forcedTheme={forcedTheme}>
{children}
<LinkSettingsContext.Provider value={{ externalLinksTarget }}>
{children}
</LinkSettingsContext.Provider>
</ThemeProvider>
);
}
@@ -54,6 +54,7 @@ export async function SiteLayout(props: {
forcedTheme ??
(customization.themes.toggeable ? undefined : customization.themes.default)
}
externalLinksTarget={customization.externalLinks.target}
>
<SpaceLayout
context={context}
@@ -4,6 +4,7 @@ import NextLink, { type LinkProps as NextLinkProps } from 'next/link';
import React from 'react';
import { tcls } from '@/lib/tailwind';
import { SiteExternalLinksTarget } from '@gitbook/api';
import { type TrackEventInput, useTrackEvent } from '../Insights';
import { type DesignTokenName, useClassnames } from './StyleProvider';
@@ -30,6 +31,15 @@ export type LinkProps = Omit<BaseLinkProps, 'href'> &
classNames?: DesignTokenName[];
};
/**
* Context to configure the default behavior of links.
*/
export const LinkSettingsContext = React.createContext<{
externalLinksTarget: SiteExternalLinksTarget;
}>({
externalLinksTarget: SiteExternalLinksTarget.Self,
});
/**
* Low-level Link component that handles navigation to external urls.
* It does not contain any styling.
@@ -39,6 +49,7 @@ export const Link = React.forwardRef(function Link(
ref: React.Ref<HTMLAnchorElement>
) {
const { href, prefetch, children, insights, classNames, className, ...domProps } = props;
const { externalLinksTarget } = React.useContext(LinkSettingsContext);
const trackEvent = useTrackEvent();
const forwardedClassNames = useClassnames(classNames || []);
@@ -53,9 +64,14 @@ export const Link = React.forwardRef(function Link(
});
}
// When the page is embedded in an iframe, for security reasons other urls cannot be opened.
// In this case, we open the link in a new tab.
if (window.self !== window.top && isExternalLink(href, window.location.origin)) {
if (
isExternalLink(href, window.location.origin) &&
// When the page is embedded in an iframe, for security reasons other urls cannot be opened.
// In this case, we open the link in a new tab.
(window.self !== window.top ||
// If the site is configured to open links in a new tab
externalLinksTarget === SiteExternalLinksTarget.Blank)
) {
event.preventDefault();
window.open(href, '_blank');
}
@@ -73,6 +89,9 @@ export const Link = React.forwardRef(function Link(
{...domProps}
href={href}
onClick={onClick}
{...(externalLinksTarget === SiteExternalLinksTarget.Blank
? { target: '_blank', rel: 'noopener noreferrer' }
: {})}
>
{children}
</a>
+3
View File
@@ -51,6 +51,9 @@ export function defaultCustomization(): api.SiteCustomizationSettings {
aiSearch: {
enabled: true,
},
externalLinks: {
target: api.SiteExternalLinksTarget.Self,
},
advancedCustomization: {
enabled: true,
},