mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
Use newer APIs to optimize data loading and fix resolution of links in the TOC (#11)
* Update api@0.16.0 * Fetch pages and files independantly * Correctly resolve content ref in TOC * Format * Format * Cleanup
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@geist-ui/icons": "^1.0.2",
|
||||
"@gitbook/api": "^0.15.0",
|
||||
"@gitbook/api": "^0.16.0",
|
||||
"@readme/openapi-parser": "^2.5.0",
|
||||
"@upstash/redis": "^1.25.1",
|
||||
"ajv": "^8.12.0",
|
||||
|
||||
@@ -5,10 +5,11 @@ import shadesOf from 'tailwind-shades';
|
||||
import colors from 'tailwindcss/colors';
|
||||
|
||||
import { hexToRgb } from '@/components/utils/HexToRgb';
|
||||
import { getSpaceCustomization } from '@/lib/api';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { ClientLayout } from './ClientLayout';
|
||||
import { PagePathParams, fetchPageData } from '../fetch';
|
||||
import { PagePathParams } from '../fetch';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
@@ -16,8 +17,9 @@ export default async function SpaceRootLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: PagePathParams;
|
||||
}) {
|
||||
const { children, params } = props;
|
||||
const { customization } = await fetchPageData(params);
|
||||
const { params, children } = props;
|
||||
|
||||
const customization = await getSpaceCustomization(params.spaceId);
|
||||
const headerTheme = generateHeaderTheme(customization);
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,7 @@ export const runtime = 'nodejs';
|
||||
export default async function Page(props: { params: PagePathParams }) {
|
||||
const { params } = props;
|
||||
|
||||
const { space, customization, revision, page, collection, collectionSpaces, ancestors } =
|
||||
const { content, space, customization, pages, page, collection, collectionSpaces, ancestors } =
|
||||
await fetchPageData(params);
|
||||
const linksContext: PageHrefContext = {};
|
||||
|
||||
@@ -27,13 +27,14 @@ export default async function Page(props: { params: PagePathParams }) {
|
||||
redirect(pageHref(page, linksContext));
|
||||
}
|
||||
|
||||
const document = await getPageDocument(space.id, revision.id, page.id);
|
||||
const document = await getPageDocument(content, page.id);
|
||||
|
||||
return (
|
||||
<SpaceContent
|
||||
content={content}
|
||||
space={space}
|
||||
customization={customization}
|
||||
revision={revision}
|
||||
pages={pages}
|
||||
page={page}
|
||||
ancestors={ancestors}
|
||||
document={document}
|
||||
|
||||
@@ -2,16 +2,15 @@ import { ContentVisibility, Space } from '@gitbook/api';
|
||||
|
||||
import {
|
||||
getSpace,
|
||||
getCurrentRevision,
|
||||
getSpaceCustomization,
|
||||
getCollectionSpaces,
|
||||
getCollection,
|
||||
ContentPointer,
|
||||
getRevisionPages,
|
||||
} from '@/lib/api';
|
||||
import { resolvePagePath, resolvePageId } from '@/lib/pages';
|
||||
|
||||
export interface SpaceParams {
|
||||
spaceId: string;
|
||||
}
|
||||
export type SpaceParams = ContentPointer;
|
||||
|
||||
export interface PagePathParams extends SpaceParams {
|
||||
pathname?: string[];
|
||||
@@ -27,21 +26,28 @@ export interface PageIdParams extends SpaceParams {
|
||||
export async function fetchPageData(params: PagePathParams | PageIdParams) {
|
||||
const { spaceId } = params;
|
||||
|
||||
const [space, revision, customization] = await Promise.all([
|
||||
const content: ContentPointer = {
|
||||
spaceId: params.spaceId,
|
||||
changeRequestId: params.changeRequestId,
|
||||
revisionId: params.revisionId,
|
||||
};
|
||||
|
||||
const [space, pages, customization] = await Promise.all([
|
||||
getSpace(spaceId),
|
||||
getCurrentRevision(spaceId),
|
||||
getRevisionPages(content),
|
||||
getSpaceCustomization(spaceId),
|
||||
]);
|
||||
|
||||
const collection = await fetchParentCollection(space);
|
||||
const page =
|
||||
'pageId' in params && params.pageId
|
||||
? resolvePageId(revision, params.pageId)
|
||||
: resolvePagePath(revision, getPagePath(params));
|
||||
? resolvePageId(pages, params.pageId)
|
||||
: resolvePagePath(pages, getPagePath(params));
|
||||
|
||||
return {
|
||||
content,
|
||||
space,
|
||||
revision,
|
||||
pages,
|
||||
customization,
|
||||
ancestors: [],
|
||||
...page,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Revision, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
import { RevisionPage, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
import jsontoxml from 'jsontoxml';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
import { api } from '@/lib/api';
|
||||
import { getRevisionPages } from '@/lib/api';
|
||||
import { pageHref } from '@/lib/links';
|
||||
|
||||
import { SpaceParams } from '../fetch';
|
||||
@@ -15,11 +15,8 @@ export const runtime = 'nodejs';
|
||||
* Generate a sitemap.xml for the current space.
|
||||
*/
|
||||
export async function GET(req: NextRequest, { params }: { params: SpaceParams }) {
|
||||
const { spaceId } = params;
|
||||
|
||||
const { data: revision } = await api().spaces.getCurrentRevision(spaceId);
|
||||
|
||||
const pages = flattenPages(revision);
|
||||
const rootPages = await getRevisionPages(params);
|
||||
const pages = flattenPages(rootPages);
|
||||
const urls = pages.map(({ page, depth }) => {
|
||||
// Decay priority with depth
|
||||
const priority = Math.pow(2, -0.25 * depth);
|
||||
@@ -70,7 +67,7 @@ export async function GET(req: NextRequest, { params }: { params: SpaceParams })
|
||||
|
||||
type FlatPageEntry = { page: RevisionPageDocument; depth: number };
|
||||
|
||||
function flattenPages(revision: Revision): FlatPageEntry[] {
|
||||
function flattenPages(rootPags: RevisionPage[]): FlatPageEntry[] {
|
||||
const flattenPage = (
|
||||
page: RevisionPageDocument | RevisionPageGroup,
|
||||
depth: number,
|
||||
@@ -83,5 +80,5 @@ function flattenPages(revision: Revision): FlatPageEntry[] {
|
||||
];
|
||||
};
|
||||
|
||||
return revision.pages.flatMap((page) => (page.type === 'link' ? [] : flattenPage(page, 0)));
|
||||
return rootPags.flatMap((page) => (page.type === 'link' ? [] : flattenPage(page, 0)));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import IconDownload from '@geist-ui/icons/download';
|
||||
import { DocumentBlockFile } from '@gitbook/api';
|
||||
|
||||
import { getRevisionFile } from '@/lib/api';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from './Block';
|
||||
|
||||
export function File(props: BlockProps<DocumentBlockFile>) {
|
||||
export async function File(props: BlockProps<DocumentBlockFile>) {
|
||||
const { block, context, style } = props;
|
||||
|
||||
const file = context.revision.files.find((f) => f.id === block.data.ref.file);
|
||||
const file = await getRevisionFile(context.content, block.data.ref.file);
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CustomizationSettings, Revision, RevisionPageDocument, Space } from '@g
|
||||
import React from 'react';
|
||||
|
||||
import { Image } from '@/components/utils';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { FooterLinksGroup } from './FooterLinksGroup';
|
||||
@@ -10,12 +11,11 @@ import { ThemeToggler } from '../ThemeToggler';
|
||||
|
||||
export function Footer(props: {
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
page: RevisionPageDocument;
|
||||
context: ContentRefContext;
|
||||
customization: CustomizationSettings;
|
||||
asFullWidth: boolean;
|
||||
}) {
|
||||
const { space, revision, page, customization, asFullWidth } = props;
|
||||
const { space, context, customization, asFullWidth } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -53,11 +53,7 @@ export function Footer(props: {
|
||||
) : null}
|
||||
|
||||
{customization.footer.groups.map((group, index) => (
|
||||
<FooterLinksGroup
|
||||
key={index}
|
||||
group={group}
|
||||
context={{ space, revision, page }}
|
||||
/>
|
||||
<FooterLinksGroup key={index} group={group} context={context} />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import {
|
||||
Collection,
|
||||
CustomizationSettings,
|
||||
Revision,
|
||||
RevisionPageDocument,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import { Collection, CustomizationSettings, Space } from '@gitbook/api';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { CONTAINER_MAX_WIDTH_NORMAL, CONTAINER_PADDING } from '@/components/layout';
|
||||
import { t } from '@/lib/intl';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { CollectionSpacesDropdown } from './CollectionSpacesDropdown';
|
||||
@@ -23,13 +18,11 @@ export function Header(props: {
|
||||
space: Space;
|
||||
collection: Collection | null;
|
||||
collectionSpaces: Space[];
|
||||
revision: Revision;
|
||||
page: RevisionPageDocument;
|
||||
context: ContentRefContext;
|
||||
asFullWidth: boolean;
|
||||
customization: CustomizationSettings;
|
||||
}) {
|
||||
const { space, collection, collectionSpaces, revision, page, asFullWidth, customization } =
|
||||
props;
|
||||
const { context, space, collection, collectionSpaces, asFullWidth, customization } = props;
|
||||
|
||||
return (
|
||||
<header
|
||||
@@ -93,13 +86,7 @@ export function Header(props: {
|
||||
)}
|
||||
>
|
||||
{customization.header.links.map((link, index) => (
|
||||
<HeaderLink
|
||||
key={index}
|
||||
link={link}
|
||||
space={space}
|
||||
revision={revision}
|
||||
page={page}
|
||||
/>
|
||||
<HeaderLink key={index} link={link} context={context} />
|
||||
))}
|
||||
</div>
|
||||
<div className={tcls('flex', 'basis-56', 'grow-0', 'shrink-0')}>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import {
|
||||
CustomizationContentLink,
|
||||
CustomizationHeaderLink,
|
||||
Revision,
|
||||
RevisionPageDocument,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import { CustomizationContentLink, CustomizationHeaderLink } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { ContentRefContext, resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import {
|
||||
@@ -19,18 +13,12 @@ import {
|
||||
} from './Dropdown';
|
||||
|
||||
export async function HeaderLink(props: {
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
page: RevisionPageDocument;
|
||||
context: ContentRefContext;
|
||||
link: CustomizationHeaderLink;
|
||||
}) {
|
||||
const { space, revision, page, link } = props;
|
||||
const { context, link } = props;
|
||||
|
||||
const target = await resolveContentRef(link.to, {
|
||||
space,
|
||||
revision,
|
||||
page,
|
||||
});
|
||||
const target = await resolveContentRef(link.to, context);
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
@@ -73,18 +61,12 @@ export async function HeaderLink(props: {
|
||||
}
|
||||
|
||||
async function SubHeaderLink(props: {
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
page: RevisionPageDocument;
|
||||
context: ContentRefContext;
|
||||
link: CustomizationContentLink;
|
||||
}) {
|
||||
const { space, revision, page, link } = props;
|
||||
const { context, link } = props;
|
||||
|
||||
const target = await resolveContentRef(link.to, {
|
||||
space,
|
||||
revision,
|
||||
page,
|
||||
});
|
||||
const target = await resolveContentRef(link.to, context);
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Revision, RevisionPageDocument, Space } from '@gitbook/api';
|
||||
import { JSONDocument, Revision, RevisionPageDocument, Space } from '@gitbook/api';
|
||||
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PageFooterNavigation } from './PageFooterNavigation';
|
||||
@@ -8,25 +9,17 @@ import { DocumentView } from '../DocumentView';
|
||||
|
||||
export function PageBody(props: {
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
page: RevisionPageDocument;
|
||||
document: any;
|
||||
context: ContentRefContext;
|
||||
document: JSONDocument;
|
||||
}) {
|
||||
const { space, revision, page, document } = props;
|
||||
const { space, context, page, document } = props;
|
||||
|
||||
return (
|
||||
<main className={tcls('py-8', 'px-4', 'lg:px-12', 'flex-1')}>
|
||||
<PageHeader page={page} />
|
||||
<DocumentView
|
||||
document={document}
|
||||
style={['space-y-6']}
|
||||
context={{
|
||||
space,
|
||||
revision,
|
||||
page,
|
||||
}}
|
||||
/>
|
||||
<PageFooterNavigation space={space} revision={revision} page={page} />
|
||||
<DocumentView document={document} style={['space-y-6']} context={context} />
|
||||
<PageFooterNavigation space={space} pages={context.pages} page={page} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import { tcls } from '@/lib/tailwind';
|
||||
*/
|
||||
export function PageFooterNavigation(props: {
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
pages: Revision['pages'];
|
||||
page: RevisionPageDocument;
|
||||
}) {
|
||||
const { space, revision, page } = props;
|
||||
const { previous, next } = resolvePrevNextPages(revision, page);
|
||||
const { space, pages, page } = props;
|
||||
const { previous, next } = resolvePrevNextPages(pages, page);
|
||||
|
||||
return (
|
||||
<div className={tcls('flex', 'flex-row', 'mt-6', 'gap-2', 'max-w-3xl', 'mx-auto')}>
|
||||
|
||||
@@ -15,8 +15,10 @@ import { CONTAINER_MAX_WIDTH_NORMAL, CONTAINER_PADDING } from '@/components/layo
|
||||
import { PageBody } from '@/components/PageBody';
|
||||
import { SearchModal } from '@/components/Search';
|
||||
import { TableOfContents } from '@/components/TableOfContents';
|
||||
import { ContentPointer } from '@/lib/api';
|
||||
import { hasFullWidthBlock } from '@/lib/document';
|
||||
import { tString } from '@/lib/intl';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PageAside } from '../PageAside';
|
||||
@@ -25,11 +27,12 @@ import { PageAside } from '../PageAside';
|
||||
* Render the entire content of the space (header, table of contents, footer, and page content).
|
||||
*/
|
||||
export function SpaceContent(props: {
|
||||
content: ContentPointer;
|
||||
space: Space;
|
||||
collection: Collection | null;
|
||||
collectionSpaces: Space[];
|
||||
customization: CustomizationSettings;
|
||||
revision: Revision;
|
||||
pages: Revision['pages'];
|
||||
page: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
document: any;
|
||||
@@ -38,7 +41,8 @@ export function SpaceContent(props: {
|
||||
space,
|
||||
collection,
|
||||
collectionSpaces,
|
||||
revision,
|
||||
content,
|
||||
pages,
|
||||
customization,
|
||||
page,
|
||||
ancestors,
|
||||
@@ -48,6 +52,13 @@ export function SpaceContent(props: {
|
||||
const asFullWidth = hasFullWidthBlock(document);
|
||||
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
|
||||
|
||||
const contentRefContext: ContentRefContext = {
|
||||
space,
|
||||
pages,
|
||||
page,
|
||||
content,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{withTopHeader ? (
|
||||
@@ -55,8 +66,7 @@ export function SpaceContent(props: {
|
||||
space={space}
|
||||
collection={collection}
|
||||
collectionSpaces={collectionSpaces}
|
||||
revision={revision}
|
||||
page={page}
|
||||
context={contentRefContext}
|
||||
customization={customization}
|
||||
asFullWidth={asFullWidth}
|
||||
/>
|
||||
@@ -72,9 +82,11 @@ export function SpaceContent(props: {
|
||||
>
|
||||
<TableOfContents
|
||||
space={space}
|
||||
revision={revision}
|
||||
content={content}
|
||||
pages={pages}
|
||||
activePage={page}
|
||||
ancestors={ancestors}
|
||||
context={contentRefContext}
|
||||
header={
|
||||
withTopHeader ? null : (
|
||||
<CompactHeader
|
||||
@@ -87,7 +99,12 @@ export function SpaceContent(props: {
|
||||
}
|
||||
withHeaderOffset={withTopHeader}
|
||||
/>
|
||||
<PageBody space={space} revision={revision} page={page} document={document} />
|
||||
<PageBody
|
||||
space={space}
|
||||
context={contentRefContext}
|
||||
page={page}
|
||||
document={document}
|
||||
/>
|
||||
<PageAside
|
||||
space={space}
|
||||
page={page}
|
||||
@@ -102,8 +119,7 @@ export function SpaceContent(props: {
|
||||
customization.footer.groups?.length ? (
|
||||
<Footer
|
||||
space={space}
|
||||
revision={revision}
|
||||
page={page}
|
||||
context={contentRefContext}
|
||||
customization={customization}
|
||||
asFullWidth={asFullWidth}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { pageHref } from '@/lib/links';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PagesList } from './PagesList';
|
||||
@@ -11,8 +12,9 @@ export function PageDocumentItem(props: {
|
||||
page: RevisionPageDocument;
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
context: ContentRefContext;
|
||||
}) {
|
||||
const { page, activePage, ancestors } = props;
|
||||
const { page, activePage, ancestors, context } = props;
|
||||
|
||||
const hasActiveDescendant = ancestors.some((ancestor) => ancestor.id === page.id);
|
||||
|
||||
@@ -64,6 +66,7 @@ export function PageDocumentItem(props: {
|
||||
)}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
context={context}
|
||||
/>
|
||||
}
|
||||
defaultOpen={hasActiveDescendant || activePage.id === page.id}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PagesList } from './PagesList';
|
||||
@@ -8,14 +9,20 @@ export function PageGroupItem(props: {
|
||||
page: RevisionPageGroup;
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
context: ContentRefContext;
|
||||
}) {
|
||||
const { page, activePage, ancestors } = props;
|
||||
const { page, activePage, ancestors, context } = props;
|
||||
|
||||
return (
|
||||
<li className={tcls('flex', 'flex-col', 'my-3')}>
|
||||
<div className={tcls('px-2', 'py-2', 'text', 'font-medium')}>{page.title}</div>
|
||||
{page.pages && page.pages.length ? (
|
||||
<PagesList pages={page.pages} activePage={activePage} ancestors={ancestors} />
|
||||
<PagesList
|
||||
pages={page.pages}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
context={context}
|
||||
/>
|
||||
) : null}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { RevisionPageLink } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ContentRefContext, resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export function PageLinkItem(props: { page: RevisionPageLink }) {
|
||||
const { page } = props;
|
||||
export async function PageLinkItem(props: { page: RevisionPageLink; context: ContentRefContext }) {
|
||||
const { page, context } = props;
|
||||
|
||||
const resolved = await resolveContentRef(page.target, context);
|
||||
|
||||
return (
|
||||
<li className={tcls('flex', 'flex-col', 'mb-0.5')}>
|
||||
<Link
|
||||
href={
|
||||
page.href ?? '#todo' // TODO: Will be fixed soon as the `target`
|
||||
}
|
||||
href={resolved?.href ?? '#'}
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { RevisionPage, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PageDocumentItem } from './PageDocumentItem';
|
||||
@@ -10,9 +11,10 @@ export function PagesList(props: {
|
||||
pages: RevisionPage[];
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
context: ContentRefContext;
|
||||
style?: ClassValue;
|
||||
}) {
|
||||
const { pages, activePage, ancestors, style } = props;
|
||||
const { pages, activePage, ancestors, context, style } = props;
|
||||
|
||||
return (
|
||||
<ul className={tcls('flex', 'flex-col', style)}>
|
||||
@@ -24,10 +26,11 @@ export function PagesList(props: {
|
||||
page={page}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
} else if (page.type === 'link') {
|
||||
return <PageLinkItem key={page.id} page={page} />;
|
||||
return <PageLinkItem key={page.id} page={page} context={context} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -36,6 +39,7 @@ export function PagesList(props: {
|
||||
page={page}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Revision, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
||||
import React from 'react';
|
||||
|
||||
import { ContentPointer } from '@/lib/api';
|
||||
import { IntlContext } from '@/lib/intl';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PagesList } from './PagesList';
|
||||
@@ -10,14 +12,24 @@ import { SIDE_COLUMN_WITHOUT_HEADER, SIDE_COLUMN_WITH_HEADER } from '../layout';
|
||||
|
||||
export function TableOfContents(
|
||||
props: IntlContext & {
|
||||
revision: Revision;
|
||||
content: ContentPointer;
|
||||
context: ContentRefContext;
|
||||
pages: Revision['pages'];
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
header?: React.ReactNode;
|
||||
withHeaderOffset?: boolean;
|
||||
},
|
||||
) {
|
||||
const { space, revision, activePage, ancestors, header, withHeaderOffset = false } = props;
|
||||
const {
|
||||
space,
|
||||
pages,
|
||||
activePage,
|
||||
ancestors,
|
||||
header,
|
||||
context,
|
||||
withHeaderOffset = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<aside
|
||||
@@ -42,7 +54,12 @@ export function TableOfContents(
|
||||
'pr-4',
|
||||
)}
|
||||
>
|
||||
<PagesList pages={revision.pages} activePage={activePage} ancestors={ancestors} />
|
||||
<PagesList
|
||||
pages={pages}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
context={context}
|
||||
/>
|
||||
</div>
|
||||
<Trademark space={space} />
|
||||
</aside>
|
||||
|
||||
+136
-23
@@ -5,6 +5,12 @@ import { headers } from 'next/headers';
|
||||
|
||||
import { cache } from './cache';
|
||||
|
||||
export interface ContentPointer {
|
||||
spaceId: string;
|
||||
changeRequestId?: string;
|
||||
revisionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an API client for the current request.
|
||||
*/
|
||||
@@ -36,6 +42,72 @@ export const getSpace = cache('api.getSpace', async (spaceId: string) => {
|
||||
return data;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all the pages in the space.
|
||||
*/
|
||||
export const getRevisionPages = cache('api.getRevisionPages', async (pointer: ContentPointer) => {
|
||||
const { data } = await (async () => {
|
||||
if (pointer.revisionId) {
|
||||
return api().spaces.listPagesInRevisionById(pointer.spaceId, pointer.revisionId, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
}
|
||||
|
||||
if (pointer.changeRequestId) {
|
||||
return api().spaces.listPagesInChangeRequest(spaceId, pointer.changeRequestId, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
}
|
||||
|
||||
return api().spaces.listPages(pointer.spaceId, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
})();
|
||||
return data.pages!;
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve a file by its ID.
|
||||
*/
|
||||
export const getRevisionFile = cache(
|
||||
'api.getRevisionFile',
|
||||
async (pointer: ContentPointer, fileId: string) => {
|
||||
try {
|
||||
const { data } = await (async () => {
|
||||
if (pointer.revisionId) {
|
||||
return api().spaces.getFileInRevisionById(
|
||||
pointer.spaceId,
|
||||
pointer.revisionId,
|
||||
fileId,
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (pointer.changeRequestId) {
|
||||
return api().spaces.getFileInChangeRequestById(
|
||||
spaceId,
|
||||
pointer.changeRequestId,
|
||||
fileId,
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return api().spaces.getFileById(pointer.spaceId, fileId, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
})();
|
||||
return data;
|
||||
} catch (error) {
|
||||
// TODO: improve error handling to throw non-404
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the current revision of a space
|
||||
*/
|
||||
@@ -51,21 +123,59 @@ export const getCurrentRevision = cache('api.getCurrentRevision', async (spaceId
|
||||
*/
|
||||
export const getPageDocument = cache(
|
||||
'api.getPageDocument',
|
||||
async (spaceId: string, revisionId: string, pageId: string) => {
|
||||
const { data } = await api().spaces.getPageInRevisionById(
|
||||
spaceId,
|
||||
revisionId,
|
||||
pageId,
|
||||
{},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
async (pointer: ContentPointer, pageId: string) => {
|
||||
const { data } = await (async () => {
|
||||
if (pointer.revisionId) {
|
||||
return api().spaces.getPageInRevisionById(
|
||||
pointer.spaceId,
|
||||
pointer.revisionId,
|
||||
pageId,
|
||||
{
|
||||
format: 'document',
|
||||
},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (pointer.changeRequestId) {
|
||||
return api().spaces.getPageInChangeRequestById(
|
||||
pointer.spaceId,
|
||||
pointer.changeRequestId,
|
||||
pageId,
|
||||
{
|
||||
format: 'document',
|
||||
},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return api().spaces.getPageById(
|
||||
pointer.spaceId,
|
||||
pageId,
|
||||
{
|
||||
format: 'document',
|
||||
},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
})();
|
||||
// @ts-ignore
|
||||
return data.document as JSONDocument;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Get a document by its ID.
|
||||
*/
|
||||
export const getDocument = cache('api.getDocument', async (spaceId: string, documentId: string) => {
|
||||
// TODO
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the customization settings for a space.
|
||||
*/
|
||||
@@ -79,8 +189,8 @@ export const getSpaceCustomization = cache('api.getSpaceCustomization', async (s
|
||||
/**
|
||||
* Get the infos about a collection by its ID.
|
||||
*/
|
||||
export const getCollection = cache('api.getCollection', async (spaceId: string) => {
|
||||
const { data } = await api().collections.getCollectionById(spaceId, {
|
||||
export const getCollection = cache('api.getCollection', async (collectionId: string) => {
|
||||
const { data } = await api().collections.getCollectionById(collectionId, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
return data;
|
||||
@@ -89,14 +199,17 @@ export const getCollection = cache('api.getCollection', async (spaceId: string)
|
||||
/**
|
||||
* List all the spaces variants published in a collection.
|
||||
*/
|
||||
export const getCollectionSpaces = cache('api.getCollectionSpaces', async (spaceId: string) => {
|
||||
const { data } = await api().collections.listSpacesInCollectionById(
|
||||
spaceId,
|
||||
{},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
// TODO: do this filtering on the API side
|
||||
return data.items.filter((space) => space.visibility === ContentVisibility.InCollection);
|
||||
});
|
||||
export const getCollectionSpaces = cache(
|
||||
'api.getCollectionSpaces',
|
||||
async (collectionId: string) => {
|
||||
const { data } = await api().collections.listSpacesInCollectionById(
|
||||
collectionId,
|
||||
{},
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
);
|
||||
// TODO: do this filtering on the API side
|
||||
return data.items.filter((space) => space.visibility === ContentVisibility.InCollection);
|
||||
},
|
||||
);
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ const memoryCache = new Map<string, any>();
|
||||
* Cache data from an async function.
|
||||
* We don't use the next.js cache because it has a 2MB limit.
|
||||
*/
|
||||
export function cache<Args extends string[], Result>(
|
||||
export function cache<Args extends any[], Result>(
|
||||
fnName: string,
|
||||
fn: (...args: Args) => Promise<Result>,
|
||||
): (...args: Args) => Promise<Result> {
|
||||
@@ -38,8 +38,8 @@ export function cache<Args extends string[], Result>(
|
||||
/**
|
||||
* Create a cache key from a function name and its arguments.
|
||||
*/
|
||||
function getCacheKey(fnName: string, args: string[]) {
|
||||
return `${fnName}(${args.join(',')})`;
|
||||
function getCacheKey(fnName: string, args: any[]) {
|
||||
return `${fnName}(${args.map((arg) => JSON.stringify(arg)).join(',')})`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@ export type AncestorRevisionPage = RevisionPageDocument | RevisionPageGroup;
|
||||
* Resolve a page path to a page document.
|
||||
*/
|
||||
export function resolvePagePath(
|
||||
revision: Revision,
|
||||
rootPages: Revision['pages'],
|
||||
pagePath: string,
|
||||
): { page: RevisionPageDocument; ancestors: AncestorRevisionPage[] } | undefined {
|
||||
const iteratePages = (
|
||||
@@ -33,7 +33,7 @@ export function resolvePagePath(
|
||||
};
|
||||
|
||||
if (!pagePath) {
|
||||
const firstPage = resolveFirstDocument(revision.pages, []);
|
||||
const firstPage = resolveFirstDocument(rootPages, []);
|
||||
if (!firstPage) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -41,14 +41,14 @@ export function resolvePagePath(
|
||||
return firstPage;
|
||||
}
|
||||
|
||||
return iteratePages(revision.pages, []);
|
||||
return iteratePages(rootPages, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a page by its ID in a revision.
|
||||
*/
|
||||
export function resolvePageId(
|
||||
revision: Revision,
|
||||
rootPages: Revision['pages'],
|
||||
pageId: string,
|
||||
): { page: RevisionPageDocument; ancestors: AncestorRevisionPage[] } | undefined {
|
||||
const iteratePages = (
|
||||
@@ -70,17 +70,17 @@ export function resolvePageId(
|
||||
}
|
||||
}
|
||||
};
|
||||
return iteratePages(revision.pages, []);
|
||||
return iteratePages(rootPages, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the next/previous page before another one.
|
||||
*/
|
||||
export function resolvePrevNextPages(
|
||||
revision: Revision,
|
||||
rootPages: Revision['pages'],
|
||||
page: RevisionPageDocument,
|
||||
): { previous?: RevisionPageDocument; next?: RevisionPageDocument } {
|
||||
const flat = flattenPages(revision.pages);
|
||||
const flat = flattenPages(rootPages);
|
||||
|
||||
const currentIndex = flat.findIndex((p) => p.id === page.id);
|
||||
if (currentIndex === -1) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContentRef, Revision, RevisionPageDocument, Space } from '@gitbook/api';
|
||||
|
||||
import { ContentPointer, getRevisionFile } from './api';
|
||||
import { pageHref, PageHrefContext } from './links';
|
||||
import { resolvePageId } from './pages';
|
||||
|
||||
@@ -13,8 +14,9 @@ export interface ResolvedContentRef {
|
||||
}
|
||||
|
||||
export interface ContentRefContext extends PageHrefContext {
|
||||
content: ContentPointer;
|
||||
space: Space;
|
||||
revision: Revision;
|
||||
pages: Revision['pages'];
|
||||
page: RevisionPageDocument;
|
||||
}
|
||||
|
||||
@@ -23,7 +25,7 @@ export interface ContentRefContext extends PageHrefContext {
|
||||
*/
|
||||
export async function resolveContentRef(
|
||||
contentRef: ContentRef,
|
||||
{ space, revision, page: activePage, ...linksContext }: ContentRefContext,
|
||||
{ content, space, pages, page: activePage, ...linksContext }: ContentRefContext,
|
||||
): Promise<ResolvedContentRef | null> {
|
||||
// Try to resolve a local ref in the current space
|
||||
if (contentRef.kind === 'url') {
|
||||
@@ -33,7 +35,7 @@ export async function resolveContentRef(
|
||||
active: false,
|
||||
};
|
||||
} else if (contentRef.kind === 'file') {
|
||||
const file = revision.files.find((file) => file.id === contentRef.file);
|
||||
const file = await getRevisionFile(content, contentRef.file);
|
||||
if (file) {
|
||||
return {
|
||||
href: file.downloadURL,
|
||||
@@ -50,7 +52,7 @@ export async function resolveContentRef(
|
||||
const page =
|
||||
!contentRef.page || contentRef.page === activePage.id
|
||||
? activePage
|
||||
: resolvePageId(revision, contentRef.page)?.page;
|
||||
: resolvePageId(pages, contentRef.page)?.page;
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user