Compare commits

...

6 Commits

Author SHA1 Message Date
taranvohra d6b9fe6643 Bump api package 2024-10-11 12:58:44 +05:30
taranvohra 766c97a100 Merge remote-tracking branch 'origin/main' into cleanup-legacy-code 2024-10-11 12:58:05 +05:30
taranvohra 29fca1e835 bun.lockb 2024-10-07 02:01:02 +05:30
taranvohra 48b5c0707b Merge main 2024-10-07 01:52:50 +05:30
taranvohra 434cfd7a08 WIP 2024-09-12 18:02:33 +02:00
taranvohra e3587d59aa use gitbook/api version 0.61 2024-09-12 17:40:55 +02:00
27 changed files with 177 additions and 321 deletions
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
},
"dependencies": {
"@gitbook/api": "0.60.0",
"@gitbook/api": "0.64.1",
"@gitbook/cache-do": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/icons": "workspace:*",
@@ -1,14 +1,12 @@
import { getSpaceLanguage, t } from '@/intl/server';
import { getCurrentSiteLayoutData, getSpaceLayoutData } from '@/lib/api';
import { getCurrentSiteLayoutData } from '@/lib/api';
import { tcls } from '@/lib/tailwind';
import { getContentPointer } from '../../fetch';
import { getSiteContentPointer } from '../../fetch';
export default async function NotFound() {
const pointer = getContentPointer();
const { customization } = await ('siteId' in pointer
? getCurrentSiteLayoutData(pointer)
: getSpaceLayoutData(pointer.spaceId));
const pointer = getSiteContentPointer();
const { customization } = await getCurrentSiteLayoutData(pointer);
const language = getSpaceLanguage(customization);
@@ -65,7 +65,7 @@ export default async function Page(props: {
const withPageFeedback = customization.feedback.enabled;
const contentRefContext: ContentRefContext = {
siteContext: 'siteId' in contentPointer ? contentPointer : null,
siteContext: contentPointer,
space,
revisionId: contentTarget.revisionId,
pages,
@@ -5,7 +5,7 @@ import { getCollection, getSite, getSpace } from '@/lib/api';
import { absoluteHref } from '@/lib/links';
import { isSpaceIndexable } from '@/lib/seo';
import { getContentPointer } from '../../fetch';
import { getSiteContentPointer } from '../../fetch';
export const runtime = 'edge';
@@ -13,17 +13,9 @@ export const runtime = 'edge';
* Generate a robots.txt for the current space.
*/
export async function GET(req: NextRequest) {
const pointer = getContentPointer();
const space = await getSpace(
pointer.spaceId,
'siteId' in pointer ? pointer.siteShareKey : undefined,
);
const parent =
'siteId' in pointer
? await getSite(pointer.organizationId, pointer.siteId)
: space.visibility === ContentVisibility.InCollection && space.parent
? await getCollection(space.parent)
: null;
const pointer = getSiteContentPointer();
const space = await getSpace(pointer.spaceId, pointer.siteShareKey);
const parent = await getSite(pointer.organizationId, pointer.siteId);
const lines = [
`User-agent: *`,
@@ -7,7 +7,7 @@ import { absoluteHref } from '@/lib/links';
import { getPagePath } from '@/lib/pages';
import { isPageIndexable } from '@/lib/seo';
import { getContentPointer } from '../../fetch';
import { getSiteContentPointer } from '../../fetch';
export const runtime = 'edge';
@@ -4,7 +4,7 @@ import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
import React from 'react';
import { getContentPointer } from '@/app/(space)/fetch';
import { getSiteContentPointer } from '@/app/(space)/fetch';
import {
getCollection,
getCurrentSiteCustomization,
@@ -42,19 +42,15 @@ export async function GET(req: NextRequest) {
const options = getOptions(req.url);
const size = SIZES[options.size];
const pointer = getContentPointer();
const pointer = getSiteContentPointer();
const spaceId = pointer.spaceId;
const [space, customization] = await Promise.all([
getSpace(spaceId, 'siteId' in pointer ? pointer.siteShareKey : undefined),
'siteId' in pointer ? getCurrentSiteCustomization(pointer) : getSpaceCustomization(spaceId),
getSpace(spaceId, pointer.siteShareKey),
getCurrentSiteCustomization(pointer),
]);
const parent =
'siteId' in pointer
? await getSite(pointer.organizationId, pointer.siteId)
: space.visibility === ContentVisibility.InCollection && space.parent
? await getCollection(space.parent)
: null;
const parent = await getSite(pointer.organizationId, pointer.siteId);
const contentTitle = getContentTitle(space, customization, parent);
return new ImageResponse(
@@ -11,7 +11,7 @@ import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import * as React from 'react';
import { getContentPointer } from '@/app/(space)/fetch';
import { getSiteContentPointer } from '@/app/(space)/fetch';
import { DocumentView, createHighlightingContext } from '@/components/DocumentView';
import { TrademarkLink } from '@/components/TableOfContents/Trademark';
import { PolymorphicComponentProp } from '@/components/utils/types';
@@ -39,15 +39,10 @@ const DEFAULT_LIMIT = 100;
export const runtime = 'edge';
export async function generateMetadata(): Promise<Metadata> {
const contentPointer = getContentPointer();
const contentPointer = getSiteContentPointer();
const [space, customization] = await Promise.all([
getSpace(
contentPointer.spaceId,
'siteId' in contentPointer ? contentPointer.siteShareKey : undefined,
),
'siteId' in contentPointer
? getCurrentSiteCustomization(contentPointer)
: getSpaceCustomization(contentPointer.spaceId),
getSpace(contentPointer.spaceId, contentPointer.siteShareKey),
getCurrentSiteCustomization(contentPointer),
]);
return {
@@ -60,7 +55,7 @@ export async function generateMetadata(): Promise<Metadata> {
* Render a space as a standalone HTML page that can be printed as a PDF.
*/
export default async function PDFHTMLOutput(props: { searchParams: { [key: string]: string } }) {
const contentPointer = getContentPointer();
const contentPointer = getSiteContentPointer();
const searchParams = new URLSearchParams(props.searchParams);
const pdfParams = getPDFSearchParams(new URLSearchParams(searchParams));
@@ -71,13 +66,8 @@ export default async function PDFHTMLOutput(props: { searchParams: { [key: strin
// Load the content,
const [customization, { space, contentTarget, pages: rootPages }] = await Promise.all([
'siteId' in contentPointer
? getCurrentSiteCustomization(contentPointer)
: getSpaceCustomization(contentPointer.spaceId),
getSpaceContentData(
contentPointer,
'siteId' in contentPointer ? contentPointer.siteShareKey : undefined,
),
getCurrentSiteCustomization(contentPointer),
getSpaceContentData(contentPointer),
]);
const language = getSpaceLanguage(customization);
@@ -175,7 +165,7 @@ export default async function PDFHTMLOutput(props: { searchParams: { [key: strin
space={space}
page={page}
refContext={{
siteContext: 'siteId' in contentPointer ? contentPointer : null,
siteContext: contentPointer,
space,
revisionId: contentTarget.revisionId,
pages: rootPages,
+33 -55
View File
@@ -4,10 +4,8 @@ import { headers } from 'next/headers';
import {
getCollectionSpaces,
getCollection,
ContentPointer,
getRevisionPageByPath,
getDocument,
getSpaceData,
ContentTarget,
SiteContentPointer,
getCurrentSiteData,
@@ -26,53 +24,48 @@ export interface PageIdParams {
}
/**
* Get the current content pointer from the params.
* Get the current site content pointer from the params.
*/
export function getContentPointer(): ContentPointer | SiteContentPointer {
export function getSiteContentPointer(): SiteContentPointer {
const headerSet = headers();
const spaceId = headerSet.get('x-gitbook-content-space');
if (!spaceId) {
throw new Error(
'getContentPointer is called outside the scope of a request processed by the middleware',
'getSiteContentPointer is called outside the scope of a request processed by the middleware',
);
}
const siteId = headerSet.get('x-gitbook-content-site');
if (siteId) {
const organizationId = headerSet.get('x-gitbook-content-organization');
const siteSpaceId = headerSet.get('x-gitbook-content-site-space');
const siteShareKey = headerSet.get('x-gitbook-content-site-share-key');
if (!organizationId) {
throw new Error('Missing site content headers');
}
const siteContent: SiteContentPointer = {
siteId,
spaceId,
siteSpaceId: siteSpaceId ?? undefined,
siteShareKey: siteShareKey ?? undefined,
organizationId,
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
};
return siteContent;
} else {
const content: ContentPointer = {
spaceId,
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
};
return content;
if (!siteId) {
throw new Error('Expected site content headers');
}
const organizationId = headerSet.get('x-gitbook-content-organization');
const siteSpaceId = headerSet.get('x-gitbook-content-site-space');
const siteShareKey = headerSet.get('x-gitbook-content-site-share-key');
if (!organizationId) {
throw new Error('Missing site content headers');
}
const siteContent: SiteContentPointer = {
siteId,
spaceId,
siteSpaceId: siteSpaceId ?? undefined,
siteShareKey: siteShareKey ?? undefined,
organizationId,
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
};
return siteContent;
}
/**
* Fetch all the data needed to render the space layout.
*/
export async function fetchSpaceData() {
const content = getContentPointer();
const content = getSiteContentPointer();
const siteShareKey = 'siteId' in content ? content.siteShareKey : undefined;
const siteShareKey = content.siteShareKey;
const [{ space, contentTarget, pages, customization, scripts }, parentSite] = await Promise.all(
'siteId' in content
@@ -108,21 +101,17 @@ export async function fetchSpaceData() {
* Optimized to fetch in parallel as much as possible.
*/
export async function fetchPageData(params: PagePathParams | PageIdParams) {
const content = getContentPointer();
const siteShareKey = 'siteId' in content ? content.siteShareKey : undefined;
const { space, contentTarget, pages, customization, scripts } = await ('siteId' in content
? getCurrentSiteData(content)
: getSpaceData(content, siteShareKey));
const content = getSiteContentPointer();
const { space, contentTarget, pages, customization, scripts } =
await getCurrentSiteData(content);
const page = await resolvePage(contentTarget, pages, params);
const [parent, document] = await Promise.all([
'siteId' in content
? fetchParentSite({
organizationId: content.organizationId,
siteId: content.siteId,
siteShareKey: content.siteShareKey,
})
: fetchParentCollection(space),
fetchParentSite({
organizationId: content.organizationId,
siteId: content.siteId,
siteShareKey: content.siteShareKey,
}),
page?.page.documentId ? getDocument(space.id, page.page.documentId) : null,
]);
@@ -181,17 +170,6 @@ async function resolvePage(
return undefined;
}
async function fetchParentCollection(space: Space) {
const parentCollectionId =
space.visibility === ContentVisibility.InCollection ? space.parent : undefined;
const [collection, spaces] = await Promise.all([
parentCollectionId ? getCollection(parentCollectionId) : null,
parentCollectionId ? getCollectionSpaces(parentCollectionId) : ([] as Space[]),
]);
return { parent: collection, spaces };
}
async function fetchParentSite(args: {
organizationId: string;
siteId: string;
+5 -6
View File
@@ -14,7 +14,7 @@ import colors from 'tailwindcss/colors';
import { emojiFontClassName } from '@/components/primitives';
import { fonts, ibmPlexMono } from '@/fonts';
import { getSpaceLanguage } from '@/intl/server';
import { getCurrentSiteLayoutData, getSpaceLayoutData } from '@/lib/api';
import { getCurrentSiteLayoutData } from '@/lib/api';
import { getStaticFileURL } from '@/lib/assets';
import { hexToRgb, shadesOfColor } from '@/lib/colors';
import { tcls } from '@/lib/tailwind';
@@ -22,7 +22,7 @@ import { tcls } from '@/lib/tailwind';
import { ClientContexts } from './ClientContexts';
import './globals.css';
import '@gitbook/icons/style.css';
import { getContentPointer } from './fetch';
import { getSiteContentPointer } from './fetch';
/**
* Layout shared between the content and the PDF renderer.
@@ -31,10 +31,9 @@ import { getContentPointer } from './fetch';
export default async function SpaceRootLayout(props: { children: React.ReactNode }) {
const { children } = props;
const pointer = getContentPointer();
const { customization } = await ('siteId' in pointer
? getCurrentSiteLayoutData(pointer)
: getSpaceLayoutData(pointer.spaceId));
const pointer = getSiteContentPointer();
const { customization } = await getCurrentSiteLayoutData(pointer);
const headerTheme = generateHeaderTheme(customization);
const language = getSpaceLanguage(customization);
@@ -2,7 +2,7 @@ import { Space } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import React from 'react';
import { ContentPointer, getChangeRequest, getRevision } from '@/lib/api';
import { SiteContentPointer, getChangeRequest, getRevision } from '@/lib/api';
import { tcls } from '@/lib/tailwind';
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
@@ -10,7 +10,7 @@ import { Toolbar, ToolbarBody, ToolbarButton, ToolbarButtonGroups } from './Tool
import { DateRelative } from '../primitives';
interface AdminToolbarProps {
content: ContentPointer;
content: SiteContentPointer;
space: Space;
}
@@ -20,15 +20,13 @@ import { SearchButton } from '../Search';
*/
export function CompactHeader(props: {
space: Space;
parent: Site | Collection | null;
parent: Site;
spaces: Space[];
customization: CustomizationSettings | SiteCustomizationSettings;
}) {
const { space, spaces, parent, customization } = props;
const isMultiVariants =
parent?.object === 'collection' ||
(parent && parent.object === 'site' && spaces.length > 1);
const isMultiVariants = parent.object === 'site' && spaces.length > 1;
return (
<div
@@ -24,7 +24,7 @@ import { SearchButton } from '../Search';
*/
export function Header(props: {
space: Space;
parent: Site | Collection | null;
parent: Site;
spaces: Space[];
context: ContentRefContext;
customization: CustomizationSettings | SiteCustomizationSettings;
@@ -34,9 +34,7 @@ export function Header(props: {
const isCustomizationDefault =
customization.header.preset === CustomizationHeaderPreset.Default;
const isMultiVariants =
parent?.object === 'collection' ||
(parent && parent.object === 'site' && spaces.length > 1);
const isMultiVariants = parent.object === 'site' && spaces.length > 1;
return (
<header
@@ -17,7 +17,7 @@ import { Link } from '../primitives';
import { SpaceIcon } from '../Space/SpaceIcon';
interface HeaderLogoProps {
parent: Site | Collection | null;
parent: Site;
space: Space;
customization: CustomizationSettings | SiteCustomizationSettings;
}
@@ -9,7 +9,7 @@ import React from 'react';
import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
import { ContentPointer, ContentTarget, SiteContentPointer, api } from '@/lib/api';
import { ContentTarget, SiteContentPointer, api } from '@/lib/api';
import { hasFullWidthBlock, isNodeEmpty } from '@/lib/document';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -27,7 +27,7 @@ import { DateRelative } from '../primitives';
export function PageBody(props: {
space: Space;
contentPointer: ContentPointer | SiteContentPointer;
contentPointer: SiteContentPointer;
contentTarget: ContentTarget;
customization: CustomizationSettings | SiteCustomizationSettings;
page: RevisionPageDocument;
@@ -50,10 +50,6 @@ export function PageBody(props: {
const language = getSpaceLanguage(customization);
const updatedAt = page.updatedAt ?? page.createdAt;
const shouldHighlightCode = createHighlightingContext();
const sitePointer =
'siteId' in contentPointer
? { organizationId: contentPointer.organizationId, siteId: contentPointer.siteId }
: undefined;
return (
<>
@@ -146,7 +142,7 @@ export function PageBody(props: {
</main>
{shouldTrackPageViews() ? (
<TrackPageView
sitePointer={sitePointer}
sitePointer={contentPointer}
spaceId={space.id}
pageId={page.id}
apiHost={api().endpoint}
@@ -12,7 +12,7 @@ import { SiteContentPointer } from '@/lib/api';
*/
export function TrackPageView(props: {
apiHost: string;
sitePointer?: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
sitePointer: SiteContentPointer;
spaceId: string;
pageId: string | undefined;
}) {
@@ -21,7 +21,8 @@ interface SearchModalProps {
spaceId: string;
revisionId: string;
spaceTitle: string;
parent: Site | Collection | null;
parent: Site;
withParent: boolean;
withAsk: boolean;
}
@@ -136,7 +137,7 @@ function SearchModalBody(
onClose: (to?: string) => void;
},
) {
const { spaceId, revisionId, spaceTitle, withAsk, parent, state, onChangeQuery, onClose } =
const { spaceId, revisionId, spaceTitle, withAsk, withParent, state, onChangeQuery, onClose } =
props;
const language = useLanguage();
@@ -239,7 +240,7 @@ function SearchModalBody(
ref={resultsRef}
spaceId={spaceId}
revisionId={revisionId}
parent={state.global ? parent : null}
withParentSearch={withParent && state.global ? true : false}
query={state.query}
withAsk={withAsk}
onSwitchToAsk={() => {
@@ -250,7 +251,9 @@ function SearchModalBody(
});
}}
>
{parent && state.query ? <SearchScopeToggle spaceTitle={spaceTitle} /> : null}
{withParent && state.query ? (
<SearchScopeToggle spaceTitle={spaceTitle} />
) : null}
</SearchResults>
) : null}
{state.query && state.ask && withAsk ? (
@@ -12,7 +12,7 @@ import { SearchSectionResultItem } from './SearchSectionResultItem';
import {
getRecommendedQuestions,
OrderedComputedResult,
searchParentContent,
searchSiteContent,
searchSpaceContent,
} from './server-actions';
import { Loading } from '../primitives';
@@ -40,13 +40,17 @@ export const SearchResults = React.forwardRef(function SearchResults(
query: string;
spaceId: string;
revisionId: string;
parent: Site | Collection | null;
/**
* Whether to search in the parent site
*/
withParentSearch: boolean;
withAsk: boolean;
onSwitchToAsk: () => void;
},
ref: React.Ref<SearchResultsRef>,
) {
const { children, query, spaceId, revisionId, parent, withAsk, onSwitchToAsk } = props;
const { children, query, spaceId, revisionId, withParentSearch, withAsk, onSwitchToAsk } =
props;
const language = useLanguage();
const debounceTimeout = React.useRef<Timer | null>(null);
@@ -93,8 +97,8 @@ export const SearchResults = React.forwardRef(function SearchResults(
debounceTimeout.current = setTimeout(async () => {
setCursor(null);
const fetchedResults = await (parent
? searchParentContent(parent, query)
const fetchedResults = await (withParentSearch
? searchSiteContent({ query })
: searchSpaceContent(spaceId, revisionId, query));
setResults(withAsk ? withQuestionResult(fetchedResults, query) : fetchedResults);
@@ -10,7 +10,7 @@ import {
Space,
} from '@gitbook/api';
import { getContentPointer } from '@/app/(space)/fetch';
import { getSiteContentPointer } from '@/app/(space)/fetch';
import { streamResponse } from '@/lib/actions';
import * as api from '@/lib/api';
import { absoluteHref, pageHref } from '@/lib/links';
@@ -58,7 +58,7 @@ export async function searchSiteContent(args: {
cacheBust?: string;
}): Promise<OrderedComputedResult[]> {
const { siteSpaceIds, query, cacheBust } = args;
const pointer = getContentPointer();
const pointer = getSiteContentPointer();
if (query.length <= 1) {
return [];
@@ -69,46 +69,41 @@ export async function searchSiteContent(args: {
return [];
}
if ('siteId' in pointer && 'organizationId' in pointer) {
const [searchResults, allSiteSpaces] = await Promise.all([
api.searchSiteContent(
pointer.organizationId,
pointer.siteId,
query,
siteSpaceIds,
cacheBust,
),
siteSpaceIds
? null
: api.getSiteSpaces({
organizationId: pointer.organizationId,
siteId: pointer.siteId,
siteShareKey: pointer.siteShareKey,
}),
]);
if (!siteSpaceIds) {
// We are searching all of this Site's content
return searchResults.items
.map((spaceItem) => {
const siteSpace = allSiteSpaces?.find(
(siteSpace) => siteSpace.space.id === spaceItem.id,
);
return spaceItem.pages.map((item) => transformSitePageResult(item, siteSpace));
})
.flat(2);
}
const [searchResults, allSiteSpaces] = await Promise.all([
api.searchSiteContent(
pointer.organizationId,
pointer.siteId,
query,
siteSpaceIds,
cacheBust,
),
siteSpaceIds
? null
: api.getSiteSpaces({
organizationId: pointer.organizationId,
siteId: pointer.siteId,
siteShareKey: pointer.siteShareKey,
}),
]);
if (!siteSpaceIds) {
// We are searching all of this Site's content
return searchResults.items
.map((spaceItem) => {
return spaceItem.pages.map((item) => transformPageResult(item));
const siteSpace = allSiteSpaces?.find(
(siteSpace) => siteSpace.space.id === spaceItem.id,
);
return spaceItem.pages.map((item) => transformSitePageResult(item, siteSpace));
})
.flat(2);
}
// This should never happen
return [];
return searchResults.items
.map((spaceItem) => {
return spaceItem.pages.map((item) => transformPageResult(item));
})
.flat(2);
}
/**
@@ -119,47 +114,13 @@ export async function searchSpaceContent(
revisionId: string,
query: string,
): Promise<OrderedComputedResult[]> {
const pointer = getContentPointer();
const pointer = getSiteContentPointer();
if ('siteId' in pointer && 'organizationId' in pointer) {
const siteSpaceIds = pointer.siteSpaceId ? [pointer.siteSpaceId] : []; // if we don't have a siteSpaceID search all content
const siteSpaceIds = pointer.siteSpaceId ? [pointer.siteSpaceId] : []; // if we don't have a siteSpaceID search all content
// This is a site so use a different function which we can eventually call directly
// We also want to break cache for this specific space if the revisionId is different so use it as a cache busting key
return await searchSiteContent({ siteSpaceIds, query, cacheBust: revisionId });
}
const data = await api.searchSpaceContent(spaceId, revisionId, query);
return data.items.map((item) => transformPageResult(item, undefined)).flat();
}
/**
* Server action to search content in a parent (site or collection)
*/
export async function searchParentContent(
parent: Site | Collection,
query: string,
): Promise<OrderedComputedResult[]> {
const pointer = getContentPointer();
const isSite = 'siteId' in pointer;
if (isSite) {
return searchSiteContent({ query });
}
const [data, collectionSpaces] = await Promise.all([
api.searchParentContent(parent.id, query),
parent.object === 'collection' ? api.getCollectionSpaces(parent.id) : null,
]);
let spaces: Space[] = collectionSpaces ? collectionSpaces : [];
return data.items
.map((spaceItem) => {
const space = spaces.find((space) => space.id === spaceItem.id);
return spaceItem.pages.map((item) => transformPageResult(item, space));
})
.flat(2);
// This is a site so use a different function which we can eventually call directly
// We also want to break cache for this specific space if the revisionId is different so use it as a cache busting key
return await searchSiteContent({ siteSpaceIds, query, cacheBust: revisionId });
}
/**
@@ -169,7 +130,7 @@ export const streamAskQuestion = streamResponse(async function* (spaceId: string
const stream = api
.api()
.spaces.streamAskInSpace(spaceId, { query, format: 'document', details: true });
const pagesPromise = api.getSpaceContentData({ spaceId }, undefined);
const pagesPromise = api.getSpaceContentData({ spaceId, siteShareKey: undefined });
for await (const chunk of stream) {
// We run the AI search and fetch the pages in parallel
@@ -17,7 +17,7 @@ import { CONTAINER_STYLE } from '@/components/layout';
import { ColorDebugger } from '@/components/primitives/ColorDebugger';
import { SearchModal } from '@/components/Search';
import { TableOfContents } from '@/components/TableOfContents';
import { ContentPointer, ContentTarget, SiteContentPointer } from '@/lib/api';
import { ContentTarget, SiteContentPointer } from '@/lib/api';
import { ContentRefContext } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -25,10 +25,10 @@ import { tcls } from '@/lib/tailwind';
* Render the entire content of the space (header, table of contents, footer, and page content).
*/
export function SpaceLayout(props: {
content: ContentPointer | SiteContentPointer;
content: SiteContentPointer;
contentTarget: ContentTarget;
space: Space;
parent: Site | Collection | null;
parent: Site;
spaces: Space[];
customization: CustomizationSettings | SiteCustomizationSettings;
pages: Revision['pages'];
@@ -50,7 +50,7 @@ export function SpaceLayout(props: {
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
const contentRefContext: ContentRefContext = {
siteContext: 'siteId' in content ? content : null,
siteContext: content,
space,
revisionId: contentTarget.revisionId,
pages,
@@ -113,11 +113,12 @@ export function SpaceLayout(props: {
<React.Suspense fallback={null}>
<SearchModal
parent={parent}
spaceId={contentTarget.spaceId}
revisionId={contentTarget.revisionId}
spaceTitle={customization.title ?? space.title}
withAsk={customization.aiSearch.enabled}
parent={parent && spaces.length > 1 ? parent : null}
withParent={parent && spaces.length > 1 ? true : false}
/>
</React.Suspense>
</>
@@ -8,7 +8,7 @@ import {
} from '@gitbook/api';
import React from 'react';
import { ContentPointer } from '@/lib/api';
import { SiteContentPointer } from '@/lib/api';
import { ContentRefContext } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -19,7 +19,7 @@ import { Trademark } from './Trademark';
export function TableOfContents(props: {
space: Space;
customization: CustomizationSettings | SiteCustomizationSettings;
content: ContentPointer;
content: SiteContentPointer;
context: ContentRefContext;
pages: Revision['pages'];
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
@@ -27,4 +27,5 @@ export const languages: {
nl,
no,
'pt-br': pt_br,
nl: en,
};
+15 -48
View File
@@ -8,7 +8,6 @@ import {
GitBookAPIError,
HttpResponse,
List,
PublishedContentLookup,
PublishedSiteContentLookup,
RequestRenderIntegrationUI,
RevisionFile,
@@ -32,16 +31,10 @@ import {
/**
* Pointer to a relative content, it might change overtime, the pointer is relative in the content history.
*/
export interface ContentPointer {
spaceId: string;
export interface SiteContentPointer {
changeRequestId?: string;
revisionId?: string;
}
/**
* Pointer to a relative content, it might change overtime, the pointer is relative in the content history.
*/
export interface SiteContentPointer extends ContentPointer {
spaceId: string;
organizationId: string;
siteId: string;
/**
@@ -768,7 +761,7 @@ export const getSiteIntegrationScripts = cache({
*/
export async function getCurrentSiteData(pointer: SiteContentPointer) {
const [{ space, pages, contentTarget }, { customization, scripts }] = await Promise.all([
getSpaceData(pointer, pointer.siteShareKey),
getSpaceContentData(pointer),
getCurrentSiteLayoutData(pointer),
]);
@@ -910,38 +903,27 @@ export const getCollectionSpaces = cache({
},
});
/**
* Fetch all the data to render a space at once.
*/
export async function getSpaceData(pointer: ContentPointer, shareKey: string | undefined) {
const [{ space, pages, contentTarget }, { customization, scripts }] = await Promise.all([
getSpaceContentData(pointer, shareKey),
getSpaceLayoutData(pointer.spaceId),
]);
return {
space,
pages,
contentTarget,
customization,
scripts,
};
}
/**
* Fetch all the content data about a space at once.
* This function executes the requests in parallel and should be used as early as possible
* instead of calling the individual functions.
*/
export async function getSpaceContentData(pointer: ContentPointer, shareKey: string | undefined) {
export async function getSpaceContentData(
contentPointer: Pick<
SiteContentPointer,
'spaceId' | 'revisionId' | 'changeRequestId' | 'siteShareKey'
>,
) {
const [space, changeRequest] = await Promise.all([
getSpace(pointer.spaceId, shareKey),
pointer.changeRequestId ? getChangeRequest(pointer.spaceId, pointer.changeRequestId) : null,
getSpace(contentPointer.spaceId, contentPointer.siteShareKey),
contentPointer.changeRequestId
? getChangeRequest(contentPointer.spaceId, contentPointer.changeRequestId)
: null,
]);
const contentTarget: ContentTarget = {
spaceId: pointer.spaceId,
revisionId: changeRequest?.revision ?? pointer.revisionId ?? space.revision,
spaceId: contentPointer.spaceId,
revisionId: changeRequest?.revision ?? contentPointer.revisionId ?? space.revision,
};
const [pages] = await Promise.all([
getRevisionPages(space.id, contentTarget.revisionId, {
@@ -958,21 +940,6 @@ export async function getSpaceContentData(pointer: ContentPointer, shareKey: str
};
}
/**
* Fetch all the layout data about a space at once.
*/
export async function getSpaceLayoutData(spaceId: string) {
const [customization, scripts] = await Promise.all([
getSpaceCustomization(spaceId),
getSpaceIntegrationScripts(spaceId),
]);
return {
customization,
scripts,
};
}
/**
* Search content in a space.
*/
+5 -12
View File
@@ -5,7 +5,6 @@ import React from 'react';
import { PageIcon } from '@/components/PageIcon';
import {
ContentPointer,
SiteContentPointer,
getCollection,
getDocument,
@@ -45,9 +44,8 @@ export interface ContentRefContext extends PageHrefContext {
/**
* Site in which we are resolving the content reference.
* If null, the site is not known (legacy published content mode)
*/
siteContext: SiteContentPointer | null;
siteContext: SiteContentPointer;
/**
* Space in which we are resolving the content reference.
*/
@@ -119,7 +117,7 @@ export async function resolveContentRef(
case 'anchor':
case 'page': {
if (contentRef.space && contentRef.space !== space.id) {
return resolveContentRefInSpace(contentRef.space, siteContext, contentRef);
return resolveContentRefInSpace(siteContext, contentRef);
}
const resolvePageResult =
@@ -265,15 +263,10 @@ export async function resolveContentRef(
}
async function resolveContentRefInSpace(
spaceId: string,
siteContext: SiteContentPointer | null,
siteContextPointer: SiteContentPointer,
contentRef: ContentRef,
) {
const pointer: ContentPointer = {
spaceId,
};
const result = await ignoreAPIError(getSpaceContentData(pointer, siteContext?.siteShareKey));
const result = await ignoreAPIError(getSpaceContentData(siteContextPointer));
if (!result) {
return null;
}
@@ -287,7 +280,7 @@ async function resolveContentRefInSpace(
}
const resolved = await resolveContentRef(contentRef, {
siteContext,
siteContext: siteContextPointer,
space,
revisionId: space.revision,
pages,
+3 -12
View File
@@ -55,18 +55,9 @@ export function isSpaceIndexable({
return false;
}
if (parent && parent.object === 'site') {
return shouldIndexVisibility(parent.visibility);
}
if (space.visibility === ContentVisibility.InCollection) {
return parent && parent.object === 'collection'
? shouldIndexVisibility(parent.visibility)
: false;
}
return shouldIndexVisibility(space.visibility);
return shouldIndexVisibility(parent.visibility);
}
function shouldIndexVisibility(visibility: ContentVisibility | SiteVisibility) {
return visibility === ContentVisibility.Public;
function shouldIndexVisibility(visibility: SiteVisibility) {
return visibility === SiteVisibility.Public;
}
+18 -28
View File
@@ -13,7 +13,6 @@ import {
getSpaceContentData,
userAgent,
withAPI,
getSpaceLayoutData,
DEFAULT_API_ENDPOINT,
getCurrentSiteLayoutData,
} from '@/lib/api';
@@ -78,9 +77,8 @@ export type LookupResult = PublishedContentWithCache & {
interface ContentAPITokenPayload {
organization: string;
spaces: string[];
collection?: string;
site?: string;
siteSpace?: string;
site: string;
siteSpace: string;
}
/**
@@ -172,23 +170,20 @@ export async function middleware(request: NextRequest) {
// Start fetching everything as soon as possible, but do not block the middleware on it
// the cache will handle concurrent calls
await waitUntil(
getSpaceContentData(
{
spaceId: resolved.space,
changeRequestId: resolved.changeRequest,
revisionId: resolved.revision,
},
'site' in resolved ? resolved.shareKey : undefined,
),
getSpaceContentData({
spaceId: resolved.space,
changeRequestId: resolved.changeRequest,
revisionId: resolved.revision,
siteShareKey: resolved.shareKey,
}),
);
const { scripts } = await ('site' in resolved
? getCurrentSiteLayoutData({
organizationId: resolved.organization,
siteId: resolved.site,
siteSpaceId: resolved.siteSpace,
})
: getSpaceLayoutData(resolved.space));
const { scripts } = await getCurrentSiteLayoutData({
organizationId: resolved.organization,
siteId: resolved.site,
siteSpaceId: resolved.siteSpace,
});
return getContentSecurityPolicy(scripts, nonce);
},
);
@@ -503,19 +498,14 @@ async function lookupSpaceInMultiIdMode(request: NextRequest, url: URL): Promise
}
const { organization, site, siteSpace } = jwt.decode(apiToken) as ContentAPITokenPayload;
const siteLookupResult =
typeof organization === 'string' && organization && typeof site === 'string' && site
? {
organization,
site,
...(typeof siteSpace === 'string' && siteSpace ? { siteSpace } : {}),
}
: {};
return {
space: spaceId,
changeRequest: changeRequestId,
revision: revisionId,
...siteLookupResult,
organization,
site,
siteSpace,
basePath: normalizePathname(basePathParts.join('/')),
pathname: normalizePathname(pathSegments.join('/')),
apiToken,
+1 -1
View File
@@ -10,7 +10,7 @@
},
"dependencies": {
"classnames": "^2.5.1",
"@gitbook/api": "0.60.0",
"@gitbook/api": "0.64.1",
"assert-never": "^1.2.1"
},
"peerDependencies": {