mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-19 17:15:24 +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"
|
"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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user