Skip to main content
Glama
MarkDownRender.tsx12.7 kB
import type { LocalesValues } from '@intlayer/types'; import type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react'; import type { BundledLanguage } from 'shiki/bundle/web'; import { cn } from '../../utils/cn'; import { H1, H2, H3, H4, H5, H6 } from '../Headers'; import type { CodeCompAttributes } from '../IDE/Code'; import { Code } from '../IDE/Code'; import { CodeProvider } from '../IDE/CodeContext'; import { Link } from '../Link'; import { Tab } from '../Tab'; import { TabProvider } from '../Tab/TabContext'; import { Table } from '../Table'; import { MarkdownProcessor, type MarkdownProcessorOptions } from './processor'; type MarkdownRendererProps = { children: string; isDarkMode?: boolean; locale?: LocalesValues; options?: MarkdownProcessorOptions; }; /** * Removes frontmatter from markdown content * Frontmatter is the YAML metadata block at the beginning of markdown files * delimited by --- at the start and end */ const stripFrontmatter = (markdown: string): string => { const lines = markdown.split(/\r?\n/); // Check if the very first non-empty line is the metadata start delimiter const firstNonEmptyLine = lines.find((line) => line.trim() !== ''); if (!firstNonEmptyLine || firstNonEmptyLine.trim() !== '---') { // No frontmatter, return original content return markdown; } let inMetadataBlock = false; let endOfMetadataIndex = -1; for (let i = 0; i < lines.length; i++) { const trimmedLine = lines[i].trim(); // Toggle metadata block on encountering the delimiter if (trimmedLine === '---') { if (!inMetadataBlock) { // Begin metadata block inMetadataBlock = true; } else { // End of metadata block endOfMetadataIndex = i; break; } } } if (endOfMetadataIndex > -1) { // Return content after the frontmatter return lines.slice(endOfMetadataIndex + 1).join('\n'); } // If we couldn't find the end delimiter, return original content return markdown; }; /** * MarkdownRenderer Component * * A comprehensive markdown renderer that transforms markdown text into rich, * interactive HTML with custom styling and Intlayer integration. Supports * code syntax highlighting, responsive tables, internationalized links, * and automatic frontmatter stripping. * * @component * @example * Basic usage: * ```tsx * const markdownContent = ` * # Hello World * This is **bold** and *italic* text. * `; * * <MarkdownRenderer>{markdownContent}</MarkdownRenderer> * ``` * * @example * With dark mode: * ```tsx * const codeExample = ` * # API Documentation * \`\`\`javascript * const response = await fetch('/api/data'); * const data = await response.json(); * \`\`\` * `; * * <MarkdownRenderer isDarkMode={true}> * {codeExample} * </MarkdownRenderer> * ``` * * @example * With internationalized links: * ```tsx * const content = ` * Visit our [documentation](/docs) for more information. * External link: [Google](https://google.com) * `; * * <MarkdownRenderer locale="fr"> * {content} * </MarkdownRenderer> * ``` * * @example * With custom overrides: * ```tsx * const customOptions = { * overrides: { * h1: ({ children }) => ( * <h1 className="custom-title">{children}</h1> * ), * }, * }; * * <MarkdownRenderer options={customOptions}> * {markdownContent} * </MarkdownRenderer> * ``` * * @example * With frontmatter (automatically stripped): * ```tsx * const blogPost = ` * --- * title: "My Blog Post" * date: "2023-12-01" * author: "John Doe" * --- * # My Blog Post * This content will be rendered without the frontmatter. * `; * * <MarkdownRenderer>{blogPost}</MarkdownRenderer> * ``` * * Features: * - Automatic frontmatter detection and removal * - Syntax-highlighted code blocks with theme support * - Clickable headers with anchor links * - Internationalized link handling with locale awareness * - Responsive tables with hover effects * - Custom blockquote, list, and image styling * - External link detection and security attributes * - Inline code with distinctive styling * - Lazy-loaded images with GitHub raw URL support * - Horizontal rules with custom styling * * Supported Markdown Elements: * - Headers (h1-h4) with click-to-anchor functionality * - Code blocks with language-specific syntax highlighting * - Inline code with background styling * - Blockquotes with custom border and padding * - Ordered and unordered lists with custom spacing * - Links (internal and external) with security attributes * - Images with lazy loading and responsive sizing * - Tables with hover effects and proper styling * - Horizontal rules with custom appearance * - All standard markdown formatting (bold, italic, etc.) * * Code Block Support: * - Language detection from code fence info * - Syntax highlighting through Code component * - Dark/light mode theme switching * - Line numbers and copy functionality (via Code component) * - Support for popular languages (JS, TS, Python, etc.) * * Link Handling: * - Automatic external link detection (starts with 'http') * - Security attributes for external links (rel="noopener noreferrer") * - Locale-aware internal link routing * - Custom Link component integration * - Underlined styling for better visibility * * Image Processing: * - Automatic lazy loading for performance * - GitHub raw URL transformation for repository images * - Responsive sizing with max-width constraints * - Rounded corners for visual appeal * * Accessibility: * - Semantic HTML structure with proper heading hierarchy * - ARIA attributes through component integration * - Screen reader friendly link descriptions * - Keyboard navigation support * - High contrast styling options * * Performance: * - Lazy loading for images * - Efficient re-rendering with React memo patterns * - Code syntax highlighting with minimal bundle impact * - Optimized markdown parsing with markdown-to-jsx * * @param props - Component props * @param props.children - Raw markdown content to render * @param props.isDarkMode - Enable dark mode styling for code blocks * @param props.locale - Current locale for link internationalization * @param props.options - Additional markdown-to-jsx options * @param props.options.overrides - Custom component overrides for markdown elements * * @returns Rendered markdown content with custom styling and functionality */ export const MarkdownRenderer: FC<MarkdownRendererProps> = ({ children, isDarkMode, locale, options, }) => { const { overrides, ...restOptions } = options ?? {}; // Strip frontmatter from the markdown content before rendering const cleanMarkdown = stripFrontmatter(children); return ( <CodeProvider> <TabProvider> <MarkdownProcessor options={{ overrides: { h1: (props: ComponentProps<typeof H1>) => ( <H1 isClickable={true} {...props} /> ), h2: (props: ComponentProps<typeof H2>) => ( <H2 isClickable={true} className="mt-16" {...props} /> ), h3: (props: ComponentProps<typeof H3>) => ( <H3 isClickable={true} className="mt-5" {...props} /> ), h4: (props: ComponentProps<typeof H4>) => ( <H4 isClickable={true} className="mt-3" {...props} /> ), h5: (props: ComponentProps<typeof H5>) => ( <H5 isClickable={true} className="mt-3" {...props} /> ), h6: (props: ComponentProps<typeof H6>) => ( <H6 isClickable={true} className="mt-3" {...props} /> ), code: ( props: Omit<ComponentPropsWithoutRef<'code'>, 'children'> & Partial<CodeCompAttributes> & { children: string } ) => !props.className ? ( <strong className="rounded bg-card/60 box-decoration-clone px-1 py-0.5"> {props.children} </strong> ) : ( <Code {...props} isDarkMode={isDarkMode} language={ (props.className?.replace('lang-', '') || 'plaintext') as BundledLanguage } fileName={props.fileName} showHeader={Boolean(props.fileName)} /> ), blockquote: ({ className, ...props }: ComponentPropsWithoutRef<'blockquote'>) => ( <blockquote className={cn( 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral', className )} {...props} /> ), ul: ({ className, ...props }: ComponentPropsWithoutRef<'ul'>) => ( <ul className={cn( 'mt-5 flex list-disc flex-col gap-3 pl-5', className )} {...props} /> ), ol: ({ className, ...props }: ComponentPropsWithoutRef<'ol'>) => ( <ol className={cn( 'mt-5 flex list-decimal flex-col gap-3 pl-5', className )} {...props} /> ), img: ({ className, ...props }: ComponentPropsWithoutRef<'img'>) => ( <img {...props} loading="lazy" className={cn( 'max-h-[80vh] max-w-full rounded-md', className )} src={`${props.src}?raw=true`} /> ), a: (props: ComponentProps<typeof Link>) => ( <Link color="neutral" isExternalLink={props.href?.startsWith('http')} underlined={true} locale={locale} {...props} /> ), pre: (props: ComponentPropsWithoutRef<'pre'>) => props.children, table: (props: ComponentProps<typeof Table>) => ( <Table isRollable={true} {...props} /> ), th: ({ className, ...props }: ComponentPropsWithoutRef<'th'>) => ( <th className={cn( 'border-neutral border-b bg-neutral/10 p-4', className )} {...props} /> ), tr: ({ className, ...props }: ComponentPropsWithoutRef<'tr'>) => ( <tr className={cn('hover:/10 hover:bg-neutral/10', className)} {...props} /> ), td: ({ className, ...props }: ComponentPropsWithoutRef<'td'>) => ( <td className={cn( 'border-neutral-500/50 border-b p-4', className )} {...props} /> ), hr: ({ className, ...props }: ComponentPropsWithoutRef<'hr'>) => ( <hr className={cn('mx-6 mt-16 text-neutral', className)} {...props} /> ), // Support both <Tab> and <Tabs> in markdown Tabs: (props: ComponentProps<typeof Tab>) => <Tab {...props} />, Tab: (props: ComponentProps<typeof Tab>) => <Tab {...props} />, TabItem: (props: ComponentProps<typeof Tab.Item>) => ( <Tab.Item {...props} /> ), Columns: ({ className, ...props }: ComponentPropsWithoutRef<'div'>) => ( <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} /> ), Column: ({ className, ...props }: ComponentPropsWithoutRef<'div'>) => ( <div className={cn('flex-1', className)} {...props} /> ), ...overrides, }, ...restOptions, }} > {cleanMarkdown ?? ''} </MarkdownProcessor> </TabProvider> </CodeProvider> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server