mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 03:16:40 +00:00
Add support for reusable content (#2531)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': minor
|
||||
---
|
||||
|
||||
Added support for new Reusable Content block.
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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<T extends DocumentBlock>(props: BlockProps<T>) {
|
||||
case 'synced-block':
|
||||
return <BlockSyncedBlock {...props} {...contextProps} block={block} />;
|
||||
case 'reusable-content':
|
||||
return null;
|
||||
return <ReusableContent {...props} {...contextProps} block={block} />;
|
||||
case 'stepper':
|
||||
return <Stepper {...props} {...contextProps} block={block} />;
|
||||
case 'stepper-step':
|
||||
|
||||
@@ -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<DocumentBlockReusableContent>) {
|
||||
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 (
|
||||
<UnwrappedBlocks
|
||||
nodes={document.nodes}
|
||||
document={document}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
context={context}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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<RevisionReusableContent | null> => {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user