Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

MarkdownRenderer.tsx6.6 kB
'use client'; import React from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeSanitize from 'rehype-sanitize'; interface MarkdownRendererProps { content: string; className?: string; /** * Maximum lines to show before truncation. Null = show all. */ maxLines?: number | null; } /** * Renders Markdown content safely with GitHub Flavored Markdown support. * * Features: * - GFM support (tables, strikethrough, task lists) * - XSS protection via sanitization * - Automatic link targets (open in new tab) * - Optional line truncation for previews * * @example * ```tsx * <MarkdownRenderer content="**Bold text**" /> * <MarkdownRenderer content={note} maxLines={3} /> * ``` */ export function MarkdownRenderer({ content, className = '', maxLines = null, }: MarkdownRendererProps) { if (!content || content.trim() === '') { return null; } // Truncate content if maxLines is specified const displayContent = maxLines !== null ? content.split('\n').slice(0, maxLines).join('\n') : content; const isTruncated = maxLines !== null && content.split('\n').length > maxLines; return ( <div className={`markdown-content ${className}`}> <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSanitize]} components={{ // Open links in new tab a: ({ node, ...props }) => ( <a {...props} target="_blank" rel="noopener noreferrer" /> ), // Style code blocks code: ({ node, ...props }: any) => props.inline ? ( <code className="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 rounded text-sm font-mono" {...props} /> ) : ( <code className="block p-3 bg-gray-100 dark:bg-gray-800 rounded-md text-sm font-mono overflow-x-auto" {...props} /> ), // Style blockquotes blockquote: ({ node, ...props }) => ( <blockquote className="border-l-4 border-gray-300 dark:border-gray-600 pl-4 italic my-2" {...props} /> ), // Style lists ul: ({ node, ...props }) => ( <ul className="list-disc list-inside my-2 space-y-1" {...props} /> ), ol: ({ node, ...props }) => ( <ol className="list-decimal list-inside my-2 space-y-1" {...props} /> ), // Style headings h1: ({ node, ...props }) => ( <h1 className="text-2xl font-bold my-3" {...props} /> ), h2: ({ node, ...props }) => ( <h2 className="text-xl font-bold my-2.5" {...props} /> ), h3: ({ node, ...props }) => ( <h3 className="text-lg font-semibold my-2" {...props} /> ), h4: ({ node, ...props }) => ( <h4 className="text-base font-semibold my-1.5" {...props} /> ), // Style paragraphs p: ({ node, ...props }) => <p className="my-2" {...props} />, // Style tables (GFM) table: ({ node, ...props }) => ( <div className="overflow-x-auto my-3"> <table className="min-w-full border-collapse border border-gray-300 dark:border-gray-600" {...props} /> </div> ), th: ({ node, ...props }) => ( <th className="border border-gray-300 dark:border-gray-600 px-3 py-2 bg-gray-50 dark:bg-gray-700 font-semibold text-left" {...props} /> ), td: ({ node, ...props }) => ( <td className="border border-gray-300 dark:border-gray-600 px-3 py-2" {...props} /> ), }} > {displayContent} </ReactMarkdown> {isTruncated && ( <div className="text-sm text-gray-500 dark:text-gray-400 mt-1"> ... </div> )} <style jsx global>{` .markdown-content { font-size: 0.9375rem; line-height: 1.6; color: inherit; } .markdown-content > *:first-child { margin-top: 0; } .markdown-content > *:last-child { margin-bottom: 0; } .markdown-content a { color: #dc143c; text-decoration: underline; text-underline-offset: 2px; } .markdown-content a:hover { color: #a00f2e; } .markdown-content strong { font-weight: 600; } .markdown-content em { font-style: italic; } .markdown-content hr { border: none; border-top: 1px solid #e5e7eb; margin: 1.5rem 0; } .dark .markdown-content hr { border-top-color: #374151; } /* Task lists (GFM) */ .markdown-content input[type='checkbox'] { margin-right: 0.5rem; } /* Strikethrough (GFM) */ .markdown-content del { text-decoration: line-through; opacity: 0.7; } `}</style> </div> ); } /** * Get a plain text preview from markdown content * Strips all markdown formatting */ export function getMarkdownPreview(content: string, maxLength: number = 100): string { if (!content) return ''; // Remove markdown formatting const stripped = content .replace(/#{1,6}\s/g, '') // Remove headings .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold .replace(/\*(.+?)\*/g, '$1') // Remove italic .replace(/__(.+?)__/g, '$1') // Remove bold alt .replace(/_(.+?)_/g, '$1') // Remove italic alt .replace(/~~(.+?)~~/g, '$1') // Remove strikethrough .replace(/`(.+?)`/g, '$1') // Remove inline code .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links .replace(/!\[(.+?)\]\(.+?\)/g, '$1') // Remove images .replace(/>\s/g, '') // Remove blockquotes .replace(/[-*+]\s/g, '') // Remove list bullets .replace(/\d+\.\s/g, '') // Remove ordered list numbers .trim(); // Truncate to max length if (stripped.length > maxLength) { return stripped.slice(0, maxLength) + '...'; } return stripped; } /** * Count characters in markdown content (excluding formatting) */ export function getMarkdownCharCount(content: string): number { return content.length; // For tier limits, we count all characters including formatting }

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/northernvariables/FedMCP'

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