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