diff --git a/bun.lockb b/bun.lockb index 946566f15..1fa2f7086 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 19a31fb98..23d714902 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@geist-ui/icons": "^1.0.2", - "@gitbook/api": "^0.25.0", + "@gitbook/api": "^0.26.0", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-popover": "^1.0.7", "@readme/openapi-parser": "^2.5.0", diff --git a/src/app/[spaceId]/(content)/layout.tsx b/src/app/[spaceId]/(content)/layout.tsx index 3b9d848d3..17630f225 100644 --- a/src/app/[spaceId]/(content)/layout.tsx +++ b/src/app/[spaceId]/(content)/layout.tsx @@ -3,6 +3,7 @@ import { Metadata, Viewport } from 'next'; import Script from 'next/script'; import React from 'react'; +import { AdminToolbar } from '@/components/AdminToolbar'; import { CookiesToast } from '@/components/Cookies'; import { SpaceLayout } from '@/components/SpaceLayout'; import { getContentSecurityPolicyNonce } from '@/lib/csp'; @@ -57,6 +58,10 @@ export default async function ContentLayout(props: { ) : null} + + {content.revisionId || content.changeRequestId ? ( + + ) : null} ); } diff --git a/src/app/[spaceId]/(core)/sitemap.xml/route.ts b/src/app/[spaceId]/(core)/sitemap.xml/route.ts index 0729688c7..93e006db6 100644 --- a/src/app/[spaceId]/(core)/sitemap.xml/route.ts +++ b/src/app/[spaceId]/(core)/sitemap.xml/route.ts @@ -5,7 +5,7 @@ import { NextRequest } from 'next/server'; import { getRevisionPages } from '@/lib/api'; import { pageHref } from '@/lib/links'; -import { SpaceParams } from '../../fetch'; +import { SpaceParams, getContentPointer } from '../../fetch'; export const runtime = 'edge'; @@ -13,7 +13,7 @@ export const runtime = 'edge'; * Generate a sitemap.xml for the current space. */ export async function GET(req: NextRequest, { params }: { params: SpaceParams }) { - const rootPages = await getRevisionPages(params); + const rootPages = await getRevisionPages(getContentPointer(params)); const pages = flattenPages(rootPages); const urls = pages.map(({ page, depth }) => { // Decay priority with depth diff --git a/src/app/[spaceId]/fetch.ts b/src/app/[spaceId]/fetch.ts index 87eb9a8ef..04b7004eb 100644 --- a/src/app/[spaceId]/fetch.ts +++ b/src/app/[spaceId]/fetch.ts @@ -1,4 +1,5 @@ import { ContentVisibility, RevisionPage, Space } from '@gitbook/api'; +import { headers } from 'next/headers'; import { getCollectionSpaces, @@ -10,7 +11,9 @@ import { } from '@/lib/api'; import { resolvePagePath, resolvePageId } from '@/lib/pages'; -export type SpaceParams = ContentPointer; +export interface SpaceParams { + spaceId: string; +} export interface PagePathParams extends SpaceParams { pathname?: string[]; @@ -20,16 +23,25 @@ export interface PageIdParams extends SpaceParams { pageId?: string; } +/** + * Get the current content pointer from the params. + */ +export function getContentPointer(params: PagePathParams | PageIdParams) { + const headerSet = headers(); + const content: ContentPointer = { + spaceId: params.spaceId, + revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined, + changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined, + }; + + return content; +} + /** * Fetch all the data needed to render the space layout. */ export async function fetchSpaceData(params: PagePathParams | PageIdParams) { - const content: ContentPointer = { - spaceId: params.spaceId, - changeRequestId: params.changeRequestId, - revisionId: params.revisionId, - }; - + const content = getContentPointer(params); const { space, pages, customization, scripts } = await getSpaceContent(content); const collection = await fetchParentCollection(space); @@ -49,12 +61,7 @@ export async function fetchSpaceData(params: PagePathParams | PageIdParams) { * Optimized to fetch in parallel as much as possible. */ export async function fetchPageData(params: PagePathParams | PageIdParams) { - const content: ContentPointer = { - spaceId: params.spaceId, - changeRequestId: params.changeRequestId, - revisionId: params.revisionId, - }; - + const content = getContentPointer(params); const { space, pages, customization, scripts } = await getSpaceContent(content); const page = await resolvePage(pages, content, params); @@ -120,5 +127,5 @@ async function fetchParentCollection(space: Space) { */ export function getPathnameParam(params: PagePathParams): string { const { pathname } = params; - return pathname ? pathname.map(part => decodeURIComponent(part)).join('/') : ''; + return pathname ? pathname.map((part) => decodeURIComponent(part)).join('/') : ''; } diff --git a/src/components/AdminToolbar/AdminToolbar.tsx b/src/components/AdminToolbar/AdminToolbar.tsx new file mode 100644 index 000000000..d1b63bc96 --- /dev/null +++ b/src/components/AdminToolbar/AdminToolbar.tsx @@ -0,0 +1,95 @@ +import React from 'react'; + +import { ContentPointer, getChangeRequest, getRevision, getRevisionPages } from '@/lib/api'; +import { tcls } from '@/lib/tailwind'; + +interface AdminToolbarProps { + content: ContentPointer; +} + +/** + * Toolbar with information for the content admin when previewing a revision or change-request. + */ +export function AdminToolbar(props: AdminToolbarProps) { + const { content } = props; + + return ( +
+ + {content.changeRequestId ? ( + + ) : null} + {content.revisionId ? ( + + ) : null} + +
+ ); +} + +async function ChangeRequestToolbar(props: { spaceId: string; changeRequestId: string }) { + const { spaceId, changeRequestId } = props; + + const changeRequest = await getChangeRequest(spaceId, changeRequestId); + + return ( + + Change request #{changeRequest.number}: {changeRequest.subject ?? 'No subject'} + + ); +} + +async function RevisionToolbar(props: { spaceId: string; revisionId: string }) { + const { spaceId, revisionId } = props; + + const revision = await getRevision(spaceId, revisionId); + + return ( + + Revision created on {new Date(revision.createdAt).toLocaleDateString()} + + ); +} + +function ToolbarButton(props: { href: string; children: React.ReactNode }) { + const { href, children } = props; + + return ( + + {children} + + ); +} diff --git a/src/components/AdminToolbar/index.ts b/src/components/AdminToolbar/index.ts new file mode 100644 index 000000000..bcbabf184 --- /dev/null +++ b/src/components/AdminToolbar/index.ts @@ -0,0 +1 @@ +export * from './AdminToolbar'; diff --git a/src/components/Search/SearchScopeToggle.tsx b/src/components/Search/SearchScopeToggle.tsx index fb75fe10c..0ff825ebf 100644 --- a/src/components/Search/SearchScopeToggle.tsx +++ b/src/components/Search/SearchScopeToggle.tsx @@ -18,7 +18,7 @@ export function SearchScopeToggle(props: { spaceTitle: string }) { return (
@@ -53,6 +53,8 @@ function ToggleButton(props: { onClick: () => void; children: React.ReactNode; a return (