// Integration for converting pasted spreadsheet tables to Markdown tables in EasyMDE/CodeMirror // Handles both HTML tables (from Google Sheets) and tab-separated values window.markdownTablePaste = { /** * Initializes paste handler for a MarkdownEditor instance. * Finds the textarea or CodeMirror instance created by EasyMDE and attaches paste event handler. * @param {string} editorId - The ID of the editor wrapper element, or null to find by class */ initialize: function(editorId) { let attempts = 0; const maxAttempts = 5; const tryInitialize = function() { attempts++; let textarea = null; let codeMirror = null; // Try to find the textarea element if (editorId) { const editorElement = document.getElementById(editorId); if (editorElement) { textarea = editorElement.querySelector('textarea'); } } else { // Find the most recently created EasyMDE textarea (last one in DOM) const containers = document.querySelectorAll('.EasyMDEContainer'); if (containers.length > 0) { const lastContainer = containers[containers.length - 1]; textarea = lastContainer.querySelector('textarea'); } // Fallback: try to find any textarea near an editor-toolbar if (!textarea) { const toolbars = document.querySelectorAll('.editor-toolbar'); if (toolbars.length > 0) { const lastToolbar = toolbars[toolbars.length - 1]; const nextSibling = lastToolbar.nextElementSibling; if (nextSibling && nextSibling.tagName === 'TEXTAREA') { textarea = nextSibling; } } } // Another fallback: find any textarea that's a child of a container with editor classes if (!textarea) { const allTextareas = document.querySelectorAll('textarea'); for (let ta of allTextareas) { const parent = ta.parentElement; if (parent && ( parent.classList.contains('EasyMDEContainer') || parent.classList.contains('editor') || parent.querySelector('.editor-toolbar') )) { textarea = ta; break; } } } } // If we found a textarea, try to get the CodeMirror instance from EasyMDE if (textarea) { if (window.EasyMDE) { const easyMDEInstances = window.EasyMDE.instances || []; for (let i = 0; i < easyMDEInstances.length; i++) { const instance = easyMDEInstances[i]; if (instance && instance.codemirror) { const cmTextarea = instance.codemirror.getTextArea(); if (cmTextarea === textarea) { codeMirror = instance.codemirror; break; } } } // Alternative: try to get CodeMirror from the textarea's parent if (!codeMirror && textarea.parentElement) { const parent = textarea.parentElement; if (parent._easyMDEInstance && parent._easyMDEInstance.codemirror) { codeMirror = parent._easyMDEInstance.codemirror; } } } const target = codeMirror || textarea; if (target) { // Add paste handler if (codeMirror) { codeMirror.on('paste', function(cm, event) { handleManualPaste(event, cm); }); } else if (textarea) { textarea.addEventListener('paste', function(event) { handleManualPaste(event, textarea); }, true); // Use capture phase to intercept early } return; // Success - we're done } } // If we didn't find the textarea, retry if (attempts < maxAttempts) { setTimeout(tryInitialize, attempts * 100); } }; // Start with initial delay to allow EasyMDE to initialize setTimeout(tryInitialize, 100); } }; /** * Handler for converting pasted spreadsheet tables to Markdown tables. * Handles both HTML tables (from Google Sheets) and tab-separated values. */ function handleManualPaste(event, target) { const clipboardData = event.clipboardData || window.clipboardData; if (!clipboardData) return; const html = clipboardData.getData('text/html'); const text = clipboardData.getData('text/plain'); let markdownTable = null; // Check if HTML contains a table if (html && html.includes('')) { markdownTable = convertHtmlTableToMarkdown(html); } // Check if it's tab-separated text (spreadsheet cells) else if (text && text.includes('\t') && text.includes('\n')) { const rows = text.trim().split(/\r?\n/).filter(row => row.trim().length > 0); if (rows.length < 1) return; const cells = rows.map(row => row.split('\t').map(cell => cell.trim())); const maxCols = Math.max(...cells.map(row => row.length)); // Pad rows to have same number of columns const paddedCells = cells.map(row => { while (row.length < maxCols) row.push(''); return row; }); // Escape pipe characters in cells const escapeCell = (cell) => cell.replace(/\|/g, '\\|'); // Build Markdown table const header = paddedCells[0]; const body = paddedCells.slice(1); const headerRow = '| ' + header.map(escapeCell).join(' | ') + ' |'; const separatorRow = '| ' + header.map(() => '---').join(' | ') + ' |'; const bodyRows = body.map(row => '| ' + row.map(escapeCell).join(' | ') + ' |'); markdownTable = [headerRow, separatorRow, ...bodyRows].join('\n') + '\n'; } // If we have a table to insert, do it if (markdownTable) { event.preventDefault(); event.stopPropagation(); // Insert into editor if (target.getDoc && target.getDoc().replaceRange) { // CodeMirror const doc = target.getDoc(); const cursor = doc.getCursor(); doc.replaceRange(markdownTable, cursor); } else if (target.setRangeText) { // Textarea with setRangeText const start = target.selectionStart; const end = target.selectionEnd; target.setRangeText(markdownTable, start, end, 'end'); target.dispatchEvent(new Event('input', { bubbles: true })); } else { // Fallback: insert at cursor const start = target.selectionStart || 0; const end = target.selectionEnd || 0; const before = target.value.substring(0, start); const after = target.value.substring(end); target.value = before + markdownTable + after; target.selectionStart = target.selectionEnd = before.length + markdownTable.length; target.dispatchEvent(new Event('input', { bubbles: true })); } } } /** * Converts an HTML table to Markdown table format */ function convertHtmlTableToMarkdown(html) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; const table = tempDiv.querySelector('table'); if (!table) return null; const rows = table.querySelectorAll('tr'); if (rows.length === 0) return null; const escapeCell = (cell) => { const text = cell.textContent || cell.innerText || ''; return text.trim().replace(/\|/g, '\\|').replace(/\n/g, ' '); }; const markdownRows = []; // Process header row (first row) const headerRow = rows[0]; const headerCells = headerRow.querySelectorAll('th, td'); const headerText = Array.from(headerCells).map(escapeCell); markdownRows.push('| ' + headerText.join(' | ') + ' |'); // Add separator markdownRows.push('| ' + headerText.map(() => '---').join(' | ') + ' |'); // Process data rows for (let i = 1; i < rows.length; i++) { const row = rows[i]; const cells = row.querySelectorAll('td, th'); const cellText = Array.from(cells).map(escapeCell); markdownRows.push('| ' + cellText.join(' | ') + ' |'); } return markdownRows.join('\n') + '\n'; }