From c3675fdfe7f7d8d9ccee9724f5647c96fe981fba Mon Sep 17 00:00:00 2001 From: Steven H Date: Fri, 18 Oct 2024 10:59:46 +0100 Subject: [PATCH] Add support for reusable content (#2531) --- .changeset/slimy-rules-think.md | 5 ++ bun.lockb | Bin 580040 -> 580040 bytes packages/gitbook/package.json | 2 +- .../src/components/DocumentView/Block.tsx | 3 +- .../DocumentView/ReusableContent.tsx | 34 ++++++++++ packages/gitbook/src/lib/api.ts | 63 +++++++++++++++++- packages/gitbook/src/lib/references.tsx | 29 +++++++- 7 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 .changeset/slimy-rules-think.md create mode 100644 packages/gitbook/src/components/DocumentView/ReusableContent.tsx diff --git a/.changeset/slimy-rules-think.md b/.changeset/slimy-rules-think.md new file mode 100644 index 000000000..19c9ca5ea --- /dev/null +++ b/.changeset/slimy-rules-think.md @@ -0,0 +1,5 @@ +--- +'gitbook': minor +--- + +Added support for new Reusable Content block. diff --git a/bun.lockb b/bun.lockb index 83eb8366fdfc510b7b777e19a3455c4f08ea01da..ee93c9e0592d5cbb3577504b1235b7d98362c4ca 100755 GIT binary patch delta 174 zcmV;f08#(Qvm?l}Bakj2FMSVxG6aUgI-XNKA!ssU2V0?}t!6vXmsPHIJn$s3fljqf z0Tw#}N0Sj-D~FIf0f&$~0=JMm1HBkP>H~xHRi^5wNIu6g21bAx+g%37zgl4q-$HC> zZWRHc{YCG|O4|!>`99Mg+?~>JcDg{23zr3fi50XLTcqz59m@h%A0s#NAi=>Px# delta 175 zcmV;g08szPvm?l}Bakj25@*hPsZd=#NJvK2tlT*xaDi>zx;ODsj4*p8 zBJbChM-D&-cI6H`gQZzDWZiv?y2}ReAua?3gfv2|h!%-yhmBMNhmBMOw~bT<7OVm` dIfbPLg{23zr3fi50ya695gP{~xA86r*Q%v-M@|3$ diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json index 6769ec21f..8a095c00f 100644 --- a/packages/gitbook/package.json +++ b/packages/gitbook/package.json @@ -16,7 +16,7 @@ "clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static" }, "dependencies": { - "@gitbook/api": "^0.69.0", + "@gitbook/api": "^0.71.0", "@gitbook/cache-do": "workspace:*", "@gitbook/emoji-codepoints": "workspace:*", "@gitbook/icons": "workspace:*", diff --git a/packages/gitbook/src/components/DocumentView/Block.tsx b/packages/gitbook/src/components/DocumentView/Block.tsx index 9aeca506f..df3657e15 100644 --- a/packages/gitbook/src/components/DocumentView/Block.tsx +++ b/packages/gitbook/src/components/DocumentView/Block.tsx @@ -32,6 +32,7 @@ import { BlockMath } from './Math'; import { OpenAPI } from './OpenAPI'; import { Paragraph } from './Paragraph'; import { Quote } from './Quote'; +import { ReusableContent } from './ReusableContent'; import { Stepper } from './Stepper'; import { StepperStep } from './StepperStep'; import { Table } from './Table'; @@ -107,7 +108,7 @@ export function Block(props: BlockProps) { case 'synced-block': return ; case 'reusable-content': - return null; + return ; case 'stepper': return ; case 'stepper-step': diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx new file mode 100644 index 000000000..e8ed31312 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -0,0 +1,34 @@ +import { DocumentBlockReusableContent } from '@gitbook/api'; + +import { getDocument } from '@/lib/api'; + +import { BlockProps } from './Block'; +import { UnwrappedBlocks } from './Blocks'; + +export async function ReusableContent(props: BlockProps) { + const { block, context, ancestorBlocks } = props; + + if (!context.content) { + throw new Error(`Expected a content context to render a reusable content block`); + } + + const resolved = await context.resolveContentRef(block.data.ref); + if (!resolved?.reusableContent?.document) { + return null; + } + + const document = await getDocument(context.content.spaceId, resolved.reusableContent.document); + + if (!document) { + return null; + } + + return ( + + ); +} diff --git a/packages/gitbook/src/lib/api.ts b/packages/gitbook/src/lib/api.ts index 46d5d608f..c8bb288f4 100644 --- a/packages/gitbook/src/lib/api.ts +++ b/packages/gitbook/src/lib/api.ts @@ -12,7 +12,7 @@ import { RequestRenderIntegrationUI, RevisionFile, SiteCustomizationSettings, - SpaceIntegrationScript, + RevisionReusableContent, } from '@gitbook/api'; import assertNever from 'assert-never'; import { headers } from 'next/headers'; @@ -491,6 +491,43 @@ const getRevisionFileById = cache({ }, }); +const getRevisionReusableContentById = cache({ + name: 'api.getRevisionReusableContentById.v1', + tag: (spaceId, revisionId) => + getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + get: async ( + spaceId: string, + revisionId: string, + reusableContentId: string, + options: CacheFunctionOptions, + ) => { + try { + const response = await (async () => { + return api().spaces.getReusableContentInRevisionById( + spaceId, + revisionId, + reusableContentId, + { + metadata: false, + }, + { + ...noCacheFetchOptions, + signal: options.signal, + }, + ); + })(); + + return cacheResponse(response, cacheTtl_7days); + } catch (error: any) { + if (error instanceof GitBookAPIError && error.code === 404) { + return { data: null, ...cacheTtl_7days }; + } + + throw error; + } + }, +}); + /** * Get all the files in a revision of a space. * It should not be used directly, use `getRevisionFile` instead. @@ -582,6 +619,30 @@ export const getRevisionFile = batch<[string, string, string], RevisionFile | nu }, ); +/** + * Get reusable content in a revision. + */ +export const getReusableContent = async ( + spaceId: string, + revisionId: string, + reusableContentId: string, +): Promise => { + const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, { + metadata: false, + }); + + if (hasRevisionInMemory) { + const revision = await getRevision(spaceId, revisionId, { metadata: false }); + return ( + revision.reusableContents.find( + (reusableContent) => reusableContent.id === reusableContentId, + ) ?? null + ); + } else { + return getRevisionReusableContentById(spaceId, revisionId, reusableContentId); + } +}; + /** * Get a document by its ID. */ diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index 309fc12eb..ca15e7ec6 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -1,4 +1,11 @@ -import { ContentRef, Revision, RevisionFile, RevisionPageDocument, Space } from '@gitbook/api'; +import { + ContentRef, + Revision, + RevisionFile, + RevisionPageDocument, + RevisionReusableContent, + Space, +} from '@gitbook/api'; import assertNever from 'assert-never'; import React from 'react'; @@ -9,6 +16,7 @@ import { SpaceContentPointer, getCollection, getDocument, + getReusableContent, getRevisionFile, getSpace, getSpaceContentData, @@ -35,6 +43,8 @@ export interface ResolvedContentRef { active: boolean; /** File, if the reference is a file */ file?: RevisionFile; + /** Resolved reusable content, if the ref points to reusable content on a revision. */ + reusableContent?: RevisionReusableContent; } export interface ContentRefContext extends PageHrefContext { @@ -255,7 +265,22 @@ export async function resolveContentRef( }; } - case 'reusable-content': + case 'reusable-content': { + const reusableContent = await getReusableContent( + space.id, + revisionId, + contentRef.reusableContent, + ); + if (!reusableContent) { + return null; + } + return { + href: gitbookAppHref(`/s/${space.id}`), + text: reusableContent.title, + active: false, + reusableContent, + }; + } case 'synced-block': return null;