diff --git a/src/app/[spaceId]/(content)/[[...pathname]]/loading.tsx b/src/app/[spaceId]/(content)/[[...pathname]]/loading.tsx new file mode 100644 index 000000000..349690565 --- /dev/null +++ b/src/app/[spaceId]/(content)/[[...pathname]]/loading.tsx @@ -0,0 +1,25 @@ +import { SkeletonHeading, SkeletonParagraph } from '@/components/primitives'; +import { tcls } from '@/lib/tailwind'; + +/** + * Placeholder when loading a page. + */ +export default function PageSkeleton() { + return ( +
+
+ + +
+
+ ); +} diff --git a/src/app/[spaceId]/(content)/[[...pathname]]/page.tsx b/src/app/[spaceId]/(content)/[[...pathname]]/page.tsx new file mode 100644 index 000000000..6a7e2f9c5 --- /dev/null +++ b/src/app/[spaceId]/(content)/[[...pathname]]/page.tsx @@ -0,0 +1,108 @@ +import { CustomizationHeaderPreset, CustomizationThemeMode } from '@gitbook/api'; +import { Metadata, Viewport } from 'next'; +import { notFound, redirect } from 'next/navigation'; +import React from 'react'; + +import { PageAside } from '@/components/PageAside'; +import { PageBody, PageCover } from '@/components/PageBody'; +import { PageHrefContext, absoluteHref, pageHref } from '@/lib/links'; +import { getPagePath } from '@/lib/pages'; +import { ContentRefContext } from '@/lib/references'; +import { tcls } from '@/lib/tailwind'; + +import { PagePathParams, fetchPageData, getPathnameParam } from '../../fetch'; + +export const runtime = 'edge'; + +/** + * Fetch and render a page. + */ +export default async function Page(props: { params: PagePathParams }) { + const { params } = props; + + const { content, space, customization, pages, page, document } = await fetchPageData(params); + const linksContext: PageHrefContext = {}; + + if (!page) { + notFound(); + } else if (getPagePath(pages, page) !== getPathnameParam(params)) { + redirect(pageHref(pages, page, linksContext)); + } + + const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None; + const withFullPageCover = !!( + page.cover && + page.layout.cover && + page.layout.coverSize === 'full' + ); + const withPageFeedback = customization.feedback.enabled; + + const contentRefContext: ContentRefContext = { + space, + pages, + page, + content, + }; + + return ( + <> + {withFullPageCover && page.cover ? ( + + ) : null} +
+ + {page.layout.outline ? ( + + ) : null} +
+ + ); +} + +export async function generateViewport({ params }: { params: PagePathParams }): Promise { + const { customization } = await fetchPageData(params); + return { + colorScheme: customization.themes.toggeable + ? customization.themes.default === CustomizationThemeMode.Dark + ? 'dark light' + : 'light dark' + : customization.themes.default, + }; +} + +export async function generateMetadata({ params }: { params: PagePathParams }): Promise { + const { space, page, customization } = await fetchPageData(params); + if (!page) { + notFound(); + } + + return { + title: `${page.title} | ${space.title}`, + description: page.description ?? '', + openGraph: { + images: [ + customization.socialPreview.url ?? absoluteHref(`~gitbook/ogimage/${page.id}`), + ], + }, + }; +} diff --git a/src/app/[spaceId]/[[...pathname]]/page.tsx b/src/app/[spaceId]/(content)/layout.tsx similarity index 64% rename from src/app/[spaceId]/[[...pathname]]/page.tsx rename to src/app/[spaceId]/(content)/layout.tsx index 2235d71ba..3b9d848d3 100644 --- a/src/app/[spaceId]/[[...pathname]]/page.tsx +++ b/src/app/[spaceId]/(content)/layout.tsx @@ -1,25 +1,26 @@ import { CustomizationThemeMode } from '@gitbook/api'; import { Metadata, Viewport } from 'next'; -import { notFound, redirect } from 'next/navigation'; import Script from 'next/script'; import React from 'react'; import { CookiesToast } from '@/components/Cookies'; -import { SpaceContent } from '@/components/SpaceContent'; +import { SpaceLayout } from '@/components/SpaceLayout'; import { getContentSecurityPolicyNonce } from '@/lib/csp'; -import { PageHrefContext, absoluteHref, baseUrl, pageHref } from '@/lib/links'; -import { getPagePath } from '@/lib/pages'; +import { absoluteHref, baseUrl } from '@/lib/links'; import { shouldIndexSpace } from '@/lib/seo'; -import { PagePathParams, fetchPageData, getPathnameParam } from '../fetch'; +import { SpaceParams, fetchSpaceData } from '../fetch'; export const runtime = 'edge'; /** - * Fetch and render a page. + * Layout when rendering the content. */ -export default async function Page(props: { params: PagePathParams }) { - const { params } = props; +export default async function ContentLayout(props: { + params: SpaceParams; + children: React.ReactNode; +}) { + const { params, children } = props; const nonce = getContentSecurityPolicyNonce(); const { @@ -27,34 +28,25 @@ export default async function Page(props: { params: PagePathParams }) { space, customization, pages, - page, collection, collectionSpaces, ancestors, - document, scripts, - } = await fetchPageData(params); - const linksContext: PageHrefContext = {}; - - if (!page) { - notFound(); - } else if (getPagePath(pages, page) !== getPathnameParam(params)) { - redirect(pageHref(pages, page, linksContext)); - } + } = await fetchSpaceData(params); return ( <> - + customization={customization} + pages={pages} + ancestors={ancestors} + content={content} + > + {children} + {scripts.map(({ script }) => (