From 2fdf3e3838b372f19f4bd66ea75753579f5040fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Thu, 28 Dec 2023 14:38:15 +0100 Subject: [PATCH] Progressive loading (#79) * Suspense boundary and progressive loading on blocks * Start refactoring to better leverage app router * Use skeleton for page layout * Improve dynamic toc * Switch page full width to be client side only * Format * Remove old PageLoading * Fix first page not marked as active * Close search when clicking search link --- .../(content)/[[...pathname]]/loading.tsx | 25 ++ .../(content)/[[...pathname]]/page.tsx | 108 +++++++++ .../page.tsx => (content)/layout.tsx} | 64 ++--- .../{ => (core)}/robots.txt/route.ts | 2 +- .../{ => (core)}/sitemap.xml/route.ts | 2 +- .../{ => (core)}/~gitbook/icon/route.tsx | 2 +- .../~gitbook/ogimage/[pageId]/route.tsx | 2 +- .../~gitbook/pdf/PageControlButtons.tsx | 0 .../{ => (core)}/~gitbook/pdf/PrintButton.tsx | 0 .../{ => (core)}/~gitbook/pdf/page.tsx | 2 +- .../{ => (core)}/~gitbook/pdf/params.ts | 0 .../{ => (core)}/~gitbook/pdf/pdf.css | 0 src/app/[spaceId]/fetch.ts | 28 ++- src/app/[spaceId]/layout.tsx | 4 + src/components/DocumentView/Block.tsx | 117 +++++++--- src/components/Footer/Footer.tsx | 16 +- src/components/Header/Header.tsx | 24 +- src/components/PageAside/PageAside.tsx | 6 +- src/components/PageBody/PageBody.tsx | 108 ++++----- src/components/PageBody/PageLoading.tsx | 43 ---- .../PageBody/TogglePageFullWidth.tsx | 19 ++ src/components/Search/SearchModal.tsx | 1 + .../Search/SearchPageResultItem.tsx | 4 +- src/components/Search/SearchResults.tsx | 5 +- .../Search/SearchSectionResultItem.tsx | 5 +- src/components/SpaceContent/SpaceContent.tsx | 165 ------------- src/components/SpaceContent/index.ts | 1 - src/components/SpaceLayout/SpaceLayout.tsx | 102 ++++++++ src/components/SpaceLayout/index.ts | 1 + .../TableOfContents/PageDocumentItem.tsx | 75 +----- .../TableOfContents/PageGroupItem.tsx | 4 +- src/components/TableOfContents/PagesList.tsx | 5 +- .../TableOfContents/TableOfContents.tsx | 3 - .../TableOfContents/ToggleableLinkItem.tsx | 218 ++++++++++++------ src/components/layout.ts | 16 +- src/components/primitives/Skeleton.tsx | 73 ++++++ src/components/primitives/index.ts | 1 + src/components/state/index.ts | 1 - src/components/state/useIsLoadingPage.tsx | 37 --- src/lib/references.ts | 6 +- tailwind.config.ts | 1 + 41 files changed, 722 insertions(+), 574 deletions(-) create mode 100644 src/app/[spaceId]/(content)/[[...pathname]]/loading.tsx create mode 100644 src/app/[spaceId]/(content)/[[...pathname]]/page.tsx rename src/app/[spaceId]/{[[...pathname]]/page.tsx => (content)/layout.tsx} (64%) rename src/app/[spaceId]/{ => (core)}/robots.txt/route.ts (96%) rename src/app/[spaceId]/{ => (core)}/sitemap.xml/route.ts (98%) rename src/app/[spaceId]/{ => (core)}/~gitbook/icon/route.tsx (98%) rename src/app/[spaceId]/{ => (core)}/~gitbook/ogimage/[pageId]/route.tsx (95%) rename src/app/[spaceId]/{ => (core)}/~gitbook/pdf/PageControlButtons.tsx (100%) rename src/app/[spaceId]/{ => (core)}/~gitbook/pdf/PrintButton.tsx (100%) rename src/app/[spaceId]/{ => (core)}/~gitbook/pdf/page.tsx (99%) rename src/app/[spaceId]/{ => (core)}/~gitbook/pdf/params.ts (100%) rename src/app/[spaceId]/{ => (core)}/~gitbook/pdf/pdf.css (100%) delete mode 100644 src/components/PageBody/PageLoading.tsx create mode 100644 src/components/PageBody/TogglePageFullWidth.tsx delete mode 100644 src/components/SpaceContent/SpaceContent.tsx delete mode 100644 src/components/SpaceContent/index.ts create mode 100644 src/components/SpaceLayout/SpaceLayout.tsx create mode 100644 src/components/SpaceLayout/index.ts create mode 100644 src/components/primitives/Skeleton.tsx delete mode 100644 src/components/state/index.ts delete mode 100644 src/components/state/useIsLoadingPage.tsx 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 }) => (