From af50296145cd1d8d2fb387594a30602814d7cd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Wed, 19 Nov 2025 11:15:57 +0100 Subject: [PATCH 1/3] Fix links to other spaces in embeddable view --- packages/gitbook/src/lib/embeddable.test.ts | 23 +++++++++ packages/gitbook/src/lib/embeddable.ts | 7 +++ packages/gitbook/src/lib/links.test.ts | 17 +++---- packages/gitbook/src/lib/links.ts | 53 ++++++++++----------- packages/gitbook/src/lib/references.tsx | 9 +--- 5 files changed, 62 insertions(+), 47 deletions(-) create mode 100644 packages/gitbook/src/lib/embeddable.test.ts diff --git a/packages/gitbook/src/lib/embeddable.test.ts b/packages/gitbook/src/lib/embeddable.test.ts new file mode 100644 index 0000000000..740bd9cb6a --- /dev/null +++ b/packages/gitbook/src/lib/embeddable.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'bun:test'; +import { getEmbeddableLinker } from './embeddable'; +import { createLinker } from './links'; + +describe('getEmbeddableLinker', () => { + it('withOtherSiteSpace should resolve future links within the embed namespace', () => { + const root = createLinker({ + host: 'docs.company.com', + spaceBasePath: '/', + siteBasePath: '/', + }); + + const embeddableLinker = getEmbeddableLinker(root); + + const otherSpaceEmbeddableLinker = embeddableLinker.withOtherSiteSpace({ + spaceBasePath: '/section/variant', + }); + + expect(otherSpaceEmbeddableLinker.toPathInSpace('some/path')).toBe( + '/section/variant/~gitbook/embed/page/some/path' + ); + }); +}); diff --git a/packages/gitbook/src/lib/embeddable.ts b/packages/gitbook/src/lib/embeddable.ts index 7d63849b7e..339d806ad1 100644 --- a/packages/gitbook/src/lib/embeddable.ts +++ b/packages/gitbook/src/lib/embeddable.ts @@ -48,5 +48,12 @@ export function getEmbeddableLinker(linker: GitBookLinker): GitBookLinker { return linker.toPathInSpace(embedPagePath) + (anchor ? `#${anchor}` : ''); }, + + withOtherSiteSpace(override: { spaceBasePath: string }): GitBookLinker { + return linker.withOtherSiteSpace({ + // We make sure that links in the other site space will be shown in the embeddeable view. + spaceBasePath: joinPath(override.spaceBasePath, '~gitbook/embed/page'), + }); + }, }; } diff --git a/packages/gitbook/src/lib/links.test.ts b/packages/gitbook/src/lib/links.test.ts index 8430a69cd6..b8343eb64b 100644 --- a/packages/gitbook/src/lib/links.test.ts +++ b/packages/gitbook/src/lib/links.test.ts @@ -1,10 +1,5 @@ import { describe, expect, it } from 'bun:test'; -import { - createLinker, - linkerForPublishedURL, - linkerWithAbsoluteURLs, - linkerWithOtherSpaceBasePath, -} from './links'; +import { createLinker, linkerForPublishedURL, linkerWithAbsoluteURLs } from './links'; const root = createLinker({ host: 'docs.company.com', @@ -122,9 +117,9 @@ describe('linkerWithAbsoluteURLs', () => { }); }); -describe('linkerWithOtherSpaceBasePath', () => { +describe('linker.withOtherSiteSpace', () => { it('should return a new linker that resolves links relative to a new spaceBasePath in the current site', () => { - const otherSpaceBasePathLinker = linkerWithOtherSpaceBasePath(root, { + const otherSpaceBasePathLinker = root.withOtherSiteSpace({ spaceBasePath: '/section/variant', }); expect(otherSpaceBasePathLinker.toPathInSpace('some/path')).toBe( @@ -133,7 +128,7 @@ describe('linkerWithOtherSpaceBasePath', () => { }); it('should return a new linker that resolves links relative to a new spaceBasePath in the current site', () => { - const otherSpaceBasePathLinker = linkerWithOtherSpaceBasePath(root, { + const otherSpaceBasePathLinker = root.withOtherSiteSpace({ spaceBasePath: '/section/variant', }); expect(otherSpaceBasePathLinker.toPathInSpace('some/path')).toBe( @@ -142,14 +137,14 @@ describe('linkerWithOtherSpaceBasePath', () => { }); it('should use a basepath relative to the site', () => { - const otherSpaceBasePathLinker = linkerWithOtherSpaceBasePath(siteGitBookIO, { + const otherSpaceBasePathLinker = siteGitBookIO.withOtherSiteSpace({ spaceBasePath: 'a/b', }); expect(otherSpaceBasePathLinker.toPathInSpace('some/path')).toBe('/sitename/a/b/some/path'); }); it('should use a basepath relative to the site (with trailing slash)', () => { - const otherSpaceBasePathLinker = linkerWithOtherSpaceBasePath(siteGitBookIO, { + const otherSpaceBasePathLinker = siteGitBookIO.withOtherSiteSpace({ spaceBasePath: '/a/b', }); expect(otherSpaceBasePathLinker.toPathInSpace('some/path')).toBe('/sitename/a/b/some/path'); diff --git a/packages/gitbook/src/lib/links.ts b/packages/gitbook/src/lib/links.ts index 24b620807d..81a7d1ddc9 100644 --- a/packages/gitbook/src/lib/links.ts +++ b/packages/gitbook/src/lib/links.ts @@ -60,6 +60,11 @@ export interface GitBookLinker { */ fork(override: { spaceBasePath: string }): GitBookLinker; + /** + * Create a new linker that resolves links relative to a new space in the current site. + */ + withOtherSiteSpace(override: { spaceBasePath: string }): GitBookLinker; + /** * Site base path used to create this linker. */ @@ -156,6 +161,25 @@ export function createLinker( return rawURL; }, + + withOtherSiteSpace(override: { spaceBasePath: string }): GitBookLinker { + const newLinker: GitBookLinker = { + ...linker, + toPathInSpace(relativePath: string): string { + return linker.toPathInSite(joinPaths(override.spaceBasePath, relativePath)); + }, + // implementation matches the base linker toPathForPage, but decouples from using `this` to + // ensure we always use the updates `toPathInSpace` method. + toPathForPage({ pages, page, anchor }) { + return ( + newLinker.toPathInSpace(getPagePath(pages, page)) + + (anchor ? `#${anchor}` : '') + ); + }, + }; + + return newLinker; + }, }; return linker; @@ -204,35 +228,6 @@ export function linkerWithAbsoluteURLs(linker: GitBookLinker): GitBookLinker { }; } -/** - * Create a new linker that resolves links relative to a new spaceBasePath in the current site. - */ -export function linkerWithOtherSpaceBasePath( - linker: GitBookLinker, - { - spaceBasePath, - }: { - /** - * The base path of the space. It should be relative to the root of the site. - */ - spaceBasePath: string; - } -): GitBookLinker { - const newLinker: GitBookLinker = { - ...linker, - toPathInSpace(relativePath: string): string { - return linker.toPathInSite(joinPaths(spaceBasePath, relativePath)); - }, - // implementation matches the base linker toPathForPage, but decouples from using `this` to - // ensure we always use the updates `toPathInSpace` method. - toPathForPage({ pages, page, anchor }) { - return newLinker.toPathInSpace(getPagePath(pages, page)) + (anchor ? `#${anchor}` : ''); - }, - }; - - return newLinker; -} - function joinPaths(prefix: string, path: string): string { const prefixPath = prefix.endsWith('/') ? prefix : `${prefix}/`; const suffixPath = path.startsWith('/') ? path.slice(1) : path; diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index c1f02f3eba..a0051ead94 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -10,12 +10,7 @@ import { getRevisionReusableContent, ignoreDataThrownError, } from '@/lib/data'; -import { - type GitBookLinker, - createLinker, - linkerWithAbsoluteURLs, - linkerWithOtherSpaceBasePath, -} from '@/lib/links'; +import { type GitBookLinker, createLinker, linkerWithAbsoluteURLs } from '@/lib/links'; import type { ContentRef, RevisionFile, @@ -448,7 +443,7 @@ async function createContextForSpace( if (bestTargetSpace?.siteSpace && 'site' in context) { // If we found the space ID in the current site context, we can resolve links relative to it in the site. - linker = linkerWithOtherSpaceBasePath(context.linker, { + linker = context.linker.withOtherSiteSpace({ spaceBasePath: getFallbackSiteSpacePath(context, bestTargetSpace.siteSpace), }); } else { From e703ea78b136a616e51db17094ec65da3d122bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Wed, 19 Nov 2025 11:25:57 +0100 Subject: [PATCH 2/3] Fix root page --- packages/gitbook/src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 915f2cc700..a6920e490a 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -590,10 +590,10 @@ function encodePathInSiteContent(rawPathname: string): { } // If the pathname is an embedded page - const embedPage = pathname.match(/^~gitbook\/embed\/page\/(\S+)$/); + const embedPage = pathname.match(/^~gitbook\/embed\/page(\/(\S*))?$/); if (embedPage) { return { - pathname: `~gitbook/embed/page/${encodeURIComponent(embedPage[1]!)}`, + pathname: `~gitbook/embed/page/${encodeURIComponent(embedPage[1] || '/')}`, }; } From 2ee9e8c07c2c6662e76ef16b8873dea9d4a231bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Wed, 19 Nov 2025 11:27:27 +0100 Subject: [PATCH 3/3] Changeset --- .changeset/social-berries-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/social-berries-visit.md diff --git a/.changeset/social-berries-visit.md b/.changeset/social-berries-visit.md new file mode 100644 index 0000000000..b75e5e54fe --- /dev/null +++ b/.changeset/social-berries-visit.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix links to other spaces and root page in embeddable view.