mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 03:16:40 +00:00
Support for site customization option to change how external links open (#3372)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": minor
|
||||
---
|
||||
|
||||
Add support for site customization option to change how external links open.
|
||||
@@ -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
@@ -36,7 +36,7 @@
|
||||
"workspaces": {
|
||||
"packages": ["packages/*"],
|
||||
"catalog": {
|
||||
"@gitbook/api": "^0.122.0"
|
||||
"@gitbook/api": "^0.123.0"
|
||||
}
|
||||
},
|
||||
"patchedDependencies": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -51,6 +51,9 @@ export function defaultCustomization(): api.SiteCustomizationSettings {
|
||||
aiSearch: {
|
||||
enabled: true,
|
||||
},
|
||||
externalLinks: {
|
||||
target: api.SiteExternalLinksTarget.Self,
|
||||
},
|
||||
advancedCustomization: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user