/** * Insertr Markdown Processor Module * Handles markdown parsing and rendering with sanitization */ class InsertrMarkdownProcessor { constructor() { this.marked = null; this.markedParser = null; this.DOMPurify = null; this.initialize(); } /** * Initialize markdown processor */ initialize() { // Check if marked is available if (typeof marked === 'undefined' && typeof window.marked === 'undefined') { console.warn('Marked.js not loaded! Markdown collections will not work.'); return false; } // Get the marked object this.marked = window.marked || marked; this.markedParser = this.marked.marked || this.marked.parse || this.marked; if (typeof this.markedParser !== 'function') { console.warn('Cannot find marked parse function'); return false; } // Configure marked for basic use if (this.marked.use) { this.marked.use({ breaks: true, gfm: true }); } // Initialize DOMPurify if available if (typeof DOMPurify !== 'undefined' || typeof window.DOMPurify !== 'undefined') { this.DOMPurify = window.DOMPurify || DOMPurify; } console.log('✅ Markdown processor initialized'); return true; } /** * Check if markdown processor is ready * @returns {boolean} Ready status */ isReady() { return this.markedParser !== null; } /** * Render markdown to HTML * @param {string} markdownText - Markdown content * @returns {string} Rendered HTML */ renderToHtml(markdownText) { if (!this.markedParser) { console.error('Markdown parser not available'); return markdownText; } if (typeof markdownText !== 'string') { console.error('Expected markdown string, got:', typeof markdownText, markdownText); return 'Markdown content error'; } try { // Convert markdown to HTML const html = this.markedParser(markdownText); if (typeof html !== 'string') { console.error('Markdown parser returned non-string:', typeof html, html); return 'Markdown parsing error'; } // Sanitize HTML if DOMPurify is available if (this.DOMPurify) { return this.DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'strong', 'em', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'br'], ALLOWED_ATTR: ['href', 'class'], ALLOWED_SCHEMES: ['http', 'https', 'mailto'] }); } return html; } catch (error) { console.error('Markdown rendering failed:', error); return markdownText; } } /** * Apply markdown content to DOM element * @param {HTMLElement} element - Element to update * @param {string} markdownText - Markdown content */ applyToElement(element, markdownText) { const html = this.renderToHtml(markdownText); element.innerHTML = html; } /** * Create markdown preview * @param {string} markdownText - Markdown content * @returns {string} HTML preview */ createPreview(markdownText) { return this.renderToHtml(markdownText); } /** * Validate markdown content * @param {string} markdownText - Markdown to validate * @returns {Object} Validation result */ validateMarkdown(markdownText) { try { const html = this.renderToHtml(markdownText); return { valid: true, html, warnings: this.getMarkdownWarnings(markdownText) }; } catch (error) { return { valid: false, message: 'Invalid markdown format', error: error.message }; } } /** * Get warnings for markdown content * @param {string} markdownText - Markdown to check * @returns {Array} Array of warnings */ getMarkdownWarnings(markdownText) { const warnings = []; // Check for potentially problematic patterns if (markdownText.includes(' 10) { warnings.push('Many headers detected - consider simplifying structure'); } return warnings; } /** * Strip markdown formatting to plain text * @param {string} markdownText - Markdown content * @returns {string} Plain text */ toPlainText(markdownText) { return markdownText .replace(/#{1,6}\s+/g, '') // Remove headers .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold .replace(/\*(.*?)\*/g, '$1') // Remove italic .replace(/\[(.*?)\]\(.*?\)/g, '$1') // Remove links, keep text .replace(/`(.*?)`/g, '$1') // Remove code .replace(/^\s*[-*+]\s+/gm, '') // Remove list markers .replace(/^\s*\d+\.\s+/gm, '') // Remove numbered list markers .replace(/\n\n+/g, '\n\n') // Normalize whitespace .trim(); } } // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = InsertrMarkdownProcessor; } // Global export for browser usage if (typeof window !== 'undefined') { window.InsertrMarkdownProcessor = InsertrMarkdownProcessor; }