Skip to main content
Glama
markdownUtils.ts4.39 kB
import { marked } from 'marked'; import type { MarkdownProcessingOptions } from '../types/index.js'; import { MarkdownProcessingOptionsSchema } from '../types/index.js'; // Configure marked with safe defaults marked.setOptions({ gfm: true, breaks: true }); export function renderMarkdown( content: string, options: Partial<MarkdownProcessingOptions> = {} ): string { const opts = MarkdownProcessingOptionsSchema.parse(options); let processedContent = content; // Generate table of contents if requested if (opts.generateToc) { const toc = generateTableOfContents(content); if (toc) { processedContent = `${toc}\n\n${content}`; } } // Render markdown to HTML const html = marked(processedContent); // Handle both sync and async returns from marked if (html instanceof Promise) { throw new Error('Async marked processing not supported in this function. Use async version.'); } // Sanitize HTML if requested (basic sanitization) if (opts.sanitizeHtml) { return sanitizeHtml(html); } return html; } export function generateTableOfContents(content: string): string | null { const headings = extractHeadings(content); if (headings.length === 0) { return null; } const toc = headings .map(({ level, text, id }) => { const indent = ' '.repeat(level - 1); return `${indent}- [${text}](#${id})`; }) .join('\n'); return `## Table of Contents\n\n${toc}`; } export function extractHeadings(content: string): Array<{ level: number; text: string; id: string; }> { const headingRegex = /^(#{1,6})\s+(.+)$/gm; const headings: Array<{ level: number; text: string; id: string }> = []; let match; while ((match = headingRegex.exec(content)) !== null) { const level = match[1]?.length ?? 0; const text = match[2]?.trim() ?? ''; const id = text .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-'); headings.push({ level, text, id }); } return headings; } export function extractFrontmatter(content: string): { frontmatter: Record<string, unknown>; content: string; } { const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/; const match = content.match(frontmatterRegex); if (!match) { return { frontmatter: {}, content }; } try { // Simple YAML parsing for frontmatter const yamlContent = match[1] ?? ''; const frontmatter = parseSimpleYaml(yamlContent); const contentWithoutFrontmatter = content.slice(match[0].length); return { frontmatter, content: contentWithoutFrontmatter }; } catch (error) { console.warn('Failed to parse frontmatter:', error); return { frontmatter: {}, content }; } } // Basic HTML sanitization (you might want to use a proper library like DOMPurify) function sanitizeHtml(html: string): string { // Remove script tags and their content html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Remove on* attributes (onclick, onload, etc.) html = html.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, ''); // Remove javascript: protocols html = html.replace(/javascript:/gi, ''); return html; } // Simple YAML parser for basic frontmatter function parseSimpleYaml(yaml: string): Record<string, unknown> { const result: Record<string, unknown> = {}; const lines = yaml.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const colonIndex = trimmed.indexOf(':'); if (colonIndex === -1) continue; const key = trimmed.slice(0, colonIndex).trim(); let value: unknown = trimmed.slice(colonIndex + 1).trim(); // Basic type inference if (value === 'true') value = true; else if (value === 'false') value = false; else if (value === 'null') value = null; else if (typeof value === 'string' && /^\d+$/.test(value)) value = parseInt(value, 10); else if (typeof value === 'string' && /^\d+\.\d+$/.test(value)) value = parseFloat(value); else if (typeof value === 'string') { // Remove quotes if present if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } } result[key] = value; } return result; }

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/Talljack/content-manager-mcp'

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