Files
markdown-converter/src/renderer.js
T
amitwhandClaude b0a96f680a Release v1.2.0: Major feature additions
- Add PowerPoint export (PPTX/ODP) with slide-level formatting
- Add comprehensive document conversion menu with import/export
- Add interactive table creation helper in markdown editor
- Update documentation with all new features and version history
- Improve menu organization for better user experience

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 19:54:19 +05:30

278 lines
8.0 KiB
JavaScript

const { ipcRenderer } = require('electron');
const marked = require('marked');
const DOMPurify = require('dompurify');
const hljs = require('highlight.js');
// Configure marked
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (err) {}
}
return hljs.highlightAuto(code).value;
},
breaks: true,
gfm: true
});
// Elements
const editor = document.getElementById('editor');
const preview = document.getElementById('preview');
const previewPane = document.getElementById('preview-pane');
const editorPane = document.getElementById('editor-pane');
const statusText = document.getElementById('status-text');
const wordCount = document.getElementById('word-count');
// State
let isPreviewVisible = true;
let currentContent = '';
let isDirty = false;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Request current theme
ipcRenderer.send('get-theme');
// Set up auto-save interval
setInterval(autoSave, 30000); // Auto-save every 30 seconds
// Initialize with empty content
updatePreview();
updateWordCount();
});
// Editor input handler
editor.addEventListener('input', () => {
currentContent = editor.value;
isDirty = true;
updatePreview();
updateWordCount();
updateStatus('Modified');
});
// Toolbar button handlers
document.getElementById('btn-bold').addEventListener('click', () => insertMarkdown('**', '**'));
document.getElementById('btn-italic').addEventListener('click', () => insertMarkdown('*', '*'));
document.getElementById('btn-heading').addEventListener('click', () => insertMarkdown('## ', ''));
document.getElementById('btn-link').addEventListener('click', () => insertMarkdown('[', '](url)'));
document.getElementById('btn-code').addEventListener('click', () => insertMarkdown('`', '`'));
document.getElementById('btn-list').addEventListener('click', () => insertMarkdown('- ', ''));
document.getElementById('btn-quote').addEventListener('click', () => insertMarkdown('> ', ''));
document.getElementById('btn-table').addEventListener('click', insertTable);
document.getElementById('btn-preview-toggle').addEventListener('click', togglePreview);
// Table insertion function
function insertTable() {
const rows = prompt('Number of rows:', '3');
const cols = prompt('Number of columns:', '3');
if (!rows || !cols) return;
const numRows = parseInt(rows);
const numCols = parseInt(cols);
if (isNaN(numRows) || isNaN(numCols) || numRows < 1 || numCols < 1) {
alert('Please enter valid numbers for rows and columns');
return;
}
let table = '\n';
// Header row
table += '|';
for (let j = 0; j < numCols; j++) {
table += ` Header ${j + 1} |`;
}
table += '\n';
// Separator row
table += '|';
for (let j = 0; j < numCols; j++) {
table += ' --- |';
}
table += '\n';
// Data rows
for (let i = 0; i < numRows; i++) {
table += '|';
for (let j = 0; j < numCols; j++) {
table += ` Cell ${i + 1}-${j + 1} |`;
}
table += '\n';
}
table += '\n';
// Insert table at cursor position
const start = editor.selectionStart;
const end = editor.selectionEnd;
editor.value = editor.value.substring(0, start) + table + editor.value.substring(end);
// Set cursor after the table
editor.selectionStart = editor.selectionEnd = start + table.length;
editor.focus();
// Trigger input event
editor.dispatchEvent(new Event('input'));
}
// Markdown insertion helper
function insertMarkdown(before, after) {
const start = editor.selectionStart;
const end = editor.selectionEnd;
const selectedText = editor.value.substring(start, end);
const replacement = before + (selectedText || 'text') + after;
editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
// Set cursor position
if (selectedText) {
editor.selectionStart = start;
editor.selectionEnd = start + replacement.length;
} else {
editor.selectionStart = start + before.length;
editor.selectionEnd = start + before.length + 4; // Select "text"
}
editor.focus();
// Trigger input event
editor.dispatchEvent(new Event('input'));
}
// Update preview
function updatePreview() {
const html = marked.parse(editor.value);
const clean = DOMPurify.sanitize(html);
preview.innerHTML = clean;
// Re-highlight code blocks
preview.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
}
// Toggle preview visibility
function togglePreview() {
isPreviewVisible = !isPreviewVisible;
if (isPreviewVisible) {
previewPane.classList.remove('hidden');
editorPane.classList.remove('full-width');
} else {
previewPane.classList.add('hidden');
editorPane.classList.add('full-width');
}
}
// Update word count
function updateWordCount() {
const text = editor.value;
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const chars = text.length;
wordCount.textContent = `Words: ${words} | Characters: ${chars}`;
}
// Update status
function updateStatus(text) {
statusText.textContent = text;
}
// Auto-save function
function autoSave() {
if (isDirty && currentContent) {
ipcRenderer.send('save-current-file', currentContent);
isDirty = false;
updateStatus('Auto-saved');
setTimeout(() => updateStatus('Ready'), 2000);
}
}
// IPC event handlers
ipcRenderer.on('file-new', () => {
if (isDirty) {
if (confirm('You have unsaved changes. Do you want to continue?')) {
editor.value = '';
currentContent = '';
isDirty = false;
updatePreview();
updateWordCount();
updateStatus('New file');
}
} else {
editor.value = '';
currentContent = '';
updatePreview();
updateWordCount();
updateStatus('New file');
}
});
ipcRenderer.on('file-opened', (event, { path, content }) => {
editor.value = content;
currentContent = content;
isDirty = false;
updatePreview();
updateWordCount();
updateStatus(`Opened: ${path}`);
});
ipcRenderer.on('file-save', () => {
ipcRenderer.send('save-current-file', editor.value);
isDirty = false;
updateStatus('Saved');
});
ipcRenderer.on('get-content-for-save', (event, path) => {
ipcRenderer.send('save-file', { path, content: editor.value });
isDirty = false;
updateStatus(`Saved: ${path}`);
});
ipcRenderer.on('toggle-preview', () => {
togglePreview();
});
ipcRenderer.on('theme-changed', (event, theme) => {
// Remove all theme classes
document.body.classList.remove('theme-light', 'theme-dark', 'theme-solarized', 'theme-monokai', 'theme-github');
// Add new theme class
if (theme !== 'light') {
document.body.classList.add(`theme-${theme}`);
}
updateStatus(`Theme: ${theme}`);
});
ipcRenderer.on('get-content-for-spreadsheet', (event, format) => {
ipcRenderer.send('export-spreadsheet', { content: editor.value, format });
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + Enter to toggle preview
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
togglePreview();
}
// Tab key in editor
if (e.key === 'Tab' && e.target === editor) {
e.preventDefault();
const start = editor.selectionStart;
const end = editor.selectionEnd;
editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end);
editor.selectionStart = editor.selectionEnd = start + 4;
}
});
// Prevent accidental navigation
window.addEventListener('beforeunload', (e) => {
if (isDirty) {
e.preventDefault();
e.returnValue = '';
}
});