Files
chapter-organizer/WebApp/wwwroot/js/markdownTablePaste.js
T

258 lines
9.5 KiB
JavaScript

// 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 = {
findEditor: function(editorId) {
let textarea = null;
let codeMirror = null;
if (editorId) {
const editorElement = document.getElementById(editorId);
if (editorElement) {
textarea = editorElement.querySelector('textarea');
const cmEl = editorElement.querySelector('.CodeMirror');
if (cmEl && cmEl.CodeMirror) {
codeMirror = cmEl.CodeMirror;
}
}
} else {
const containers = document.querySelectorAll('.EasyMDEContainer');
if (containers.length > 0) {
const lastContainer = containers[containers.length - 1];
textarea = lastContainer.querySelector('textarea');
const cmEl = lastContainer.querySelector('.CodeMirror');
if (cmEl && cmEl.CodeMirror) {
codeMirror = cmEl.CodeMirror;
}
}
}
if (!codeMirror && textarea && 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;
}
}
}
if (!codeMirror && textarea.parentElement) {
const parent = textarea.parentElement;
if (parent._easyMDEInstance && parent._easyMDEInstance.codemirror) {
codeMirror = parent._easyMDEInstance.codemirror;
}
}
}
return { textarea, codeMirror };
},
getValue: function(editorId) {
const found = this.findEditor(editorId);
if (found.codeMirror) {
return found.codeMirror.getValue();
}
if (found.textarea) {
return found.textarea.value;
}
return null;
},
setValue: function(editorId, text) {
const found = this.findEditor(editorId);
if (found.codeMirror) {
found.codeMirror.setValue(text ?? '');
return true;
}
if (found.textarea) {
found.textarea.value = text ?? '';
found.textarea.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}
return false;
},
insertAtCursor: function(editorId, text) {
const found = this.findEditor(editorId);
if (found.codeMirror) {
found.codeMirror.replaceSelection(text);
found.codeMirror.focus();
return true;
}
if (found.textarea) {
const start = found.textarea.selectionStart || 0;
const end = found.textarea.selectionEnd || 0;
const before = found.textarea.value.substring(0, start);
const after = found.textarea.value.substring(end);
found.textarea.value = before + text + after;
found.textarea.selectionStart = found.textarea.selectionEnd = before.length + text.length;
found.textarea.dispatchEvent(new Event('input', { bubbles: true }));
found.textarea.focus();
return true;
}
return false;
},
/**
* 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++;
const found = window.markdownTablePaste.findEditor(editorId);
const textarea = found.textarea;
const codeMirror = found.codeMirror;
if (textarea || 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('<table') && html.includes('</table>')) {
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';
}