Skip to main content
Glama
prosemirror-converter.ts3.62 kB
/** * Convert ProseMirror JSON to Markdown * Based on the converter from Joseph Thacker's blog post */ export interface ProseMirrorNode { type: string; content?: ProseMirrorNode[]; text?: string; attrs?: Record<string, any>; marks?: Array<{ type: string; attrs?: Record<string, any> }>; } export function convertProseMirrorToMarkdown( content: ProseMirrorNode | null | undefined ): string { if (!content || typeof content !== "object" || !content.content) { return ""; } function processNode(node: ProseMirrorNode): string { if (!node || typeof node !== "object") { return ""; } const nodeType = node.type || ""; const nodeContent = node.content || []; const text = node.text || ""; switch (nodeType) { case "heading": { const level = node.attrs?.level || 1; const headingText = nodeContent.map(processNode).join("").trim(); return `${"#".repeat(level)} ${headingText}\n\n`; } case "paragraph": { const paraText = nodeContent.map(processNode).join(""); return paraText ? `${paraText}\n\n` : "\n"; } case "bulletList": { const items: string[] = []; for (const item of nodeContent) { if (item.type === "listItem") { const itemContent = (item.content || []) .map(processNode) .join("") .trim(); if (itemContent) { items.push(`- ${itemContent}`); } } } return items.length > 0 ? items.join("\n") + "\n\n" : ""; } case "orderedList": { const items: string[] = []; for (let i = 0; i < nodeContent.length; i++) { const item = nodeContent[i]; if (item.type === "listItem") { const itemContent = (item.content || []) .map(processNode) .join("") .trim(); if (itemContent) { items.push(`${i + 1}. ${itemContent}`); } } } return items.length > 0 ? items.join("\n") + "\n\n" : ""; } case "listItem": { return nodeContent.map(processNode).join(""); } case "text": { let textContent = text; if (node.marks) { for (const mark of node.marks) { switch (mark.type) { case "bold": textContent = `**${textContent}**`; break; case "italic": textContent = `*${textContent}*`; break; case "code": textContent = `\`${textContent}\``; break; case "link": const href = mark.attrs?.href || ""; textContent = `[${textContent}](${href})`; break; } } } return textContent; } case "hardBreak": return "\n"; case "codeBlock": { const codeContent = nodeContent.map(processNode).join(""); const language = node.attrs?.language || ""; return `\`\`\`${language}\n${codeContent}\n\`\`\`\n\n`; } case "blockquote": { const quoteContent = nodeContent .map(processNode) .join("") .trim() .split("\n") .map((line) => `> ${line}`) .join("\n"); return `${quoteContent}\n\n`; } default: // For unknown node types, just process their content return nodeContent.map(processNode).join(""); } } return processNode(content).trim(); }

Implementation Reference

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/btn0s/granola-mcp'

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