Skip to content

Commit 0c22486

Browse files
authored
Sites support (#2262)
1 parent a007604 commit 0c22486

File tree

25 files changed

+443
-129
lines changed

25 files changed

+443
-129
lines changed

bun.lockb

-360 Bytes
Binary file not shown.

e2e/pages.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,41 @@ async function waitForCookiesDialog(page: Page) {
4040
}
4141

4242
const testCases: TestsCase[] = [
43+
{
44+
name: 'GitBook Site',
45+
baseUrl: 'https://gitbook.gitbook.io/gitbook-site/',
46+
tests: [
47+
{
48+
name: 'Home',
49+
url: '',
50+
run: waitForCookiesDialog,
51+
},
52+
{
53+
name: 'Search',
54+
url: '?q=',
55+
},
56+
{
57+
name: 'Search Results',
58+
url: '?q=gitbook',
59+
run: async (page) => {
60+
await page.waitForSelector('[data-test="search-results"]');
61+
},
62+
},
63+
{
64+
name: 'AI Search',
65+
url: '?q=What+is+GitBook%3F&ask=true',
66+
run: async (page) => {
67+
await page.waitForSelector('[data-test="search-ask-answer"]');
68+
},
69+
screenshot: false,
70+
},
71+
{
72+
name: 'Not found',
73+
url: 'content-not-found',
74+
run: waitForCookiesDialog,
75+
},
76+
],
77+
},
4378
{
4479
name: 'GitBook',
4580
baseUrl: 'https://docs.gitbook.com',

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
],
2121
"dependencies": {
2222
"@geist-ui/icons": "^1.0.2",
23-
"@gitbook/api": "^0.39.0",
23+
"@gitbook/api": "^0.41.0",
2424
"@radix-ui/react-checkbox": "^1.0.4",
2525
"@radix-ui/react-popover": "^1.0.7",
2626
"@sentry/nextjs": "^7.94.1",

packages/react-contentkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"exports": "./src/index.ts",
44
"dependencies": {
55
"classnames": "^2.5.1",
6-
"@gitbook/api": "^0.36.0",
6+
"@gitbook/api": "^0.41.0",
77
"assert-never": "^1.2.1"
88
},
99
"peerDependencies": {

src/app/(space)/(content)/[[...pathname]]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,13 @@ export async function generateViewport({ params }: { params: PagePathParams }):
104104
}
105105

106106
export async function generateMetadata({ params }: { params: PagePathParams }): Promise<Metadata> {
107-
const { space, pages, page, customization, collection } = await fetchPageData(params);
107+
const { space, pages, page, customization, parent } = await fetchPageData(params);
108108
if (!page) {
109109
notFound();
110110
}
111111

112112
return {
113-
title: [page.title, customization.title ?? space.title, collection?.title]
113+
title: [page.title, customization.title ?? space.title, parent?.title]
114114
.filter(Boolean)
115115
.join(' | '),
116116
description: page.description ?? '',

src/app/(space)/(content)/layout.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export default async function ContentLayout(props: { children: React.ReactNode }
3232
contentTarget,
3333
customization,
3434
pages,
35-
collection,
36-
collectionSpaces,
35+
parent,
36+
spaces,
3737
ancestors,
3838
scripts,
3939
} = await fetchSpaceData();
@@ -56,8 +56,8 @@ export default async function ContentLayout(props: { children: React.ReactNode }
5656
<SpaceLayout
5757
space={space}
5858
contentTarget={contentTarget}
59-
collection={collection}
60-
collectionSpaces={collectionSpaces}
59+
parent={parent}
60+
spaces={spaces}
6161
customization={customization}
6262
pages={pages}
6363
ancestors={ancestors}
@@ -100,11 +100,11 @@ export async function generateViewport(): Promise<Viewport> {
100100
}
101101

102102
export async function generateMetadata(): Promise<Metadata> {
103-
const { space, collection, customization } = await fetchSpaceData();
103+
const { space, parent, customization } = await fetchSpaceData();
104104
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
105105

106106
return {
107-
title: `${collection ? collection.title : customization.title ?? space.title}`,
107+
title: `${parent ? parent.title : customization.title ?? space.title}`,
108108
generator: `GitBook (${buildVersion()})`,
109109
metadataBase: new URL(baseUrl()),
110110
icons: {
@@ -125,7 +125,7 @@ export async function generateMetadata(): Promise<Metadata> {
125125
},
126126
],
127127
},
128-
robots: shouldIndexSpace({ space, collection }) ? 'index, follow' : 'noindex, nofollow',
128+
robots: shouldIndexSpace({ space, parent }) ? 'index, follow' : 'noindex, nofollow',
129129
};
130130
}
131131

src/app/(space)/(core)/robots.txt/route.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ContentVisibility } from '@gitbook/api';
22
import { NextRequest } from 'next/server';
33

4-
import { getCollection, getSpace } from '@/lib/api';
4+
import { getCollection, getSite, getSpace } from '@/lib/api';
55
import { absoluteHref } from '@/lib/links';
66
import { shouldIndexSpace } from '@/lib/seo';
77

@@ -13,16 +13,19 @@ export const runtime = 'edge';
1313
* Generate a robots.txt for the current space.
1414
*/
1515
export async function GET(req: NextRequest) {
16-
const space = await getSpace(getContentPointer().spaceId);
17-
const collection =
18-
space.visibility === ContentVisibility.InCollection && space.parent
19-
? await getCollection(space.parent)
20-
: null;
16+
const pointer = getContentPointer();
17+
const space = await getSpace(pointer.spaceId);
18+
const parent =
19+
'siteId' in pointer
20+
? await getSite(pointer.organizationId, pointer.siteId)
21+
: space.visibility === ContentVisibility.InCollection && space.parent
22+
? await getCollection(space.parent)
23+
: null;
2124

2225
const lines = [
2326
`User-agent: *`,
2427
'Disallow: /~gitbook/',
25-
...(shouldIndexSpace({ space, collection })
28+
...(shouldIndexSpace({ space, parent })
2629
? [`Allow: /`, `Sitemap: ${absoluteHref(`/sitemap.xml`, true)}`]
2730
: [`Disallow: /`]),
2831
];

src/app/(space)/(core)/~gitbook/ogimage/[pageId]/route.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const runtime = 'edge';
1111
* Render the OpenGraph image for a space.
1212
*/
1313
export async function GET(req: NextRequest, { params }: { params: PageIdParams }) {
14-
const { space, page, customization, collection } = await fetchPageData(params);
14+
const { space, page, customization, parent } = await fetchPageData(params);
1515
const url = new URL(space.urls.published ?? space.urls.app);
1616

1717
if (customization.socialPreview.url) {
@@ -31,7 +31,7 @@ export async function GET(req: NextRequest, { params }: { params: PageIdParams }
3131
}}
3232
>
3333
<h2 tw="text-7xl font-bold tracking-tight text-left">
34-
{collection?.title ?? customization.title ?? space.title}
34+
{parent?.title ?? customization.title ?? space.title}
3535
</h2>
3636
<div tw="flex flex-1">
3737
<p tw="text-4xl">{page ? page.title : 'Not found'}</p>

src/app/(space)/fetch.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {
99
getDocument,
1010
getSpaceData,
1111
ContentTarget,
12+
SiteContentPointer,
13+
getSiteSpaceData,
14+
getSite,
15+
getSiteSpaces,
1216
} from '@/lib/api';
1317
import { resolvePagePath, resolvePageId } from '@/lib/pages';
1418

@@ -23,7 +27,7 @@ export interface PageIdParams {
2327
/**
2428
* Get the current content pointer from the params.
2529
*/
26-
export function getContentPointer() {
30+
export function getContentPointer(): ContentPointer | SiteContentPointer {
2731
const headerSet = headers();
2832
const spaceId = headerSet.get('x-gitbook-content-space');
2933
if (!spaceId) {
@@ -32,22 +36,46 @@ export function getContentPointer() {
3236
);
3337
}
3438

35-
const content: ContentPointer = {
36-
spaceId,
37-
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
38-
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
39-
};
39+
const siteId = headerSet.get('x-gitbook-content-site');
40+
if (siteId) {
41+
const organizationId = headerSet.get('x-gitbook-content-organization');
42+
const siteSpaceId = headerSet.get('x-gitbook-content-site-space');
43+
if (!organizationId || !siteSpaceId) {
44+
throw new Error('Missing site content headers');
45+
}
4046

41-
return content;
47+
const siteContent: SiteContentPointer = {
48+
siteId,
49+
spaceId,
50+
siteSpaceId,
51+
organizationId,
52+
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
53+
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
54+
};
55+
return siteContent;
56+
} else {
57+
const content: ContentPointer = {
58+
spaceId,
59+
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
60+
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
61+
};
62+
return content;
63+
}
4264
}
4365

4466
/**
4567
* Fetch all the data needed to render the space layout.
4668
*/
4769
export async function fetchSpaceData() {
4870
const content = getContentPointer();
49-
const { space, contentTarget, pages, customization, scripts } = await getSpaceData(content);
50-
const collection = await fetchParentCollection(space);
71+
72+
const [{ space, contentTarget, pages, customization, scripts }, parentSite] = await Promise.all(
73+
'siteId' in content
74+
? [getSiteSpaceData(content), fetchParentSite(content.organizationId, content.siteId)]
75+
: [getSpaceData(content)],
76+
);
77+
78+
const parent = await (parentSite ?? fetchParentCollection(space));
5179

5280
return {
5381
content,
@@ -57,7 +85,7 @@ export async function fetchSpaceData() {
5785
customization,
5886
scripts,
5987
ancestors: [],
60-
...collection,
88+
...parent,
6189
};
6290
}
6391

@@ -133,12 +161,26 @@ async function resolvePage(
133161
async function fetchParentCollection(space: Space) {
134162
const parentCollectionId =
135163
space.visibility === ContentVisibility.InCollection ? space.parent : undefined;
136-
const [collection, collectionSpaces] = await Promise.all([
164+
const [collection, spaces] = await Promise.all([
137165
parentCollectionId ? getCollection(parentCollectionId) : null,
138166
parentCollectionId ? getCollectionSpaces(parentCollectionId) : ([] as Space[]),
139167
]);
140168

141-
return { collection, collectionSpaces };
169+
return { parent: collection, spaces };
170+
}
171+
172+
async function fetchParentSite(organizationId: string, siteId: string) {
173+
const [site, siteSpaces] = await Promise.all([
174+
getSite(organizationId, siteId),
175+
getSiteSpaces(organizationId, siteId),
176+
]);
177+
178+
const spaces: Record<string, Space> = {};
179+
siteSpaces.forEach((siteSpace) => {
180+
spaces[siteSpace.space.id] = siteSpace.space;
181+
});
182+
183+
return { parent: site, spaces: Object.values(spaces) };
142184
}
143185

144186
/**

src/components/Footer/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CustomizationSettings, Space } from '@gitbook/api';
1+
import { CustomizationSettings, SiteCustomizationSettings, Space } from '@gitbook/api';
22
import React from 'react';
33

44
import { Image } from '@/components/utils';
@@ -12,7 +12,7 @@ import { ThemeToggler } from '../ThemeToggler';
1212
export function Footer(props: {
1313
space: Space;
1414
context: ContentRefContext;
15-
customization: CustomizationSettings;
15+
customization: CustomizationSettings | SiteCustomizationSettings;
1616
}) {
1717
const { context, customization } = props;
1818

0 commit comments

Comments
 (0)