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 (