// Shared Markdown Preview Modal Component for Crawl4AI Assistant // Used by both SchemaBuilder and Click2CrawlBuilder class MarkdownPreviewModal { constructor(options = {}) { this.modal = null; this.markdownOptions = { includeImages: true, preserveTables: true, keepCodeFormatting: true, simplifyLayout: false, preserveLinks: true, addSeparators: true, includeXPath: false, textOnly: false, ...options }; this.onGenerateMarkdown = null; this.currentMarkdown = ''; } show(generateMarkdownCallback) { this.onGenerateMarkdown = generateMarkdownCallback; if (!this.modal) { this.createModal(); } // Generate initial markdown this.updateContent(); this.modal.style.display = 'block'; } hide() { if (this.modal) { this.modal.style.display = 'none'; } } createModal() { this.modal = document.createElement('div'); this.modal.className = 'c4ai-c2c-preview'; this.modal.innerHTML = `
Markdown Preview
`; document.body.appendChild(this.modal); // Make modal draggable if (window.C4AI_Utils && window.C4AI_Utils.makeDraggable) { window.C4AI_Utils.makeDraggable(this.modal); } // Position preview modal this.modal.style.position = 'fixed'; this.modal.style.top = '50%'; this.modal.style.left = '50%'; this.modal.style.transform = 'translate(-50%, -50%)'; this.modal.style.zIndex = '999999'; this.setupEventListeners(); } setupEventListeners() { // Close button this.modal.querySelector('.c4ai-preview-close').addEventListener('click', () => { this.hide(); }); // Tab switching this.modal.querySelectorAll('.c4ai-tab').forEach(tab => { tab.addEventListener('click', (e) => { const tabName = e.target.dataset.tab; this.switchTab(tabName); }); }); // Wrap toggle const wrapToggle = this.modal.querySelector('.c4ai-wrap-toggle'); wrapToggle.addEventListener('click', () => { const panes = this.modal.querySelectorAll('.c4ai-preview-pane'); panes.forEach(pane => { pane.classList.toggle('wrap'); }); wrapToggle.classList.toggle('active'); }); // Options change this.modal.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { checkbox.addEventListener('change', async (e) => { this.markdownOptions[e.target.name] = e.target.checked; // Handle text-only mode dependencies if (e.target.name === 'textOnly' && e.target.checked) { const preserveLinksCheckbox = this.modal.querySelector('input[name="preserveLinks"]'); if (preserveLinksCheckbox) { preserveLinksCheckbox.checked = false; preserveLinksCheckbox.disabled = true; this.markdownOptions.preserveLinks = false; } const includeImagesCheckbox = this.modal.querySelector('input[name="includeImages"]'); if (includeImagesCheckbox) { includeImagesCheckbox.disabled = true; } } else if (e.target.name === 'textOnly' && !e.target.checked) { // Re-enable options when text-only is disabled const preserveLinksCheckbox = this.modal.querySelector('input[name="preserveLinks"]'); if (preserveLinksCheckbox) { preserveLinksCheckbox.disabled = false; } const includeImagesCheckbox = this.modal.querySelector('input[name="includeImages"]'); if (includeImagesCheckbox) { includeImagesCheckbox.disabled = false; } } // Update markdown content await this.updateContent(); }); }); // Action buttons this.modal.querySelector('.c4ai-copy-markdown-btn').addEventListener('click', () => { this.copyToClipboard(); }); this.modal.querySelector('.c4ai-download-btn').addEventListener('click', () => { this.downloadMarkdown(); }); } switchTab(tabName) { // Update active tab this.modal.querySelectorAll('.c4ai-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === tabName); }); // Update active pane this.modal.querySelectorAll('.c4ai-preview-pane').forEach(pane => { pane.classList.toggle('active', pane.dataset.pane === tabName); }); } async updateContent() { if (!this.onGenerateMarkdown) return; try { // Generate markdown with current options this.currentMarkdown = await this.onGenerateMarkdown(this.markdownOptions); // Update markdown pane const markdownPane = this.modal.querySelector('[data-pane="markdown"]'); markdownPane.innerHTML = `
${this.escapeHtml(this.currentMarkdown)}
`; // Update preview pane const previewPane = this.modal.querySelector('[data-pane="preview"]'); // Use marked.js if available if (window.marked) { marked.setOptions({ gfm: true, breaks: true, tables: true, headerIds: false, mangle: false }); const html = marked.parse(this.currentMarkdown); previewPane.innerHTML = `
${html}
`; } else { // Fallback previewPane.innerHTML = `
${this.escapeHtml(this.currentMarkdown)}
`; } } catch (error) { console.error('Error generating markdown:', error); this.showNotification('Error generating markdown', 'error'); } } async copyToClipboard() { try { await navigator.clipboard.writeText(this.currentMarkdown); this.showNotification('Markdown copied to clipboard!'); } catch (err) { console.error('Failed to copy:', err); this.showNotification('Failed to copy. Please try again.', 'error'); } } async downloadMarkdown() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const filename = `crawl4ai-export-${timestamp}.md`; // Create blob and download const blob = new Blob([this.currentMarkdown], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showNotification(`Downloaded ${filename}`); } showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.className = `c4ai-notification c4ai-notification-${type}`; notification.textContent = message; document.body.appendChild(notification); // Animate in setTimeout(() => notification.classList.add('show'), 10); // Remove after 3 seconds setTimeout(() => { notification.classList.remove('show'); setTimeout(() => notification.remove(), 300); }, 3000); } escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Get current options getOptions() { return { ...this.markdownOptions }; } // Update options programmatically setOptions(options) { this.markdownOptions = { ...this.markdownOptions, ...options }; // Update checkboxes to reflect new options Object.entries(options).forEach(([key, value]) => { const checkbox = this.modal?.querySelector(`input[name="${key}"]`); if (checkbox && typeof value === 'boolean') { checkbox.checked = value; } }); } // Cleanup destroy() { if (this.modal) { this.modal.remove(); this.modal = null; } this.onGenerateMarkdown = null; } } // Export for use in other scripts if (typeof window !== 'undefined') { window.MarkdownPreviewModal = MarkdownPreviewModal; }