diff --git a/packages/gitbook/src/components/DocumentView/Update.tsx b/packages/gitbook/src/components/DocumentView/Update.tsx index 09eef1f7d..dc4feedae 100644 --- a/packages/gitbook/src/components/DocumentView/Update.tsx +++ b/packages/gitbook/src/components/DocumentView/Update.tsx @@ -1,7 +1,9 @@ import { formatDateFull, formatDateShort, formatNumericDate } from '@/components/utils/dates'; +import { getRevisionTags, resolveBlockTags } from '@/lib/tags'; import { tcls } from '@/lib/tailwind'; import type { DocumentBlockUpdate, DocumentBlockUpdates } from '@gitbook/api'; import { assert } from 'ts-essentials'; +import { Tag } from '../Tag'; import type { BlockProps } from './Block'; import { Blocks } from './Blocks'; @@ -35,13 +37,12 @@ export function Update(props: BlockProps) { short: formatDateShort(parsedDate), }[dateFormat]; + // Resolve tags from the block data using revision-level tag definitions + const revisionTags = getRevisionTags(contextProps.context.contentContext?.revision); + const resolvedTags = resolveBlockTags(block.data.tags, revisionTags); + return ( -
+
) { > {displayDate} + {resolvedTags.length > 0 ? ( +
+ {resolvedTags.map((tag) => ( + + ))} +
+ ) : null}
)} + {page.layout.title ? (

+ {pageTags.map((tagRef) => { + const resolved = resolveTag(tagRef.tag.tag, revisionTags); + if (!resolved) { + return null; + } + return ; + })} +

+ ); +} diff --git a/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx b/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx index a799a6531..f53bc94bd 100644 --- a/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx @@ -1,12 +1,12 @@ 'use client'; import { tcls } from '@/lib/tailwind'; -import type { ClientTOCPageDocument } from './encodeClientTableOfContents'; - import { SiteInsightsLinkPosition } from '@gitbook/api'; +import { Tag } from '../Tag'; import { PagesList } from './PagesList'; import { TOCPageIcon } from './TOCPageIcon'; import { ToggleableLinkItem } from './ToggleableLinkItem'; +import type { ClientTOCPageDocument } from './encodeClientTableOfContents'; export function PageDocumentItem(props: { page: ClientTOCPageDocument }) { const { page } = props; @@ -40,7 +40,10 @@ export function PageDocumentItem(props: { page: ClientTOCPageDocument }) { } > - {page.title} + + {page.title} + {page.primaryTag ? : null} + ); diff --git a/packages/gitbook/src/components/TableOfContents/encodeClientTableOfContents.ts b/packages/gitbook/src/components/TableOfContents/encodeClientTableOfContents.ts index bf8ccb298..df8667a68 100644 --- a/packages/gitbook/src/components/TableOfContents/encodeClientTableOfContents.ts +++ b/packages/gitbook/src/components/TableOfContents/encodeClientTableOfContents.ts @@ -1,8 +1,9 @@ import type { GitBookSiteContext } from '@/lib/context'; import { getPagePaths, hasPageVisibleDescendant } from '@/lib/pages'; import { resolveContentRef } from '@/lib/references'; +import { getRevisionTags, resolveTag } from '@/lib/tags'; import { removeUndefined } from '@/lib/typescript'; -import type { ContentRef, RevisionPage } from '@gitbook/api'; +import type { ContentRef, RevisionPage, RevisionPageTag, RevisionTag } from '@gitbook/api'; import assertNever from 'assert-never'; export type ClientTOCPageLink = { @@ -24,6 +25,7 @@ export type ClientTOCPageDocument = { icon?: string; pathnames: string[]; descendants?: ClientTOCPage[]; + primaryTag?: RevisionTag; }; export type ClientTOCPageGroup = { @@ -69,6 +71,8 @@ export async function encodeClientTableOfContents( ? await encodeClientTableOfContents(context, rootPages, page.pages) : undefined; + const primaryTag = resolvePrimaryTag(page.tags, context); + result.push( removeUndefined({ id: page.id, @@ -78,6 +82,7 @@ export async function encodeClientTableOfContents( icon: page.icon, pathnames: getPagePaths(rootPages, page), descendants, + primaryTag, type: 'document', }) ); @@ -122,3 +127,25 @@ export async function encodeClientTableOfContents( return result; } + +/** + * Tags attached to a page can also get a `primary` prop which, when set, is the tag that gets shown in the TOC. + * - only 1 tag can be primary for available-space reasons. + * */ +function resolvePrimaryTag( + pageTags: RevisionPageTag[] | undefined, + context: GitBookSiteContext +): RevisionTag | undefined { + // TODO: simplify once new API lands as `tags` will always be defined. + if (!pageTags || pageTags.length === 0) { + return undefined; + } + + const primary = pageTags.find((t) => t.primary); + if (!primary) { + return undefined; + } + + const revisionTags = getRevisionTags(context.revision); + return resolveTag(primary.tag.tag, revisionTags); +} diff --git a/packages/gitbook/src/components/Tag.tsx b/packages/gitbook/src/components/Tag.tsx new file mode 100644 index 000000000..3e8343730 --- /dev/null +++ b/packages/gitbook/src/components/Tag.tsx @@ -0,0 +1,22 @@ +import { tcls } from '@/lib/tailwind'; +import type { RevisionTag } from '@gitbook/api'; + +/** + * Renders a tag, used to tag content like pages and blocks. + */ +export function Tag(props: { label: RevisionTag['label']; className?: string }) { + const { label, className } = props; + + return ( + + {label} + + ); +} diff --git a/packages/gitbook/src/lib/tags.ts b/packages/gitbook/src/lib/tags.ts new file mode 100644 index 000000000..d82a2d597 --- /dev/null +++ b/packages/gitbook/src/lib/tags.ts @@ -0,0 +1,32 @@ +import type { ContentRefTag, Revision, RevisionTag } from '@gitbook/api'; + +/** + * Get the tags defined on a revision. + */ +export function getRevisionTags(revision: Revision | undefined): RevisionTag[] { + // TODO: simplify once new API lands as `tags` will always be defined. + return revision?.tags ?? []; +} + +/** + * Resolve a tag slug to its full RevisionTag. + */ +export function resolveTag(slug: string, revisionTags: RevisionTag[]): RevisionTag | undefined { + return revisionTags.find((t) => t.slug === slug); +} + +/** + * Resolve an array of block-level tag references to their full RevisionTags. + */ +export function resolveBlockTags( + tags: ContentRefTag[] | undefined, + revisionTags: RevisionTag[] +): RevisionTag[] { + // TODO: simplify once new API lands as `tags` will always be defined. + if (!tags || tags.length === 0) { + return []; + } + return tags + .map((tag) => resolveTag(tag.tag, revisionTags)) + .filter((tag): tag is RevisionTag => tag !== undefined); +}