{title}
)}{operation.path}
{operation.deprecated && (
{description}
)}import { Link } from 'react-router';
import { ContextualMenu } from '@/components/ContextualMenu';
import { Badge } from '@/components/docs/Badge';
import { ArrowLeft, ArrowRight, FileText } from '@/components/icons';
import { OpenApiOperation } from '@/components/OpenApiOperation';
import { getLastModified } from '@/lib/content';
import { getScopeForPage } from '@/lib/docs-config';
import { resolveLocale } from '@/lib/locale';
import { getOperation, methodColor } from '@/lib/openapi';
import { docsSite, getPageByPathname } from '@/lib/site-config';
import { resolveContextualOptions } from '@/lib/site-model';
import type { DocModule, NormalizedDocsPage, RelatedEntry, SiteModel } from '@/lib/types';
import { cn } from '@/lib/utils';
interface RelatedLink {
href: string;
title: string;
external: boolean;
}
/**
* Related-topics entries from frontmatter. Bare paths resolve their title from
* the docs registry; unknown internal paths without an explicit title are
* skipped so dead links never render.
*/
function resolveRelated(entries: unknown): RelatedLink[] {
if (!Array.isArray(entries)) {
return [];
}
const links: RelatedLink[] = [];
for (const entry of entries as RelatedEntry[]) {
const href = typeof entry === 'string' ? entry : entry?.href;
if (!href || typeof href !== 'string') {
continue;
}
const external = !href.startsWith('/');
const explicitTitle = typeof entry === 'object' ? entry.title : undefined;
const title = explicitTitle || (external ? href : getPageByPathname(href)?.label);
if (!title) {
if (import.meta.env.DEV) {
console.warn(
`[shiso] Related topic "${href}" does not match a page and has no title — skipped.`,
);
}
continue;
}
links.push({ href, title, external });
}
return links;
}
export interface DocContentProps {
page: NormalizedDocsPage;
doc: DocModule;
site: SiteModel;
}
export function DocContent({ page, doc, site }: DocContentProps) {
const scope = getScopeForPage(docsSite, page);
// Prev/next paging never crosses a version or language boundary.
const pagerPages = scope.docs.pages.filter(item => !item.hidden);
const pageIndex = pagerPages.findIndex(item => item.slug === page.slug);
const prev = pageIndex > 0 ? pagerPages[pageIndex - 1] : undefined;
const next = pageIndex >= 0 ? pagerPages[pageIndex + 1] : undefined;
const title = doc.frontmatter?.title || page.label;
const description = doc.frontmatter?.description;
const Content = doc.default;
const shouldShowTimestamp =
typeof doc.frontmatter?.timestamp === 'boolean'
? doc.frontmatter.timestamp
: site.showTimestamp;
const lastModified = shouldShowTimestamp ? getLastModified(page.filePath) : undefined;
// `styling.eyebrows`: the section name alone, or the full navigation path.
const eyebrow =
site.styling.eyebrows === 'breadcrumbs'
? [...new Set([page.tabLabel, page.section])].filter(Boolean).join(' / ')
: page.section;
const contextualOptions = resolveContextualOptions(site.contextualOptions, page, site.labels);
const related = resolveRelated(doc.frontmatter?.related);
const operation = getOperation(doc.frontmatter?.openapi);
// Dates follow the page's language when it is a valid locale code.
const dateFormat = new Intl.DateTimeFormat(resolveLocale(page.language, site.locale), {
dateStyle: 'medium',
timeZone: 'UTC',
});
// Pagefind indexing markers on the prerendered HTML. Inert unless the site
// uses the pagefind provider. Hidden pages/scopes mirror the local index's
// search visibility rules; the scope filter matches the id Search.tsx sends.
const pagefindAttrs =
page.hidden || scope.hidden
? {}
: {
'data-pagefind-body': '',
...(page.scopeId !== 'default'
? { 'data-pagefind-filter': 'scope[data-scope]', 'data-scope': page.scopeId }
: {}),
};
return (
{description}
{title}
)}
{operation.path}
{operation.deprecated && (