Fix links to other spaces in embeddable view (#3819)

This commit is contained in:
Samy Pessé
2025-11-19 11:38:53 +01:00
committed by GitHub
parent 38e34dcce3
commit c51076efc4
7 changed files with 69 additions and 49 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix links to other spaces and root page in embeddable view.
@@ -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'
);
});
});
+7
View File
@@ -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'),
});
},
};
}
+6 -11
View File
@@ -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');
+24 -29
View File
@@ -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;
+2 -7
View File
@@ -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 {
+2 -2
View File
@@ -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] || '/')}`,
};
}