Skip to content

Commit 2c945c6

Browse files
authored
Get the best possible target space for content refs (#2580)
1 parent d9a18ec commit 2c945c6

File tree

3 files changed

+95
-9
lines changed

3 files changed

+95
-9
lines changed

packages/gitbook/e2e/pages.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,42 @@ const testCases: TestsCase[] = [
491491
},
492492
],
493493
},
494+
{
495+
name: 'Shared space navigation (first site)',
496+
baseUrl: 'https://gitbook-open-e2e-sites.gitbook.io/shared-space-uno/',
497+
tests: [
498+
{
499+
name: 'Navigation to shared space',
500+
url: '',
501+
run: async (page) => {
502+
const sharedSpaceLink = page.locator('a.underline');
503+
await sharedSpaceLink.click();
504+
expect(page.locator('h1')).toHaveText('shared');
505+
const url = page.url();
506+
expect(url.includes('shared-space-uno')).toBeTruthy(); // same uno site
507+
expect(url.endsWith('/shared')).toBeTruthy(); // correct page
508+
},
509+
},
510+
],
511+
},
512+
{
513+
name: 'Shared space navigation (second site)',
514+
baseUrl: 'https://gitbook-open-e2e-sites.gitbook.io/shared-space-dos/',
515+
tests: [
516+
{
517+
name: 'Navigation to shared space',
518+
url: '',
519+
run: async (page) => {
520+
const sharedSpaceLink = page.locator('a.underline');
521+
await sharedSpaceLink.click();
522+
expect(page.locator('h1')).toHaveText('shared');
523+
const url = page.url();
524+
expect(url.includes('shared-space-dos')).toBeTruthy(); // same dos site
525+
expect(url.endsWith('/shared')).toBeTruthy(); // correct page
526+
},
527+
},
528+
],
529+
},
494530
{
495531
name: 'Site Redirects',
496532
baseUrl: 'https://gitbook-open-e2e-sites.gitbook.io/gitbook-doc/',

packages/gitbook/src/lib/api.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,10 @@ export const getPublishedContentSite = cache({
713713

714714
export type SectionsList = { list: SiteSection[]; section: SiteSection; index: number };
715715

716-
function parseSpacesFromSiteSpaces(siteSpaces: SiteSpace[]) {
716+
/**
717+
* Parse the site spaces into a list of spaces with their title and urls.
718+
*/
719+
export function parseSpacesFromSiteSpaces(siteSpaces: SiteSpace[]) {
717720
const spaces: Record<string, Space> = {};
718721
siteSpaces.forEach((siteSpace) => {
719722
spaces[siteSpace.space.id] = {

packages/gitbook/src/lib/references.tsx

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {
22
ContentRef,
3+
ContentRefSpace,
34
Revision,
45
RevisionFile,
56
RevisionPageDocument,
67
RevisionReusableContent,
8+
SiteSpace,
79
Space,
810
} from '@gitbook/api';
911
import assertNever from 'assert-never';
@@ -16,12 +18,14 @@ import {
1618
SpaceContentPointer,
1719
getCollection,
1820
getDocument,
21+
getPublishedContentSite,
1922
getReusableContent,
2023
getRevisionFile,
2124
getSpace,
2225
getSpaceContentData,
2326
getUserById,
2427
ignoreAPIError,
28+
parseSpacesFromSiteSpaces,
2529
} from './api';
2630
import { getBlockById, getBlockTitle } from './document';
2731
import { gitbookAppHref, pageHref, PageHrefContext } from './links';
@@ -205,12 +209,7 @@ export async function resolveContentRef(
205209
const targetSpace =
206210
contentRef.space === space.id
207211
? space
208-
: await ignoreAPIError(
209-
getSpace(
210-
contentRef.space,
211-
siteContext?.siteShareKey ? siteContext.siteShareKey : undefined,
212-
),
213-
);
212+
: await getBestTargetSpace(contentRef.space, siteContext);
214213

215214
if (!targetSpace) {
216215
return {
@@ -287,6 +286,50 @@ export async function resolveContentRef(
287286
}
288287
}
289288

289+
/**
290+
* This function is used to get the best possible target space while resolving a content ref.
291+
* It will try to return the space in the site context if it exists to avoid cross-site links.
292+
*/
293+
async function getBestTargetSpace(
294+
spaceId: string,
295+
siteContext: SiteContentPointer | null,
296+
): Promise<Space | undefined> {
297+
const [fetchedSpace, publishedContentSite] = await Promise.all([
298+
ignoreAPIError(
299+
getSpace(spaceId, siteContext?.siteShareKey ? siteContext.siteShareKey : undefined),
300+
),
301+
siteContext
302+
? ignoreAPIError(
303+
getPublishedContentSite({
304+
organizationId: siteContext.organizationId,
305+
siteId: siteContext.siteId,
306+
siteShareKey: siteContext.siteShareKey,
307+
}),
308+
)
309+
: null,
310+
]);
311+
312+
// In the context of sites, we try to find our target space in the site structure.
313+
// because the url of this space will be in the same site.
314+
if (publishedContentSite) {
315+
const siteSpaces =
316+
publishedContentSite.structure.type === 'siteSpaces'
317+
? publishedContentSite.structure.structure
318+
: publishedContentSite.structure.structure.reduce<SiteSpace[]>((acc, section) => {
319+
acc.push(...section.siteSpaces);
320+
return acc;
321+
}, []);
322+
const spaces = parseSpacesFromSiteSpaces(siteSpaces);
323+
const foundSpace = spaces.find((space) => space.id === spaceId);
324+
if (foundSpace) {
325+
return foundSpace;
326+
}
327+
}
328+
329+
// Else we try return the fetched space from the API.
330+
return fetchedSpace ?? undefined;
331+
}
332+
290333
async function resolveContentRefInSpace(
291334
spaceId: string,
292335
siteContext: SiteContentPointer | null,
@@ -296,12 +339,16 @@ async function resolveContentRefInSpace(
296339
spaceId,
297340
};
298341

299-
const result = await ignoreAPIError(getSpaceContentData(pointer, siteContext?.siteShareKey));
342+
const [result, bestTargetSpace] = await Promise.all([
343+
ignoreAPIError(getSpaceContentData(pointer, siteContext?.siteShareKey)),
344+
getBestTargetSpace(spaceId, siteContext),
345+
]);
300346
if (!result) {
301347
return null;
302348
}
303349

304-
const { space, pages } = result;
350+
const { pages } = result;
351+
const space = bestTargetSpace ?? result.space;
305352

306353
// Base URL to use to prepend to relative URLs.
307354
let baseUrl = space.urls.published ?? space.urls.app;

0 commit comments

Comments
 (0)