11import {
22 ContentRef ,
3+ ContentRefSpace ,
34 Revision ,
45 RevisionFile ,
56 RevisionPageDocument ,
67 RevisionReusableContent ,
8+ SiteSpace ,
79 Space ,
810} from '@gitbook/api' ;
911import 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' ;
2630import { getBlockById , getBlockTitle } from './document' ;
2731import { 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+
290333async 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