mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 19:06:31 +00:00
Ensure all content links are resolved relatively to preview (#3450)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Ensure all content links are resolved relatively to preview.
|
||||
@@ -415,6 +415,28 @@ const testCases: TestsCase[] = [
|
||||
await expect(page.locator('[data-testid="table-of-contents"]')).toBeVisible();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'With sections',
|
||||
url: async () => {
|
||||
const data = await getSiteAPIToken('https://gitbook.com/docs');
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('token', data.apiToken);
|
||||
|
||||
return `url/preview/${data.site}/?${searchParams.toString()}`;
|
||||
},
|
||||
screenshot: false,
|
||||
run: async (page) => {
|
||||
const sectionTabs = page.getByLabel('Sections');
|
||||
await expect(sectionTabs).toBeVisible();
|
||||
|
||||
const sectionTabLinks = sectionTabs.getByRole('link');
|
||||
for (const link of await sectionTabLinks.all()) {
|
||||
const href = await link.getAttribute('href');
|
||||
expect(href).toMatch(/^\/url\/preview\/site_p4Xo4\/?/);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ import { notFound } from 'next/navigation';
|
||||
import { assert } from 'ts-essentials';
|
||||
import { GITBOOK_URL } from './env';
|
||||
import { type ImageResizer, createImageResizer } from './images';
|
||||
import { type GitBookLinker, createLinker } from './links';
|
||||
import { type GitBookLinker, createLinker, linkerForPublishedURL } from './links';
|
||||
|
||||
/**
|
||||
* Data about the site URL. Provided by the middleware.
|
||||
@@ -301,6 +301,9 @@ export async function fetchSiteContextByIds(
|
||||
|
||||
return {
|
||||
...spaceContext,
|
||||
linker: site.urls.published
|
||||
? linkerForPublishedURL(spaceContext.linker, site.urls.published)
|
||||
: spaceContext.linker,
|
||||
organizationId: ids.organization,
|
||||
site,
|
||||
siteSpaces,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { createLinker, linkerWithAbsoluteURLs, linkerWithOtherSpaceBasePath } from './links';
|
||||
import {
|
||||
createLinker,
|
||||
linkerForPublishedURL,
|
||||
linkerWithAbsoluteURLs,
|
||||
linkerWithOtherSpaceBasePath,
|
||||
} from './links';
|
||||
|
||||
const root = createLinker({
|
||||
host: 'docs.company.com',
|
||||
@@ -19,6 +24,12 @@ const siteGitBookIO = createLinker({
|
||||
siteBasePath: '/sitename/',
|
||||
});
|
||||
|
||||
const preview = createLinker({
|
||||
host: 'preview',
|
||||
spaceBasePath: '/site_abc/section/space/',
|
||||
siteBasePath: '/site_abc/',
|
||||
});
|
||||
|
||||
describe('toPathInSpace', () => {
|
||||
it('should return the correct path', () => {
|
||||
expect(root.toPathInSpace('some/path')).toBe('/some/path');
|
||||
@@ -88,6 +99,12 @@ describe('toLinkForContent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve the search and hash', () => {
|
||||
expect(root.toLinkForContent('https://docs.company.com/some/path?a=b#c')).toBe(
|
||||
'/some/path?a=b#c'
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve an absolute URL if the site is not the same', () => {
|
||||
expect(siteGitBookIO.toLinkForContent('https://org.gitbook.io/anothersite/some/path')).toBe(
|
||||
'https://org.gitbook.io/anothersite/some/path'
|
||||
@@ -138,3 +155,47 @@ describe('linkerWithOtherSpaceBasePath', () => {
|
||||
expect(otherSpaceBasePathLinker.toPathInSpace('some/path')).toBe('/sitename/a/b/some/path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('linkerForPublishedURL', () => {
|
||||
describe('Root custom domain', () => {
|
||||
it('should rewrite links that belongs to the published site to be part of the preview site', () => {
|
||||
const previewLinker = linkerForPublishedURL(preview, 'https://docs.company.com/');
|
||||
expect(previewLinker.toLinkForContent('https://docs.company.com/some/path')).toBe(
|
||||
'/site_abc/some/path'
|
||||
);
|
||||
expect(
|
||||
previewLinker.toLinkForContent('https://docs.company.com/section/variant/some/path')
|
||||
).toBe('/site_abc/section/variant/some/path');
|
||||
expect(previewLinker.toLinkForContent('https://www.google.com')).toBe(
|
||||
'https://www.google.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gitbook.io domain', () => {
|
||||
it('should rewrite links that belongs to the published site to be part of the preview site', () => {
|
||||
const previewLinker = linkerForPublishedURL(
|
||||
preview,
|
||||
'https://org.gitbook.io/sitename/'
|
||||
);
|
||||
expect(
|
||||
previewLinker.toLinkForContent('https://org.gitbook.io/sitename/some/path')
|
||||
).toBe('/site_abc/some/path');
|
||||
expect(
|
||||
previewLinker.toLinkForContent(
|
||||
'https://org.gitbook.io/sitename/section/variant/some/path'
|
||||
)
|
||||
).toBe('/site_abc/section/variant/some/path');
|
||||
expect(previewLinker.toLinkForContent('https://www.google.com')).toBe(
|
||||
'https://www.google.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should should preserve hash and search', () => {
|
||||
const previewLinker = linkerForPublishedURL(preview, 'https://docs.company.com/');
|
||||
expect(previewLinker.toLinkForContent('https://docs.company.com/some/path?a=b#c')).toBe(
|
||||
'/site_abc/some/path?a=b#c'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export function createLinker(
|
||||
// If the link points to a content in the same site, we return an absolute path
|
||||
// instead of a full URL; it makes it possible to use router navigation
|
||||
if (url.hostname === servedOn.host && url.pathname.startsWith(servedOn.siteBasePath)) {
|
||||
return url.pathname;
|
||||
return url.pathname + url.search + url.hash;
|
||||
}
|
||||
|
||||
return rawURL;
|
||||
@@ -125,6 +125,37 @@ export function createLinker(
|
||||
return linker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new linker that intercepts links that belongs to the published site and rewrite them
|
||||
* relative to the URL being served.
|
||||
*
|
||||
* It is needed for preview of site where the served URL (http://preview/site_abc)
|
||||
* is different from the actual published site URL (https://docs.company.com).
|
||||
*/
|
||||
export function linkerForPublishedURL(linker: GitBookLinker, rawSitePublishedURL: string) {
|
||||
const sitePublishedURL = new URL(rawSitePublishedURL);
|
||||
|
||||
return {
|
||||
...linker,
|
||||
toLinkForContent(rawURL: string): string {
|
||||
const url = new URL(rawURL);
|
||||
|
||||
// If the link is part of the published site, we rewrite it to be part of the preview site.
|
||||
if (
|
||||
url.hostname === sitePublishedURL.hostname &&
|
||||
url.pathname.startsWith(sitePublishedURL.pathname)
|
||||
) {
|
||||
// When detecting that the url has been computed as apart of the published site,
|
||||
// we rewrite it to be part of the preview site.
|
||||
const extractedPath = url.pathname.slice(sitePublishedURL.pathname.length);
|
||||
return linker.toPathInSite(extractedPath) + url.search + url.hash;
|
||||
}
|
||||
|
||||
return linker.toLinkForContent(rawURL);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new linker that always returns absolute URLs.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user