diff --git a/packages/gitbook/src/app/~space/[spaceId]/pdf.ts b/packages/gitbook/src/app/~space/[spaceId]/pdf.ts index 44ab82b99..4e5e0dc20 100644 --- a/packages/gitbook/src/app/~space/[spaceId]/pdf.ts +++ b/packages/gitbook/src/app/~space/[spaceId]/pdf.ts @@ -21,16 +21,15 @@ export async function getSpacePDFContext( const apiToken = await getAPITokenFromMiddleware(); const basePath = getPDFRoutePath(params); - const linker = createLinker({ - spaceBasePath: basePath, - siteBasePath: basePath, - }); const dataFetcher = createDataFetcher({ apiToken: apiToken, }); const baseContext: GitBookBaseContext = { - linker, + linker: createLinker({ + spaceBasePath: basePath, + siteBasePath: basePath, + }), dataFetcher, }; diff --git a/packages/gitbook/src/components/PDF/PDFPage.test.ts b/packages/gitbook/src/components/PDF/PDFPage.test.ts index b3fd759f8..3bae0781c 100644 --- a/packages/gitbook/src/components/PDF/PDFPage.test.ts +++ b/packages/gitbook/src/components/PDF/PDFPage.test.ts @@ -63,12 +63,16 @@ describe('createPDFLinker', () => { it('keeps links to non-exported pages on the published domain', () => { const baseLinker = createLinker({ - host: 'docs.vectra.ai', - siteBasePath: '/', - spaceBasePath: '/deployment', + host: 'open-2v.gitbook.com', + siteBasePath: '/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf', + spaceBasePath: '/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf', }); - const linker = createPDFLinker(baseLinker, [{ page: createDocumentPage('included', '') }]); + const linker = createPDFLinker( + baseLinker, + [{ page: createDocumentPage('included', '') }], + 'https://docs.vectra.ai/deployment/' + ); expect( linker.toPathForPage({ @@ -81,6 +85,31 @@ describe('createPDFLinker', () => { ).toBe('https://docs.vectra.ai/deployment/respond'); }); + it('preserves anchors for non-exported pages on the published domain', () => { + const baseLinker = createLinker({ + host: 'open-2v.gitbook.com', + siteBasePath: '/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf', + spaceBasePath: '/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf', + }); + + const linker = createPDFLinker( + baseLinker, + [{ page: createDocumentPage('included', '') }], + 'https://docs.vectra.ai/deployment/' + ); + + expect( + linker.toPathForPage({ + pages: [ + createDocumentPage('included', ''), + createDocumentPage('outside', 'respond'), + ], + page: createDocumentPage('outside', 'respond'), + anchor: 'faq', + }) + ).toBe('https://docs.vectra.ai/deployment/respond#faq'); + }); + it('returns a local placeholder link for group pages not included in the PDF export', () => { const baseLinker = createLinker({ host: 'docs.vectra.ai', diff --git a/packages/gitbook/src/components/PDF/PDFPage.tsx b/packages/gitbook/src/components/PDF/PDFPage.tsx index 3193ff64b..76664856f 100644 --- a/packages/gitbook/src/components/PDF/PDFPage.tsx +++ b/packages/gitbook/src/components/PDF/PDFPage.tsx @@ -66,7 +66,7 @@ export async function PDFPage(props: { ); // Build a linker that create anchor links for the pages rendered in the PDF page. - const linker = createPDFLinker(baseContext.linker, pages); + const linker = createPDFLinker(baseContext.linker, pages, baseContext.space.urls.published); const context: GitBookSpaceContext = { ...baseContext, diff --git a/packages/gitbook/src/components/PDF/linker.ts b/packages/gitbook/src/components/PDF/linker.ts index d05a7a1fd..188a3b651 100644 --- a/packages/gitbook/src/components/PDF/linker.ts +++ b/packages/gitbook/src/components/PDF/linker.ts @@ -2,6 +2,8 @@ import type { Revision, RevisionPageDocument, RevisionPageGroup } from '@gitbook import { RevisionPageType } from '@gitbook/api'; import type { GitBookLinker } from '@/lib/links'; +import { getPagePath } from '@/lib/pages'; +import { withTrailingSlash } from '@/lib/paths'; /** * Create the HTML ID for the container of a page or a given anchor in it. @@ -23,16 +25,19 @@ export function getPagePDFContainerId( * This linker generates in-document anchor links for pages that are included * in the current PDF export, using `getPagePDFContainerId` to build the * target element ID. For pages that are not part of the exported PDF, it - * falls back to absolute URLs pointing to the published site, preserving - * navigability for external content. + * falls back to URLs pointing to the published site if `publishedSpaceURL` is + * provided, preserving navigability for external content. Otherwise, it uses + * absolute URLs from the base linker. * * @param baseLinker - The base GitBook linker used to resolve standard paths and URLs. * @param pages - The list of pages that are included in the current PDF export. + * @param publishedSpaceURL - Optional URL of the published space for external page links. * @returns A `GitBookLinker` configured to generate PDF-friendly links. */ export function createPDFLinker( baseLinker: GitBookLinker, - pages: Array<{ page: Revision['pages'][number] }> + pages: Array<{ page: Revision['pages'][number] }>, + publishedSpaceURL?: string ): GitBookLinker { return { ...baseLinker, @@ -45,6 +50,16 @@ export function createPDFLinker( } // For pages that are not embedded in this PDF export, keep links on the published site. + if (publishedSpaceURL) { + const pagePath = getPagePath(input.pages, input.page); + const pageURL = new URL(pagePath, withTrailingSlash(publishedSpaceURL)); + if (input.anchor) { + pageURL.hash = input.anchor; + } + + return pageURL.toString(); + } + return baseLinker.toAbsoluteURL(baseLinker.toPathForPage(input)); }, };