Skip to content

Commit 6597f85

Browse files
authored
V2: search and ask AI (#2889)
1 parent 8beb5d6 commit 6597f85

File tree

24 files changed

+603
-403
lines changed

24 files changed

+603
-403
lines changed

bun.lock

Lines changed: 42 additions & 13 deletions
Large diffs are not rendered by default.

packages/gitbook-v2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.1",
44
"private": true,
55
"dependencies": {
6-
"next": "canary",
6+
"next": "^15.2.0",
77
"react": "^19.0.0",
88
"react-dom": "^19.0.0",
99
"@gitbook/api": "0.96.1",

packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '@/components/SiteLayout';
77
import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils';
88
import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env';
9-
import { getThemeFromMiddleware } from '@v2/lib/middleware';
9+
import { getThemeFromMiddleware, getVisitorAuthTokenFromMiddleware } from '@v2/lib/middleware';
1010

1111
interface SiteDynamicLayoutProps {
1212
params: Promise<RouteLayoutParams>;
@@ -18,13 +18,15 @@ export default async function SiteDynamicLayout({
1818
}: React.PropsWithChildren<SiteDynamicLayoutProps>) {
1919
const context = await getDynamicSiteContext(await params);
2020
const forcedTheme = await getThemeFromMiddleware();
21+
const visitorAuthToken = await getVisitorAuthTokenFromMiddleware();
2122

2223
return (
2324
<CustomizationRootLayout customization={context.customization}>
2425
<SiteLayout
2526
context={context}
2627
forcedTheme={forcedTheme}
2728
withTracking={!GITBOOK_DISABLE_TRACKING}
29+
visitorAuthToken={visitorAuthToken}
2830
>
2931
{children}
3032
</SiteLayout>

packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export default async function SiteStaticLayout({
2525

2626
return (
2727
<CustomizationRootLayout customization={context.customization}>
28-
<SiteLayout context={context} withTracking={!GITBOOK_DISABLE_TRACKING}>
28+
<SiteLayout
29+
context={context}
30+
withTracking={!GITBOOK_DISABLE_TRACKING}
31+
visitorAuthToken={null}
32+
>
2933
{children}
3034
</SiteLayout>
3135
</CustomizationRootLayout>

packages/gitbook-v2/src/app/utils.ts

Lines changed: 23 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { fetchSiteContextByURL } from '@v2/lib/context';
2-
import { createDataFetcher } from '@v2/lib/data';
3-
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_URL } from '@v2/lib/env';
4-
import { createImageResizer } from '@v2/lib/images';
5-
import { createLinker } from '@v2/lib/links';
6-
import { headers } from 'next/headers';
1+
import {
2+
fetchSiteContextByURL,
3+
fetchSiteContextByURLLookup,
4+
getBaseContext,
5+
} from '@v2/lib/context';
6+
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
77

88
export type RouteParamMode = 'url-host' | 'url';
99

@@ -21,66 +21,36 @@ export type RouteParams = RouteLayoutParams & {
2121
/**
2222
* Get the static context when rendering statically a site.
2323
*/
24-
export async function getStaticSiteContext(params: RouteLayoutParams) {
25-
const url = getSiteURLFromParams(params);
26-
27-
const dataFetcher = createDataFetcher();
28-
const { linker, host } = createLinkerFromParams(params);
29-
const context = await fetchSiteContextByURL(
30-
{
31-
dataFetcher,
32-
linker,
33-
},
24+
export function getStaticSiteContext(params: RouteLayoutParams) {
25+
const siteURL = getSiteURLFromParams(params);
26+
return fetchSiteContextByURL(
27+
getBaseContext({
28+
siteURL,
29+
urlMode: getModeFromParams(params.mode),
30+
}),
3431
{
35-
url: url.toString(),
32+
url: siteURL.toString(),
3633
visitorAuthToken: null,
3734
redirectOnError: false,
3835
}
3936
);
40-
41-
context.imageResizer = createImageResizer({
42-
host,
43-
linker: context.linker,
44-
});
45-
46-
return context;
4737
}
4838

4939
/**
5040
* Get the site context when rendering dynamically.
5141
* The context will depend on the request.
5242
*/
5343
export async function getDynamicSiteContext(params: RouteLayoutParams) {
54-
const url = getSiteURLFromParams(params);
55-
const headersSet = await headers();
56-
57-
const dataFetcher = createDataFetcher({
58-
apiToken: headersSet.get('x-gitbook-token') ?? GITBOOK_API_TOKEN,
59-
apiEndpoint: headersSet.get('x-gitbook-api') ?? GITBOOK_API_URL,
60-
});
61-
62-
const { linker, host } = createLinkerFromParams(params);
63-
64-
const context = await fetchSiteContextByURL(
65-
{
66-
dataFetcher,
67-
linker,
68-
},
69-
{
70-
url: url.toString(),
71-
visitorAuthToken: headersSet.get('x-gitbook-visitor-token'),
72-
73-
// TODO: set it only when the token comes from the cookies.
74-
redirectOnError: true,
75-
}
44+
const siteURL = getSiteURLFromParams(params);
45+
const siteURLData = await getSiteURLDataFromMiddleware();
46+
47+
return fetchSiteContextByURLLookup(
48+
getBaseContext({
49+
siteURL,
50+
urlMode: getModeFromParams(params.mode),
51+
}),
52+
siteURLData
7653
);
77-
78-
context.imageResizer = createImageResizer({
79-
host,
80-
linker: context.linker,
81-
});
82-
83-
return context;
8454
}
8555

8656
/**
@@ -91,39 +61,6 @@ export function getPagePathFromParams(params: RouteParams) {
9161
return decoded;
9262
}
9363

94-
function createLinkerFromParams(params: RouteLayoutParams) {
95-
const url = getSiteURLFromParams(params);
96-
const mode = getModeFromParams(params.mode);
97-
98-
if (mode === 'url-host') {
99-
return {
100-
linker: createLinker({
101-
host: url.host,
102-
pathname: '/',
103-
}),
104-
host: url.host,
105-
};
106-
}
107-
108-
const gitbookURL = new URL(GITBOOK_URL);
109-
const linker = createLinker({
110-
protocol: gitbookURL.protocol,
111-
host: gitbookURL.host,
112-
pathname: `/url/${url.host}`,
113-
});
114-
115-
// Create link in the same format for links to other sites/sections.
116-
linker.toLinkForContent = (rawURL: string) => {
117-
const urlObject = new URL(rawURL);
118-
return `/url/${urlObject.host}${urlObject.pathname}`;
119-
};
120-
121-
return {
122-
linker,
123-
host: gitbookURL.host,
124-
};
125-
}
126-
12764
function getSiteURLFromParams(params: RouteLayoutParams) {
12865
const decoded = decodeURIComponent(params.siteURL);
12966
const url = new URL(`https://${decoded}`);

packages/gitbook-v2/src/lib/context.ts

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getSiteStructureSections } from '@/lib/sites';
22
import type {
33
ChangeRequest,
4+
PublishedSiteContentLookup,
45
RevisionPage,
56
RevisionPageDocument,
67
Site,
@@ -15,8 +16,9 @@ import type {
1516
import { type GitBookDataFetcher, createDataFetcher } from '@v2/lib/data';
1617
import { redirect } from 'next/navigation';
1718
import { assert } from 'ts-essentials';
18-
import type { ImageResizer } from './images';
19-
import { type GitBookSpaceLinker, appendBasePathToLinker } from './links';
19+
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_URL } from './env';
20+
import { type ImageResizer, createImageResizer } from './images';
21+
import { type GitBookSpaceLinker, createLinker } from './links';
2022

2123
/**
2224
* Generic context when rendering content.
@@ -90,9 +92,6 @@ export type GitBookSiteContext = GitBookSpaceContext & {
9092

9193
/** Scripts to load for the site. */
9294
scripts: SiteIntegrationScript[];
93-
94-
/** Visitor token used to fetch the site */
95-
visitorAuthToken: string | null;
9695
};
9796

9897
/**
@@ -102,6 +101,58 @@ export type GitBookPageContext = (GitBookSpaceContext | GitBookSiteContext) & {
102101
page: RevisionPageDocument;
103102
};
104103

104+
/**
105+
* Get the base context for a request.
106+
*/
107+
export function getBaseContext(input: {
108+
siteURL: URL | string;
109+
urlMode: 'url' | 'url-host';
110+
apiToken?: string | null;
111+
}) {
112+
const url = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL;
113+
const urlMode = input.urlMode;
114+
115+
const dataFetcher = createDataFetcher({
116+
apiToken: input.apiToken ?? GITBOOK_API_TOKEN,
117+
apiEndpoint: GITBOOK_API_URL,
118+
});
119+
const gitbookURL = new URL(GITBOOK_URL);
120+
121+
const linker =
122+
urlMode === 'url-host'
123+
? createLinker({
124+
host: url.host,
125+
pathname: url.pathname,
126+
})
127+
: createLinker({
128+
protocol: gitbookURL.protocol,
129+
host: gitbookURL.host,
130+
pathname: `/url/${url.host}${url.pathname}`,
131+
});
132+
133+
if (urlMode === 'url') {
134+
// Create link in the same format for links to other sites/sections.
135+
linker.toLinkForContent = (rawURL: string) => {
136+
const urlObject = new URL(rawURL);
137+
return `/url/${urlObject.host}${urlObject.pathname}`;
138+
};
139+
}
140+
141+
const imageResizer = createImageResizer({
142+
host: urlMode === 'url-host' ? url.host : gitbookURL.host,
143+
144+
// To ensure image resizing work for proxied sites,
145+
// we serve images from the root of the site.
146+
linker: linker,
147+
});
148+
149+
return {
150+
dataFetcher,
151+
linker,
152+
imageResizer,
153+
};
154+
}
155+
105156
/**
106157
* Fetch the context of a site for a given URL and a base context.
107158
*/
@@ -120,15 +171,25 @@ export async function fetchSiteContextByURL(
120171
redirectOnError: input.redirectOnError,
121172
});
122173

174+
return fetchSiteContextByURLLookup(baseContext, data);
175+
}
176+
177+
/**
178+
* Fetch the context of a site using the resolution of a URL
179+
*/
180+
export async function fetchSiteContextByURLLookup(
181+
baseContext: GitBookBaseContext,
182+
data: PublishedSiteContentLookup
183+
): Promise<GitBookSiteContext> {
184+
const { dataFetcher } = baseContext;
123185
if ('redirect' in data) {
124186
redirect(data.redirect);
125187
}
126188

127-
const context = await fetchSiteContextByIds(
189+
return await fetchSiteContextByIds(
128190
{
129191
...baseContext,
130-
dataFetcher: createDataFetcher({
131-
apiEndpoint: dataFetcher.apiEndpoint,
192+
dataFetcher: dataFetcher.withToken({
132193
apiToken: data.apiToken,
133194
}),
134195
},
@@ -141,16 +202,8 @@ export async function fetchSiteContextByURL(
141202
shareKey: data.shareKey,
142203
changeRequest: data.changeRequest,
143204
revision: data.revision,
144-
visitorAuthToken: input.visitorAuthToken,
145205
}
146206
);
147-
148-
const siteContext = {
149-
...context,
150-
linker: appendBasePathToLinker(context.linker, data.basePath),
151-
};
152-
153-
return siteContext;
154207
}
155208

156209
/**
@@ -167,7 +220,6 @@ export async function fetchSiteContextByIds(
167220
shareKey: string | undefined;
168221
changeRequest: string | undefined;
169222
revision: string | undefined;
170-
visitorAuthToken: string | null;
171223
}
172224
): Promise<GitBookSiteContext> {
173225
const { dataFetcher } = baseContext;
@@ -233,7 +285,6 @@ export async function fetchSiteContextByIds(
233285
structure: siteStructure,
234286
sections,
235287
scripts,
236-
visitorAuthToken: ids.visitorAuthToken,
237288
};
238289
}
239290

packages/gitbook-v2/src/lib/data/api.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
3535
return {
3636
apiEndpoint: input.apiEndpoint,
3737

38+
async api() {
39+
return getAPI(input);
40+
},
41+
42+
withToken({ apiToken }) {
43+
return createDataFetcher({
44+
...input,
45+
apiToken,
46+
});
47+
},
48+
3849
//
3950
// API that are tied to the token
4051
//
@@ -124,6 +135,9 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
124135
spaceId: params.spaceId,
125136
});
126137
},
138+
searchSiteContent(params) {
139+
return searchSiteContent(input, params);
140+
},
127141

128142
//
129143
// API that are not tied to the token
@@ -498,6 +512,23 @@ async function getEmbedByUrl(
498512
return res.data;
499513
}
500514

515+
async function searchSiteContent(
516+
input: DataFetcherInput,
517+
params: Parameters<GitBookDataFetcher['searchSiteContent']>[0]
518+
) {
519+
'use cache';
520+
521+
const { organizationId, siteId, query, scope } = params;
522+
523+
cacheLife('days');
524+
525+
const res = await getAPI(input).orgs.searchSiteContent(organizationId, siteId, {
526+
query,
527+
...scope,
528+
});
529+
return res.data.items;
530+
}
531+
501532
function getAPI(input: DataFetcherInput) {
502533
const { apiEndpoint, apiToken } = input;
503534
const api = new GitBookAPI({

0 commit comments

Comments
 (0)