mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-13 14:19:25 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6b9fe6643 | |||
| 766c97a100 | |||
| 29fca1e835 | |||
| 48b5c0707b | |||
| 434cfd7a08 | |||
| e3587d59aa |
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user